Какие проблемы могут возникнуть при использовании left join с двойными join
Что проверяют
Понимаете ли вы, как соединения множат строки и как LEFT JOIN незаметно превращается в INNER. Для Java-разработчика следом почти всегда идёт то же самое в Hibernate: fetch join нескольких коллекций.
Как отвечать
Главные ловушки на уровне SQL:
- размножение строк:
users LEFT JOIN orders LEFT JOIN payments, где заказы и платежи независимо привязаны к пользователю. У пользователя 3 заказа и 4 платежа, в результате 12 строк, иSUM(o.amount)завышен вчетверо. Лечится агрегацией в подзапросах или CTE до соединения, чтобы на пользователя приходилась одна строка; - условие в
WHERE:LEFT JOIN orders o ... WHERE o.status = 'PAID'отбрасывает строки, гдеoпустой, и получается обычныйINNER JOIN. Условие на правую таблицу пишется вON; - цепочка с
INNER:A LEFT JOIN B ... JOIN C ON c.b_id = b.idвырезает строки, гдеBне нашлось, потому чтоNULLни с чем не соединяется. Дальше по цепочке тоже нуженLEFT JOIN; - *`COUNT()
противCOUNT(o.id)`**: первый даёт 1 пользователю без заказов, второй честный 0; DISTINCTкак пластырь: прячет дубли в выдаче, но не чинит завышенные суммы и заставляет базу сортировать или хешировать весь результат.
В Hibernate это выглядит так. JOIN FETCH двух коллекций типа List падает с MultipleBagFetchException, а с Set проходит молча и тянет декартово произведение. Пагинация вместе с fetch join коллекции выполняется в памяти, о чём Hibernate пишет предупреждение в лог. Решения: грузить коллекции отдельными запросами, включить @BatchSize или hibernate.default_batch_fetch_size, брать в @EntityGraph одну коллекцию за раз.
Где спотыкаются
Пишут DISTINCT и считают задачу решённой, хотя агрегаты по-прежнему посчитаны по размноженным строкам.
Не могут объяснить, почему фильтр в WHERE «съел» пользователей без заказов.
Лечат MultipleBagFetchException заменой List на Set. Исключение пропадает, а декартово произведение остаётся, и на реальных объёмах одна страница выдачи тянет из базы сотни тысяч строк.