Как убрать дубликаты строк при SQL JOIN
При объединении таблиц через LEFT JOIN или INNER JOIN часто возникает ситуация, когда одна строка из первой таблицы повторяется несколько раз из-за множественных совпадений во второй таблице. Это типичная проблема для начинающих разработчиков. Разберём на конкретном примере, как её исправить.
Почему дублируются строки при JOIN
Когда вы соединяете таблицу категорий servers_texts с таблицей элементов servers_world по внешнему ключу, каждая запись из первой таблицы дублируется для каждого совпадающего значения во второй. Например, если категории с id=10 соответствует несколько строк в servers_world с typew=1, то в результате запроса категория появится столько же раз.
Исходный запрос:
SELECT `servers_texts`.*, `servers_world`.`typew`
FROM `servers_texts`
LEFT JOIN `servers_world` ON `servers_texts`.`id` = `servers_world`.`cat_id`
WHERE `servers_texts`.`premod` = 1
AND `servers_texts`.`cat_id` = '10'
AND `servers_world`.`typew` = '1'
ORDER BY `vote` DESC, `servers_texts`.`id` DESC;Этот запрос возвращает категорию 10 дважды, если в servers_world есть две записи с cat_id=10 и typew=1.
Способы устранения дублирования
1. Использование DISTINCT
Самый простой способ - добавить ключевое слово DISTINCT после SELECT. Он убирает полностью повторяющиеся строки в результирующем наборе.
SELECT DISTINCT `servers_texts`.*
FROM `servers_texts`
LEFT JOIN `servers_world` ON `servers_texts`.`id` = `servers_world`.`cat_id`
WHERE `servers_texts`.`premod` = 1
AND `servers_texts`.`cat_id` = '10'
AND `servers_world`.`typew` = '1'
ORDER BY `vote` DESC, `servers_texts`.`id` DESC;Обратите внимание: мы убрали servers_world.typew из SELECT, так как иначе строки всё равно будут различаться по этому полю.
2. Применение GROUP BY
Если нужно сохранить какие-то агрегированные данные из второй таблицы (например, количество элементов), используйте GROUP BY по идентификатору первой таблицы.
SELECT `servers_texts`.*, COUNT(`servers_world`.`id`) AS cnt
FROM `servers_texts`
LEFT JOIN `servers_world` ON `servers_texts`.`id` = `servers_world`.`cat_id`
AND `servers_world`.`typew` = '1'
WHERE `servers_texts`.`premod` = 1
AND `servers_texts`.`cat_id` = '10'
GROUP BY `servers_texts`.`id`
ORDER BY `vote` DESC, `servers_texts`.`id` DESC;Здесь условие по typew перенесено в ON, чтобы не терять категории, у которых нет элементов нужного типа.
3. Использование подзапроса с EXISTS
Третий вариант - проверить наличие хотя бы одной связанной записи через подзапрос, что гарантирует уникальность строк первой таблицы.
SELECT `servers_texts`.*
FROM `servers_texts`
WHERE `servers_texts`.`premod` = 1
AND `servers_texts`.`cat_id` = '10'
AND EXISTS (
SELECT 1 FROM `servers_world`
WHERE `servers_world`.`cat_id` = `servers_texts`.`id`
AND `servers_world`.`typew` = '1'
)
ORDER BY `vote` DESC, `servers_texts`.`id` DESC;Этот способ часто работает быстрее, если во второй таблице много записей.
Какой метод выбрать
- DISTINCT - когда не нужны данные из второй таблицы, только уникальные записи первой.
- GROUP BY - если нужна агрегация (сумма, количество, максимум).
- EXISTS - для проверки существования связи без дублирования, часто оптимальнее по производительности.
Выбор зависит от конкретной задачи. Для вашего случая - вывести категории, у которых есть потомки с определённым типом, - лучше всего подойдёт DISTINCT или EXISTS.