Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: LEFT JOIN z osobnymi warunkami dla każdej z tabel
Forum PHP.pl > Forum > Bazy danych > MySQL
lukesh
Cześć! Mam dwie tabele: items i repetitions. W tabeli items przechowywane są różne pytania (fiszki). W momencie, gdy użytkownik odpowie na pytanie/wykona jakieś ćwiczenia, informacja o tym zapisywana jest w tabeli repetitions. Chcąc wyświetlić z tabeli items pytania, których użytkownik jeszcze nie odpowiedział (a więc nie mają odpowiadającego im rekordu w tabeli repetitions), wykonuję zagnieżdżone zapytanie z LEFT JOIN. Problem polega na tym, że zarówno w tabeli items i repetitions, poszczególny elementy należą do różnych grup. W tabeli items będzie docelowo około 30 tys. wierwszy, które podzielone będą na 3 grupy po 10 tys. każda. Tabela repetitions docelowo powinna zawierać do około 10 milionów rekordów.
Oryginalne zapytanie wygląda w ten sposób:
  1. SELECT * FROM (SELECT id, question, answer FROM items WHERE courseid='$courseid') AS items LEFT JOIN (SELECT itemid, nextrepetition FROM repetitions WHERE userid='$userid' AND courseid='$courseid') AS repetitions ON items.id=repetitions.itemid WHERE repetitions.nextrepetition IS NULL LIMIT 1

Jak widać w zapytaniu, najpierw wybieram fragment pierwszej tabeli, potem fragment drugiej tabeli i łączę je ze sobą tak, aby wybrane zostały wszystkie rekordy z tabeli po lewej stronie i odpowiadające im rekordy z tabeli po prawej stronie. Niektóre rekordy z tabeli po lewo nie mają swoich odpowiedników po prawo i to własnie te rekordy wybieram, używając WHERE IS NULL.
Czy ktoś mógłby doradzić mi, jak takie pytanie można najlepiej zoptymalizować? Myślałem np. o podzieleniu tabeli items na 3 tabele tak, aby nie było potrzeby określania warunku na tej tabeli. A może najlepszym rozwiązaniem będzie każdemu użytkownikowi z góry przepisać zakładane 10 tys. rekordów odpowiadających rekodom z tablicy po lewo? (Ale w tym wypadku, w przypadku usunięcia lub dodania przynajmniej jednego elementu z tabeli po lewo, będę musiał sprawdzać, czy element został usunięty lub dodany kilka tysięcy razy po prawej stronie.) Bardzo proszę o pomoc!
Kostek.88
A nie mozesz zrobic czegos na wzor
  1. SELECT tabela1.*, tabela2.*, tabela3*, tabela4.*
  2.  
  3. FROM tabela1
  4. LEFT JOIN tabela2 ON tabela1.costam = tabela2.costa
  5. LEFT JOIN tabela3 ON tabela1.costam1 = tabela3.costam
  6. LEFT JOIN tabela4 ON tabela1.costam1 = tabela4.costam
  7.  
  8. WHERE tabela1.costam = costam AND tabela2.costam = costam AND tabela3.costam = costam

questionmark.gif

Ja bym najpierw to pozlaczal, a na koniec wstawil warunki.
Ew. dolaczyc jeszcze GROUP BY jesli bedzie Ci to przebne...
lukesh
Hm, problem z warunkiem WHERE na końcu jest taki, że złączenie LEFT JOIN jest na komórce, która dla tabeli z prawej strony będzie przyjmować wartość NULL. Co prawda, dla obu tabel jest warunek WHERE courseid='$courseid', to jeśli ten warunek zapiszę na końcu tabeli, wtedy nie pobierze mi wszystkich wierszy z tabeli po lewej stronie, które spełniają ten warunek, ale wszystkie wiersze, które spełniają ten warunek naraz.
Myślę, że narazie zostawię, jak jest, a z czasem zobaczę, jak będzie to wszystko działało wraz z rozbudową serwisu. Narazie zapytanie wykojune się bardzo szybko, ale w tabelach nie ma wielu rekordów (chociaż dodałem po kilkadzesiąt tysięcy rekordków dla testów).

Zastanawia mnie jedna rzecz - czy jeśli w środku zapytanie SELECT * FROM wstawiam kolejne zapytanie w nawiasach (SELECT * FROM ...), to MySQL buferuje do pamięci całą tak wybraną tabelę z zapytania w nawiasach? Co się właściwie dzieje? Czy tworzy się wtedy jakaś tymczasowa tabela dla danego zapytania i np. przenoszone tam jest 10 tys. rekordów, które spełniają dany warunek?

Pozdrawiam! smile.gif
Kostek.88
Co do pierwszego akapitu, to az mnie to zaciekawilo. Mozesz dac jakis zrzut bazy i kilka rekordow przykladowych? Nie potrafie sobie tego wyobrazic. Co do reszty, to w zasadzie nie jestem wystarczajaco kompetentny w tej kwestii, ale dane na pewno nie przenosza sie do tymczasowej tabeli, ktora zostala utworzona na te potrzeby. Na pewno sa w jakis sposob buforowane dopoki zapytanie sie nie skonczy, potem wyswietlany jest wynik.
lukesh
Udało mi się trochę poprawić zapytanie i obecnie ma ono taką formę:

  1. SELECT items.id, items.question, items.answer FROM items LEFT JOIN (SELECT itemid FROM repetitions WHERE userid='$userid' AND courseid='$courseid' LIMIT 1000) AS repetitions ON items.id=repetitions.itemid WHERE items.courseid='$courseid' AND repetitions.itemid IS NULL LIMIT 1


W tabeli items znajdują się fiszki - id, pytanie, odpowiedź (i kilka innych informacji, takich jak np. adres do pliku audio itd.) oraz kolumna courseid, która wskazuje, do jakiej kategorii/jakiego kursu należą dane fiszki (może to być np. słówka z angielskiego, niemieckie czasowniki albo cokolwiek innego). W tabeli repetitions znajdują się informacje o tym, czy i jak użytkownik zna daną fiszkę. Są tam również kolumny: id, userid, courseid, itemid. Jeśli użytkownik nie ćwiczył jeszcze wybranej fiszki, w tabeli repetitions nie ma odpowiednika items.id=repetitions.itemid. Na początku chciałem pobrać fragment pierwszej tabeli (z warunkiem WHERE courseid), potem pobrać zawartość drugiej (z warunkiem userid, courseid) i połączyć te tabele za pomocą LEFT JOIN na items.id=repetitions.itemid, dzięki czemu lewa tabela wyświetliłaby się w całości wraz z odpowiednikami z tabeli prawej. Powyższe zapytanie wygląda trochę inaczej, w nawiasach mam tylko tabelę z powtórkami, z której wybieram tylko itemid, dopasowuję do całości i z całości wybieram te wyniki, które przypisane są do danego kursu. Myślę, że jest to bardziej optymalne zapytanie od poprzedniego, chociaż pewności nie mam. Zobaczymy, jak sprawdzi się w praktyce, jak projekt będzie skończony. 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.