Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Optymalizacja zapytania
Forum PHP.pl > Forum > PHP
matino
Mam tabelę, w której w każdym rzędzie przechowuję m.in. datę utworzenia oraz login.
Jako rezultat ma wyjść tabelka - kolumny loginy, wiersze daty a w środku liczba wystąpień w tabeli dla danej daty i danego loginu:

data | login1 | login2 | suma
2011-06-18 6 7 13
2011-06-19 2 1 3
Razem: 8 8 16

Na tą chwilę zrobiłem to w sposób taki, że pobieram jednym selectem distinct daty, drugim selectem loginy.
Następnie lecę podwójnym foreach i dla każdej daty, wykonuję select count(*) dla każdego loginu.
Takie rozwiązanie jest jednak mało wydajne (przy 12 datach i 9 loginach łączny czas zapytania zajmuje 2 sekundy smile.gif).

Może ma ktoś wydajniejszy sposób do przetestowania?

Poniżej kod jakby kogoś interesował:
  1. public static function get_cases_by_users($users, $start = NULL, $end = NULL)
  2. {
  3. if (!is_array($users))
  4. {
  5. return NULL;
  6. }
  7. if (empty($start))
  8. {
  9. $start = 0;
  10. }
  11. if (empty($end))
  12. {
  13. $end = 9999999999;
  14. }
  15.  
  16. // Get dates first where cases were generated
  17. $dates = DB::select(array('FROM_UNIXTIME("create_date", \'%Y-%m-%d\')', 'create_date'))
  18. ->distinct('create_date')
  19. ->from(table')
  20. ->where('create_date', 'BETWEEN', array($start, $end + Date::DAY))
  21. ->order_by('create_date')
  22. ->execute()->as_array('create_date');
  23.  
  24. // Count users for calculated dates
  25. $result = array();
  26. foreach ($dates as $date => $value)
  27. {
  28. $result[$date] = array();
  29. foreach ($users as $user)
  30. {
  31. $count = DB::select(array('COUNT("username")', 'total'))
  32. ->from('table')
  33. ->where(array('FROM_UNIXTIME("create_date", \'%Y-%m-%d\')', NULL), 'LIKE', $date . '%')
  34. ->and_where('username', '=', $user)
  35. ->execute()->as_array();
  36. $count = Arr::flatten($count);
  37. $result[$date][$user] = $count['total'];
  38. }
  39. }
  40. return $result;
  41. }
tehaha
najlepiej zacznij od początku - napisz co potrzebujesz uzyskać, a nie to co wydaje Ci się rozwiązaniem, bo najczęściej takie kombinacje są wynikiem źle zaprojektowanej bazy danych
matino
Baza zaprojektowana jest dobrze, wydaję mi się tylko, że można to zrobić dużo prościej...
Rozwiązaniem ma być ilość wystąpień loginu usera dla danych dat.
tehaha
eh.. nie ma to jak ludzie, którzy nie pozwalają sobie pomóc, słuchaj nikt nie siedzi w Twojej głowie i nikt nie wie co Ty sobie wymyśliłeś, chcesz pomocy? ok, ale podaj strukturę tabel i napisz dokładnie co chcesz z nich pobrać, zapytanie w pętli to bardzo złe rozwiązanie i na 99,9% można to rozwiązać przy pomocy kilku zapytań, bo chociażby zamiast w pętli można dać WHERE IN() a porównanie daty przerzucić na php, ale ciężko powiedzieć coś konkretnego skoro nie podałeś żadnych konkretnych informacji
matino
Struktura:
  1. CREATE TABLE `users` (
  2. `id` tinyint(1) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `email` varchar(50) NOT NULL,
  4. `username` varchar(32) NOT NULL DEFAULT '',
  5. `password` varchar(64) NOT NULL,
  6. PRIMARY KEY (`id`),
  7. UNIQUE KEY `uniq_username` (`username`),
  8. UNIQUE KEY `uniq_email` (`email`)
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  1. CREATE TABLE `sprawy` (
  2. `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `create_date` int(11) UNSIGNED NOT NULL,
  4. `username` varchar(32) NOT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=MYISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

W tabeli `sprawy` podałem tylko niezbędne kolumny.

Dane wjeściowe:
Data początkowa i data końcowa w formacie unix timestamp

Cel:
Znaleźć do ilu spraw, dla wszystkich dni zawartych pomiędzy datami wejściowymi, został przydzielony każdy z użytkowników z tabeli users.
Generalnie chodzi mi o wyświetlenie tabeli jak poniżej:
  1. Data | jan_kowalski | piotr_nowak | zdzisio_misio
  2. 2011-06-11 | 5 | 3 | 6
  3. 2011-06-12 | 7 | 4 | 2
  4. 2011-06-14 | 1 | 6 | 7


tehaha
Cytat
Baza zaprojektowana jest dobrze

No i właśnie nie...cała komplikacja bierze się ze złego typu kolumny w bazie danych, dałeś datę jako INT i nie możesz po niej grupować bo pewnie jest tam inny timestamp dla każdego rekordu.
1. w sprawy powinno być id użytkownika a nie jego nazwa
2. data powinna być jako date

Wtedy wszystko pobierasz jednym zapytaniem, grupując po 2 kolumnach:

  1. SELECT a.id, a.date, COUNT(a.id) AS num, b.name FROM sprawy a LEFT JOIN users b ON a.user_id = b.id GROUP BY date, user_id


sprawdziłem u siebie zapytanie zwraca liczbę spraw dla użytkownika i datę w php bardzo łatwo wyświetlisz to w takiej tabelce jaką chcesz uzyskać

zapytanie zwróciło mi taki wynik:
  1. ID DATE NUM NAME
  2. 1 2011-06-11 3 Tomek
  3. 4 2011-06-11 2 Stefan
  4. 6 2011-06-11 3 Mietek
  5. 9 2011-06-12 1 Tomek
  6. 10 2011-06-12 1 Stefan
  7. 11 2011-06-12 1 Mietek
  8. 12 2011-06-13 4 Tomek
  9. 16 2011-06-14 1 Tomek
  10. 17 2011-06-14 4 Stefan
matino
Właśnie problem jest w tym, że data jest używana również w innych częściach aplikacji, gdzie znacznie wygodniej jest operować na INT niż na DATE.
Id też wolę nie wstawiać, bo jeśli skasujesz użytkownika, to nie dowiesz się jaki username odpowiadał jego id.
Mimo wszystko dzięki za poświęcony czas smile.gif
tehaha
no to dołóż drugą kolumnę z datą, w php łatwo napiszesz skrypt którzy stworzy date dla już obecnych rekordów i wtedy będzie po kłopocie, bo tak to przy dużej ilości dat i użytkowników będzie masakra
matino
Dzięki, o to mi chodziło smile.gif

PS. Obeszło się nawet bez drugiej kolumny smile.gif
  1. SELECT username, FROM_UNIXTIME(create_date, '%Y-%m-%d') AS date, COUNT(id) AS num FROM sprawy GROUP BY date, username
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.