Как убрать дубликаты строк при 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.

    Часто задаваемые вопросы