Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Optymalizacja zapytania
matino
post
Post #1





Grupa: Zarejestrowani
Postów: 175
Pomógł: 30
Dołączył: 9.08.2007

Ostrzeżenie: (0%)
-----


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. }
Go to the top of the page
+Quote Post
tehaha
post
Post #2





Grupa: Zarejestrowani
Postów: 1 748
Pomógł: 388
Dołączył: 21.08.2009
Skąd: Gdynia

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
matino
post
Post #3





Grupa: Zarejestrowani
Postów: 175
Pomógł: 30
Dołączył: 9.08.2007

Ostrzeżenie: (0%)
-----


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.
Go to the top of the page
+Quote Post
tehaha
post
Post #4





Grupa: Zarejestrowani
Postów: 1 748
Pomógł: 388
Dołączył: 21.08.2009
Skąd: Gdynia

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
matino
post
Post #5





Grupa: Zarejestrowani
Postów: 175
Pomógł: 30
Dołączył: 9.08.2007

Ostrzeżenie: (0%)
-----


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


Go to the top of the page
+Quote Post
tehaha
post
Post #6





Grupa: Zarejestrowani
Postów: 1 748
Pomógł: 388
Dołączył: 21.08.2009
Skąd: Gdynia

Ostrzeżenie: (0%)
-----


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


Ten post edytował tehaha 18.06.2011, 16:52:23
Go to the top of the page
+Quote Post
matino
post
Post #7





Grupa: Zarejestrowani
Postów: 175
Pomógł: 30
Dołączył: 9.08.2007

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
tehaha
post
Post #8





Grupa: Zarejestrowani
Postów: 1 748
Pomógł: 388
Dołączył: 21.08.2009
Skąd: Gdynia

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
matino
post
Post #9





Grupa: Zarejestrowani
Postów: 175
Pomógł: 30
Dołączył: 9.08.2007

Ostrzeżenie: (0%)
-----


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


Ten post edytował matino 18.06.2011, 18:28:50
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 22.08.2025 - 08:03