Как получить данные из нескольких таблиц в SQL?
Что проверяют
Умеете ли вы соединять таблицы осознанно: выбирать тип соединения под задачу и предсказывать, сколько строк вернёт запрос. Для Java-разработчика следом почти всегда спрашивают, как то же самое выглядит в JPA и откуда берётся проблема N+1.
Как отвечать
Основной инструмент это JOIN с условием в ON, обычно по внешнему ключу:
SELECT c.name, o.id, o.created_at
FROM customers c
JOIN orders o ON o.customer_id = c.id;Дальше типы соединений:
INNER JOIN: только строки, у которых нашлась пара. Покупатели без заказов в результат не попадут;LEFT JOIN: все строки левой таблицы, а там, где пары нет, колонки правой заполненыNULL;RIGHT JOIN: зеркальный вариант, на практике его переписывают вLEFT, поменяв таблицы местами;FULL OUTER JOIN: строки обеих таблиц, недостающая сторона заполняетсяNULL. В MySQL его нет, эмулируют черезUNIONдвух внешних соединений;CROSS JOIN: декартово произведение, нужно редко и осознанно.
Кроме соединений есть подзапросы с EXISTS и IN, а также UNION, когда нужно не склеить строки, а сложить результаты.
Из Java это делается двумя путями. В JDBC или JdbcTemplate вы пишете SQL как есть. В JPA соединение идёт по связям сущностей: select o from Order o join fetch o.customer. Именно join fetch загружает связь одним запросом и спасает от N+1, когда для каждого заказа отдельным запросом подтягивается покупатель.
Где спотыкаются
Не думают о кратности. Соединение «один ко многим» размножает строки родителя, и SUM по колонке покупателя после такого JOIN даёт завышенную сумму.
Ставят условие на правую таблицу в WHERE после LEFT JOIN. Строки с NULL отсекаются, и соединение молча превращается во внутреннее. Такой фильтр нужно переносить в ON.
Делают join fetch двух коллекций сразу. Если обе объявлены как List, Hibernate бросает MultipleBagFetchException, а если как Set, запрос проходит, но база возвращает декартово произведение строк.