Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [IF][CASE]![JOIN] Złączenie z kilku tabel z warunkami
Forum PHP.pl > Forum > Bazy danych > MySQL
vokiel
Witam. Mam problem ze złączeniem danych z kilku tabel, w zależności od wartości danych w jednej kolumnie.
Dokładniej:
mam 3 tabele:
PRODUCTS
| ID | NAME | .... |

AWARDS
| ID | NAME |...|

OPERATIONS
| ID | .... | ACTION | PRODUCT |

I teraz pojawia się problem bo mamy ogólnie 3 dostępne akcje:
1 - standard
2 - produkt
3 - nagroda

czyli przykładowe rekordy będą wyglądać tak:
OPERATIONS
| 1 | ... | 1 | NULL |
| 2 | ... | 2 | 2 |
| 3 | ... | 3 | 1 |
| 4 | ... | 2 | 1 |
| 5 | ... | 1 | NULL |

W wyniku chciałbym otrzymać:
QUERY
| 1 | ... | 1 | NULL |
| 2 | ... | 2 | AWARD 2 NAME |
| 3 | ... | 3 | PRODUCT 1 NAME |
| 4 | ... | 2 | AWARD 1 NAME |
| 5 | ... | 1 | NULL |

Zatem JOIN chyba odpada, bo nie będzie wiedział z której tabeli dołączać.
Przydałby się IF a może bardziej CASE

skleciłem coś takiego z case, ale nie działa tak jak trzeba, w kolumnie NAME dostaje wszędzie NULL:
  1. SELECT
  2. `OPERATIONS`.`ID` ,
  3. `OPERATIONS`.`ACTION` ,
  4. CASE
  5. WHEN `OPERATIONS`.`ACTION` =2 THEN `AWARDS`.`NAME`
  6. WHEN `OPERATIONS`.`ACTION` =3 THEN `PRODUCTS`.`NAME`
  7. END AS `NAME` ,
  8. FROM `OPERATIONS` LEFT OUTER JOIN `AWARDS` ON `OPERATIONS`.`PRODUCT` = `AWARDS`.`ID`
  9. LEFT OUTER JOIN `PRODUCTS` ON `OPERATIONS`.`PRODUCT` = `PRODUCTS`.`ID`
  10.  
  11. WHERE `OPERATIONS`.`ID` >10 LIMIT 0, 30


A może najlepszym rozwiązaniem byłoby dodanie kolumn dla AWARDS i PRODUCTS?
ddiceman
Pisze z glowy, wiec wymaga to sprawdzenia

  1. SELECT operations.id, ..., IF(awards.id IS NULL, products.name, award.name) AS name FROM operations LEFT JOIN products ON operations.action = 2 AND operations.product = product.id
  2. LEFT JOIN awards ON operations.action = 3 AND operations.product = awards.id
  3. WHERE operations.id > 10 LIMIT 0, 30;
vokiel
Działające poprawnie zapytanie (bez uproszczeń):
  1. SELECT `PROG_POINTS`.`ID` , `PROG_POINTS`.`CODE` , CONCAT( `PROG_USR`.`IMIE` , ' ', `PROG_USR`.`NAZWISKO` ) AS `USR` , `PROG_POINTS`.`POINTS` , `PROG_POINTS`.`ACTION` , `PROG_POINTS`.`DATE_TIME` , CONCAT( `PROG_USR_JB`.`NAME` , ' ', `PROG_USR_JB`.`SURNAME` ) AS `USR_JB` ,
  2. CASE
  3. WHEN `PROG_POINTS`.`ACTION` =3
  4. THEN `PROG_AWARD_LIST`.`AWARD_NAME`
  5. WHEN `PROG_POINTS`.`ACTION` =2
  6. THEN `PROG_PRODUCTS`.`NAME`
  7. END AS `AWARD_NAME`
  8. FROM `PROG_USR` , `PROG_POINTS`
  9. LEFT JOIN `PROG_AWARD_LIST` ON ( `PROG_POINTS`.`ACTION` =3 AND `PROG_POINTS`.`PRODUCT_ID` = `PROG_AWARD_LIST`.`ID` )
  10. LEFT JOIN `PROG_PRODUCTS` ON ( `PROG_POINTS`.`ACTION` =2 AND `PROG_POINTS`.`PRODUCT_ID` = `PROG_PRODUCTS`.`ID` )
  11. LEFT OUTER JOIN `PROG_USR_JB` ON `PROG_POINTS`.`PASSED_BY` = `PROG_USR_JB`.`ID`
  12. WHERE `PROG_POINTS`.`USR_ID` = `PROG_USR`.`ID`
  13. ORDER BY `PROG_POINTS`.`DATE_TIME` DESC LIMIT 0, 100


Musi być CASE zamiast IF, bo IF(awards.id IS NULL, products.name, award.name) skutkuje, że wyświetla zawsze, tylko products.name, zmieniłem na początku na IS NOT NULL, żeby wyświetlał, jak pole nie będzie puste (ale wtedy niezależnie od parametru ACTION wyświetlał własnie tylko "products.name". Zatem skorzystałem z CASE, który okazał się tu idealnym rozwiązaniem.

@ddiceman bardzo pomocny okazał się Twój LEFT JOIN:
  1. LEFT JOIN products ON operations.action = 2 AND operations.product = product.id

Dzięki smile.gif
osiris
Wydaje mi sie ze wydajniejszym zapytaniem byloby:
  1. (
  2. SELECT `PROG_POINTS`.`ID` , `PROG_POINTS`.`CODE` , CONCAT( `PROG_USR`.`IMIE` , ' ', `PROG_USR`.`NAZWISKO` ) AS `USR` , `PROG_POINTS`.`POINTS` , `PROG_POINTS`.`ACTION` , `PROG_POINTS`.`DATE_TIME` AS czas, CONCAT( `PROG_USR_JB`.`NAME` , ' ', `PROG_USR_JB`.`SURNAME` ) AS `USR_JB` , `PROG_AWARD_LIST`.`AWARD_NAME` AS `AWARD_NAME`
  3. FROM `PROG_USR` INNER JOIN `PROG_POINTS` ON (`PROG_POINTS`.`USR_ID` = `PROG_USR`.`ID` AND `PROG_POINTS`.`ACTION` = 3)
  4. LEFT JOIN `PROG_AWARD_LIST` ON `PROG_POINTS`.`PRODUCT_ID` = `PROG_AWARD_LIST`.`ID`
  5. LEFT JOIN `PROG_USR_JB` ON `PROG_POINTS`.`PASSED_BY` = `PROG_USR_JB`.`ID`
  6. )
  7. UNION ALL
  8. (
  9. SELECT `PROG_POINTS`.`ID` , `PROG_POINTS`.`CODE` , CONCAT( `PROG_USR`.`IMIE` , ' ', `PROG_USR`.`NAZWISKO` ) AS `USR` , `PROG_POINTS`.`POINTS` , `PROG_POINTS`.`ACTION` , `PROG_POINTS`.`DATE_TIME` AS czas , CONCAT( `PROG_USR_JB`.`NAME` , ' ', `PROG_USR_JB`.`SURNAME` ) AS `USR_JB` , CASE WHEN `PROG_POINTS`.`ACTION` = 2 THEN `PROG_PRODUCTS`.`NAME` ELSE NULL END AS `AWARD_NAME`
  10. FROM `PROG_USR` INNER JOIN `PROG_POINTS` ON (`PROG_POINTS`.`USR_ID` = `PROG_USR`.`ID` AND `PROG_POINTS`.`ACTION` <> 3)
  11. LEFT JOIN `PROG_PRODUCTS` ON `PROG_POINTS`.`PRODUCT_ID` = `PROG_PRODUCTS`.`ID`
  12. LEFT JOIN `PROG_USR_JB` ON `PROG_POINTS`.`PASSED_BY` = `PROG_USR_JB`.`ID`
  13. )
  14. ORDER BY czas DESC LIMIT 0, 100


Wg mnie powyzszy sposob jest wydajnieszy bo wykonujesz jedno zlaczenie mniej, a operacja zlaczenia jest najbardziej obciazajaca.

Pozdrawiam
vokiel
Dzięki za zainteresowanie smile.gif
Niestety, w Twoim przykładzie, zapytanie wybiera tylko te rekordy, dla których `PROG_POINTS`.`ACTION` = 3 sad.gif
osiris
Poprawilem
vokiel
No i działa biggrin.gif
Trudno mi teraz mierzyć wydajność obu zapytań, bo na stan dzisiajszy w bazie jest jeszcze niewiele rekordów.
Pierwsze wyniki

Twoje zapytanie:
- 0.0035 s
- 0.0021 s
- 0.0020 s

Poprzednie zapytanie:
- 0.0021 s
- 0.0014 s
- 0.0009 s

Zatem na razie trudno wskazać szybsze. Zobacze z czasem jak przybędzie danych.
Wielkie dzięki za pomoc!
pozdrawiam
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.