Следующее решение работает:
Еще одна демонстрация со строкой, которая имеет дублирование в a и b
CREATE TEMPORARY TABLE ab_duplicates (
a INTEGER
) AS
SELECT a, count(*) as cnt
FROM tablename
group by a, b
Having cnt > 1;
ALTER TABLE ab_duplicates ADD INDEX (a);
-- Select duplicates for a, but not for a and b
SELECT id, name, a, b
FROM (SELECT x.*, t.id, t.name, t.a, t.b,
@rn := IF(t.a = @a, @rn + 1, 1) rn,
@a := t.a,
ab.a as ab_exists
FROM (select @a := null, @rn := 0) x,
tablename t
LEFT JOIN ab_duplicates ab on ab.a = t.a
ORDER BY a
) a_duplicates
where rn = 2 and ab_exists is null
UNION
-- union duplicates for b, including duplicates for a and b
SELECT id, name, a, b
FROM (SELECT x.*, t.id, t.name, t.a, t.b,
@rn := IF(t.b = @b, @rn + 1, 1) rn,
@b := t.b
FROM (select @b := null, @rn := 0) x,
tablename t
ORDER BY b
) b_and_ab_duplicates
where rn = 2;
Предыдущие решения, которые работали только в некоторых крайних случаях
Использование group by и count ():
Первый поиск идентификаторов с дубликатами для a:
SELECT min(id) id, count(*) cnt from tablename t group by a having cnt > 1
-- this will work better if you have an index starting with a
То же самое с b:
SELECT min(id) id, count(*) cnt from tablename t group by b having cnt > 1
-- this will work better if you have an index starting with b
Первый решение:
Union дает идентификаторы, в которых есть дубликаты для a или b, требуется 2 индекса)
SELECT min(id) id, count(*) cnt from tablename t group by a having cnt > 1
UNION
SELECT min(id) id, count(*) cnt from tablename t group by b having cnt > 1
Используйте идентификаторы для фильтрации таблицы, если вам нужно больше данных из таблицы:
SELECT tablename.*
FROM (
SELECT min(id) id, count(*) cnt from tablename t group by a having cnt > 1
UNION
SELECT min(id) id, count(*) cnt from tablename t group by b having cnt > 1
) as ids
JOIN tablename on tablename.id = ids.id
Теперь это может не использовать индекс, но вы можете использовать временную таблицу, чтобы иметь ее:
Первое решение с использованием временной таблицы (может быть быстрее):
-- using a temporary table to set an index
CREATE TEMPORARY TABLE ids (
-- adds an index on id, for the JOIN in the result query
`id` INTEGER PRIMARY KEY
) as
SELECT id
FROM (
-- duplicates on a, requires an index (a) on tablename
SELECT min(id) id, count(*) cnt from tablename t group by a having cnt > 1
-- removes duplicates between both part of the UNION : this might be slow
-- if there cannot be duplicates on a and b at the same time, consider using UNION ALL
UNION
-- duplicates on b, requires an index (b) on tablename
SELECT min(id) id, count(*) cnt from tablename t group by b having cnt > 1
) tempids;
SELECT tablename.*
FROM ids -- using the temporary table, MUST be in the same database connection, will filter duplicates
JOIN tablename on tablename.id = ids.id;
Я не знаю, лучше ли устанавливать индекс для временной таблицы, чем устанавливать индекс после заполнения данных:
-- you might want to postpone the index after the ids are set
-- using a temporary table to set an index
CREATE TEMPORARY TABLE ids2 (
`id` INTEGER
) as
SELECT id
FROM (
-- duplicates on a, requires an index (a) on tablename
SELECT min(id) id, count(*) cnt from tablename t group by a having cnt > 1
-- removes duplicates between both part of the UNION : this might be slow
-- if there cannot be duplicates on a and b at the same time, consider using UNION ALL
UNION
-- duplicates on b, requires an index (b) on tablename
SELECT min(id) id, count(*) cnt from tablename t group by b having cnt > 1
) tempids;
ALTER TABLE ids2 ADD INDEX (id);
SELECT tablename.*
FROM ids2 -- using the temporary table, MUST be in the same database connection, will filter duplicates
JOIN tablename on tablename.id = ids2.id;
С mariadb 10.2 или mysql 8 вы можете использовать окно функция (я думаю).
Другое решение: использование vars:
SELECT id, name, a, b, rn
FROM (SELECT *,
@rn := IF(a = @a, @rn + 1, 1) rn,
@a := a
FROM (select @a := null, @rn := 0) x,
tablename
ORDER BY a
) a_duplicates
where rn = 2
UNION
SELECT id, name, a, b, rn
FROM (SELECT *,
@rn := IF(b = @b, @rn + 1, 1) rn,
@b := b
FROM (select @b := null, @rn := 0) x,
tablename
ORDER BY b
) b_duplicates
where rn = 2
Демо: с некоторыми дополнительными шагами для понимания
Редактировать: это работает, только если вы не есть строки, где а и б дубликаты. Который имеет место в примере.