Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Zliczanie wystąpień rekordu w tabelach
Forum PHP.pl > Forum > Bazy danych > MySQL
zoltodziob
Witajcie.
Postanowiłem założyć konto na Forum. Od kilku miesięcy korzystałem z jego pomocy, głównie przeszukując archiwalne tematy, jednak dzisiaj potrzebuję Waszej pomocy.
Mam do wykonania takie zapytanie MYSQL: muszę sprawdzić czy podany rekord id_klienta o wartości dajmy na to 10 w tabeli KLIENCI został użyty w innych tabelach. Zapytanie miało by zwraca łączną liczbę wystąpień we wszystkich tabelach (np. w 6 podanych tabelach). Próbowałem za pomocą JOIN ale strasznie skomplikowane przy wielu tabelach. Czytałem też o HAVING, ale trochę tego nie ogarniam. Dodam też pracuję przy pomocy Codeigniter. Macie jakiś pomysł? wink.gif
mmmmmmm
Funkcje agregujące (Count) przyjacielem twym. Łącz kolejno z każdą z tabel (tych 6 podanych) i daj WHERE + Count. Sprawdzić ilość możesz sobie na kliencie.
zoltodziob
Nie wiem czy dobrze rozumiem, ale poniższy kod na pewno nie działa:
  1. SELECT COUNT(k.klient_id) AS ilość
  2. FROM klienci k
  3. JOIN tabela1 t1 ON t1.klient_id = k.klient_id
  4. JOIN tabela2 t2 ON t2.klient_id = k.klient_id
  5. WHERE k.klient_id = 10
lukasz1985
Zlicz z każdej tabeli osobno i zsumuj

  1.  
  2. $a = $this->db->where("id_klienta", $id_klienta)->get("tabela1")->result_array();
  3. $count_a = count($a);
  4.  
  5. $b = $this->db->where("id_klienta", $id_klienta)->get("tabela2")->result_array();
  6. $count_b = count($b);
  7.  
  8. $c = $this->db->where("id_klienta", $id_klienta)->get("tabela3")->result_array();
  9. $count_c = count($c);
  10.  
  11. $suma = $count_a + $count_b + $count_c;
zoltodziob
Takiego rozwiązania chciałem uniknąć. Chciałem to zrobić przy pomocy jednego zapytania. Generalnie potrzebuję tego do sprawdzenia, czy dany klient może zostać usunięty z bazy, to znaczy czy nie jest do niego przypisane żadne zamówienie, płatność i inne. Jeśli takowe wystąpią to system miałby nie pozwolić usunąć.
viking
Od tego jest sam silnik BD. Innodb oraz ON DELETE RESTRICT.
DarkAbso
Cytat(viking @ 10.05.2015, 15:12:58 ) *
Od tego jest sam silnik BD. Innodb oraz ON DELETE RESTRICT.

Dokładnie, pod warunkiem, iż dobrze relacje są porobione. Jeśli nie to spróbuj usuwać takim zapytaniem :
  1. DELETE FROM klient
  2. WHERE id = 10
  3. AND id NOT IN ( SELECT id_klient FROM test1
  4. UNION ALL
  5. SELECT id_klient FROM test2)

Gdzie NOT IN łączysz sobie tabele za pomocą UNION ALL. , a gdzie id to interesujące Ciebie id klienta.

Jeżeli chcesz zliczać to:
  1. SELECT COUNT(id_klient) FROM
  2. (SELECT id_klient FROM test1
  3. UNION ALL
  4. SELECT id_klient FROM test2) AS test
  5. WHERE id_klient = 10
mmmmmmm
Omatkoboska...
NOT IN bez warunku na NULL? http://sqlfiddle.com/#!9/9f0e5/1
Powinno być tak: http://sqlfiddle.com/#!9/9f0e5/2
W tym drugim wydajnościowo do dupy maksymalnie. WHERE powinien być w środku, w każdym podzapytaniu.
DarkAbso
Cytat(mmmmmmm @ 11.05.2015, 15:08:37 ) *
Omatkoboska...
NOT IN bez warunku na NULL? http://sqlfiddle.com/#!9/9f0e5/1
Powinno być tak: http://sqlfiddle.com/#!9/9f0e5/2
W tym drugim wydajnościowo do dupy maksymalnie. WHERE powinien być w środku, w każdym podzapytaniu.

Ciężko odpowiedzieć precyzyjnie na pytanie jeżeli nie znam schematu bazy. Jeżeli w tabelach na id_klient jest ustawione, iż nie może przyjmować wartości null to niema problemu, w ręcz dodatkowy warunek jest zbędny "bo wydajność" (niby niewielka będzie różnica, ale przy komercyjnych rozwiązania każdy czas się liczy).
Co do drugiego zapytania to masz rację mogłem ograniczyć wcześniej i nie rzeźbić po wszystkich rekordach. Zresztą jest to proponowane rozwiązanie. Ten problem ma kilka rozwiązań, a mysql niestety ma kilka ograniczeń między innymi brak klauzuli WITH za pomocą której można w przejrzysty sposób ładnie załatwić sprawę bez zagnieżdżonych zapytań. Sprawę można załatwić również za pomocą INNER JOIN lub NOT EXISTS. Jak to się mówi temat woda, ale dzięki za zwrócenie uwagi. Na drugi raz będę dokładniej analizował problemy na tym forum, aby podać precyzyjną odpowiedź.
zoltodziob
Zgadza się id_klient nie może przyjmować wartości NULL. Kombinuję z łączeniem JOIN, ale nie wychodzi. Zastanawiam się nad ON DELETE RESTRICT. Czytałem trochę o tym, ale nie wiem czy odpowiednio uda mi się ustawić odpowiednie relacje - jestem laikiem w tej sprawie. A wy jakie rozwiązanie proponujecie, żeby było jak najbardziej wydajne? Baza będzie się składać z ponad 3000 rekordów jeśli chodzi o klientów i do tego dochodzą tabele związane z zamówieniami itp.
salfunglandyare
on delete restrict lub no action spowoduje, że podczas próby usunięcia dostaniesz warning z bazy danych, że rekordu nie można usunąć, bo istnieją rekordy zależne - w innym przypadku - usuniesz. Constraints nie zadziała przy myisam (domyślnym silniku mysql), musisz ustawić na innodb.
Jeśli chcesz załatwić to jednym zapytaniem - da się - left join, np:
  1. SELECT klienci.id, (count(a.id) + count(b.id) + count(c.id)) AS ilosc FROM klienci
  2. LEFT JOIN costam1 AS a ON (a.id_klient = klienci.id)
  3. LEFT JOIN costam2 AS b ON (b.id_klient = klienci.id)
  4. LEFT JOIN costam3 AS c ON (c.id_klient = klienci.id)
  5. WHERE klienci.id = ID_KLIENTA
  6. GROUP BY klienci.id;


podstawiasz swoje dane, w wyniku dostajesz ID oraz ilosc jako sumę rekordów ze wszystkich tabel

//edit: Jeszcze info o constraints - fajna opcja - cascade, w przypadku, gdy chcesz usunąć rekord, cascade pozwoli Ci usunąć wszystkie rekordy zależne, ale WAŻNE jest odpowiednie stowrzenie struktury tabel, niekiedy przydaje się set null - tam gdzie zamiast usunięcia rekordu powinna zostać wyrzucona dana o identyfikatorze elementu
zoltodziob
Zrobię chyba tak jak zaproponował salfunglandyare. Wszystkim dziękuję za udział w dyskusji.
DarkAbso
Zapytanie z left join'ami prawdopodobnie będzie najdłużej się wykonywało, ale 3000 rekordów to "żadna" ilość dla baz danych. Rozwiązanie wybierz takie na którym będzie najłatwiej Tobie pracować.
salfunglandyare
Cóż, jestem pewny, że szybciej niż subqueries wink.gif ale też nie napisałem, że to rozwiązanie optymalne - ale chyba najlepsze jeśli chodzi o rozwiązanie w stylu 'jedno zapytanie' biggrin.gif
DarkAbso
Nie do końca i już wytłumaczę dlaczego (chociaż mogę się mylić ). Najpierw budujesz widok tabeli klienci połączonej z innymi (costam1 itd...), a następnie dopiero ograniczasz where (czyli kleimy wszystko a później dopiero wycinamy). Najlepiej było by od razu ograniczyć do rekordów które potrzebujemy. W tym wyniku takie zapytanie zagnieżdżone będzie szybsze:
  1. SELECT COUNT(klient_id) FROM
  2. (SELECT klient_id FROM tabela1 WHERE klient_id = 1
  3. UNION ALL
  4. SELECT klient_id FROM tabela2 WHERE klient_id = 1
  5. UNION ALL
  6. SELECT klient_id FROM tabela3 WHERE klient_id = 1) AS test

Do tego użyte jest jedno wywołanie funkcji count i brak dodawania co też wpływa na tempo zapytania.

W Twoim zapytaniu można kombinować z ograniczeniami od razu przy join'ach co by przyśpieszyło zapytanie.

Zresztą z ciekawości wykonałem test i go powtórzyłem kilka razy. Co prawda na małej ilości danycm ale moje zapytanie wykonuje się 0 ms, a Twoje 1 ms. Z ciekawości jutro sprawdzę na większej ilości danych. smile.gif
salfunglandyare
masz rację, ale ja pisałem o subquerries w sensie select w where innego selecta. Co do unii, masz pewnie racje w przypadku, gdy tych tabel jest relatywnie mało, gdyż każdy select jest wykonywany z osobna i "doklejany" do wyniku, suma z tego może wykonywać się krócej dla takich zapytań, kto wie, czy nie agregując tak:
  1. SELECT sum(s) AS suma FROM
  2. (
  3. SELECT count(klient_id) AS s FROM tabela1 WHERE klient_id = 1
  4. UNION ALL
  5. SELECT cpunt(klient_id) AS s FROM tabela2 WHERE klient_id = 1
  6. UNION ALL
  7. SELECT count(klient_id) AS s FROM tabela3 WHERE klient_id = 1
  8. );

nie wyjdzie szybciej. Joiny maja swoja potege w join buffer i w indeksach smile.gif
DarkAbso
Jak już pisałem, temat woda. Jest kilka rozwiązań, ale dobrze, że ktoś porusza kwestię wydajności zapytań. Dla zoltodziob nie będzie różnicy, którą opcję wybierze jak ma kilka tabel po kilkaset rekordów. Problem pojawia się jak jest kilkadziesiąt tabel po kilka milionów rekordów. smile.gif Miło, że ktoś chce się dzielić swoimi spostrzeżeniami i wiedzą. Jutro jak się uda to przetestuje rozwiązania na większe ilości danych. smile.gif
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.