Być może nie powinienem publicznie przyznawać się, że popełniłem tak podstawowy błąd w konstruowaniu zapytania SQL, ale po jego odkryciu szczerze się uśmiałem, a skoro śmiech to zdrowie to się nim podzielę.
W uproszczeniu: mam tabelkę z danymi klientów (client) i tabelkę produktów (product), pomiędzy którymi istnieje relacja jeden do wielu. Wyciąganie danych do pokazania jest zrealizowane w Doctrine w następujący sposób:
$qb ->select('customer, product') ->from($this->getEntityName(), 'customer') ->leftJoin('customer.products', 'product') ->where('customer.identifier = :identifier') ->setParameter('identifier', $identifier);
Dostałem za zadanie zmienić wyświetlanie danych w ten sposób by pokazywać jedynie aktywne produkty. Zrobiłem to tak:
$qb ->select('customer, product') ->from($this->getEntityName(), 'customer') ->leftJoin('customer.products', 'product') ->where('customer.identifier = :identifier') ->andWhere('product.isActive = 1') ->setParameter('identifier', $identifier);
Ku memu zdziwieniu w wypadku klienta, który ma tylko jeden nieaktywny produkt wynik zapytania był pusty. No jakże to?! Przecież klient jak najbardziej istnieje, a ponieważ nie ma produktów spełniających kryteria wyszukiwania (isActive = 1) to w miejscu danych produktu powinny być NULLe.
Swój błąd zrozumiałem dopiero, kiedy spojrzałem na generowane przez Doctrine zapytanie SQL. W uproszczeniu wygląda ono tak:
SELECT c.name, p.product_type FROM customer c LEFT JOIN product p ON c.id = p.customer_id WHERE c.identifier = '65092900214' AND p.is_active = 1
Ponieważ warunek p.is_active = 1 sprawdzamy w części WHERE
zapytania to eliminujemy z zapytania wszystkie wiersze, w których on nie jest spełniony. Zatem jeśli klient ma tylko jeden nieaktywny produkt to wynik zapytania będzie pusty.
Oczekiwany efekt (wiersz z NULLami w miejscu danych produktu) uzyskamy przenosząc warunek do części LEFT JOIN
zapytania:
SELECT c.name, p.product_type FROM customer c LEFT JOIN product p ON c.id = p.customer_id AND p.is_active = 1 WHERE c.identifier = '65092900214'
Ponieważ warunek dotyczy złączenia to jeśli nie zostanie on spełniony to złączenie nie zostanie wykonane, ale dane klienta zostaną zwrócone. Dokładnie tak jak chciałem.
W Doctrine poprawne zapytanie należy zbudować tak:
$qb ->select('customer, product') ->from($this->getEntityName(), 'customer') ->leftJoin('customer.products', 'product', 'WITH', 'product.isActive = 1') ->where('customer.identifier = :identifier') ->setParameter('identifier', $identifier);