Как найти строки, которые дублируются ключом, но не дублируются во всех столбцах?
Я работаю с таблицей, которая является выпиской из набора других таблиц. Все строки таблицы извлечения должны быть уникальными в соответствии с ключами D1, D2 и D3. Они не. Похоже, что более ранний разработчик попытался решить эту проблему, используя SELECT DISTINCT
для всех столбцов, запрашиваемых из этой таблицы. Это будет работать, но только если каждая строка, которая является дубликатом (D1, D2, D3), также является дубликатом по не-ключевым столбцам (игнорируя столбец IDENTITY, который был добавлен в таблицу извлечения).
Другими словами, следующие строки:
D1 D2 D3 C4 C5 C6
=== === === === === ===
A B C X1 X2 X3
A B C X1 X2 X3
затем
SELECT DISTINCT D1, D2, D3, C4, C5, C6
FROM BAD_TABLE
будет работать, поскольку нет никакой разницы между строками, которые дублируются (D1, D2, D3). Но если таблица содержала
D1 D2 D3 C4 C5 C6
=== === === === === ===
A B C X1 X2 X3
A B C X1 X2 X4
тогда SELECT DISTINCT вернет две строки для ключа (A, B, C). Кроме того, нам нужно будет решить, какой из X3 или X4 является "правильным" значением.
Я знаю, как найти дубликаты (D1, D2, D3). Я даже знаю, как найти дубликаты во всех столбцах (кроме столбца IDENTITY):
;
WITH DUPLICATES(D1,D2,D3) AS
(
SELECT D1, D2, D3
FROM SOURCE
GROUP BY D1, D2, D3
HAVING COUNT(*)>1
)
SELECT S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
FROM SOURCE S
INNER JOIN DUPLICATES D
ON S.D1 = D.D1 AND S.D2 = D.D2 AND S.D3 = D.D3
ORDER BY S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
Вопрос в том, как найти подмножество вышеупомянутого набора результатов, которые дублируются на (D1, D2, D3), но не дублирует (D1, D2, D3, C4, C5, С6)?
Ответы
Ответ 1
Вы можете сделать это, присоединив таблицу к себе, заявив, что D все равны, и хотя бы один из C не равен.
CREATE TABLE #Source (
D1 VARCHAR(2),
D2 VARCHAR(2),
D3 VARCHAR(2),
C4 VARCHAR(2),
C5 VARCHAR(2),
C6 VARCHAR(2) );
INSERT INTO #Source VALUES ('A', 'B', 'C', 'X1', 'X2', 'X3');
INSERT INTO #Source VALUES ('A', 'B', 'C', 'X1', 'X2', 'X4');
INSERT INTO #Source VALUES ('A', 'B', 'D', 'X1', 'X2', 'X3');
INSERT INTO #Source VALUES ('A', 'B', 'D', 'X1', 'X2', 'X3');
SELECT S1.D1, S1.D2, S1.D3, S1.C4 C4_1, S2.C4 C4_2, S1.C5 C5_1, S2.C5 C5_2, S1.C6 C6_1, S2.C6 C6_2
FROM
#Source S1
INNER JOIN
#Source S2
ON
( S1.D1 = S2.D1
AND S1.D2 = S2.D2
AND S1.D3 = S2.D3
AND ( S1.C4 <> S2.C4
OR S1.C5 <> S2.C5
OR S1.C6 <> S2.C6
)
);
DROP TABLE #Source;
Дает следующие результаты:
D1 D2 D3 C4_1 C4_2 C5_1 C5_2 C6_1 C6_2
---- ---- ---- ---- ---- ---- ---- ---- ----
A B C X1 X1 X2 X2 X4 X3
A B C X1 X1 X2 X2 X3 X4
Также обратите внимание, что это совместимо с MS SQL 2000, как вам было указано ниже, в Как преобразовать SQL-запрос с использованием общих выражений таблицы в One Without (для SQL Server 2000).
Ответ 2
У меня еще не было возможности попробовать Конрада, но придумал один из моих. Это скорее момент "духа".
Итак, если вы хотите найти все строки в наборе A, кроме тех, которые находятся в наборе B, вы используете оператор EXCEPT:
;
WITH KEYDUPLICATES(D1,D2,D3) AS
(
SELECT D1, D2, D3
FROM SOURCE
GROUP BY D1, D2, D3
HAVING COUNT(*)>1
),
KEYDUPLICATEROWS AS
(
SELECT S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
FROM SOURCE S
INNER JOIN KEYDUPLICATES D
ON S.D1 = D.D1 AND S.D2 = D.D2 AND S.D3 = D.D3
),
FULLDUPLICATES AS
(
SELECT S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
FROM SOURCE S
GROUP BY S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
HAVING COUNT(*)>1
)
SELECT KR.D1, KR.D2, KR.D3, KR.C4, KR.C5, KR.C6
FROM KEYDUPLICATEROWS AS KR
EXCEPT
SELECT FD.D1, FD.D2, FD.D3, FD.C4, FD.C5, FD.C6
FROM FULLDUPLICATES AS FD
ORDER BY D1, D2, D3, C4, C5, C6
Кажется, это показывает мне 1500 строк, которые дублируются (D1, D2, D3), но которые являются только дубликатами в подмножестве (D1, D2, D3, C4, C5, C6). На самом деле, похоже, что они дубликаты (D1, D2, D3, C4, C5).
Как подтвердить, что будет предметом другого вопроса.
Ответ 3
По какой-либо причине вы не просто создаете другое выражение таблицы, чтобы охватить больше полей и присоединиться к этому?
WITH DUPLICATEKEY(D1,D2,D3) AS
(
SELECT D1, D2, D3
FROM SOURCE
GROUP BY D1, D2, D3
HAVING COUNT(*)>1
)
WITH NODUPES(D1,D2,D3,C4,C5,C6) AS
(
SELECT
S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
FROM SOURCE S
GROUP BY
S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
HAVING COUNT(*)=1
)
SELECT S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
FROM SOURCE S
INNER JOIN DUPLICATEKEY D
ON S.D1 = D.D1 AND S.D2 = D.D2 AND S.D3 = D.D3
INNER JOIN NODUPES D2
ON S.D1 = D2.D1 AND S.D2 = D2.D2 AND S.D3 = D2.D3
ORDER BY S.D1, S.D2, S.D3, S.C4, S.C5, S.C6
Ответ 4
У этого были бы ограничения производительности, но гораздо легче понять:
SELECT D1, D2, D3
FROM TEST_KEY TK
WHERE (D1, D2, D3) IN
(SELECT D1, D2, D3 FROM TEST_KEY TK2
GROUP BY D1, D2, D3
HAVING COUNT(*) > 1)
AND (D1, D2, D3) IN
(SELECT D1, D2, D3 FROM TEST_KEY TK2
GROUP BY D1, D2, D3, C4, C5, C6
HAVING COUNT(*) < 2)
Невозможно протестировать SQL-сервер, надеюсь, что синтаксис хорош.
Опять же, не уверен, что у вас есть аналитические функции в SQL-Server, но это работает в Oracle и может быть быстрее:
WITH BAD_DUP AS (
SELECT TK.*,
COUNT(1) OVER (PARTITION BY D1, D2, D3, C4, C5, C6 ORDER BY D1) FULL_DUP,
COUNT(1) OVER (PARTITION BY D1, D2, D3 ORDER BY D1) KEY_DUP
FROM TEST_KEY TK)
SELECT * FROM BAD_DUP
WHERE FULL_DUP < KEY_DUP
Хотелось бы получить его до одного запроса....
Ответ 5
Я знаю, что это старый вопрос, но я видел активность по этому вопросу, и техника, которую я всегда использую для них, здесь не представлена в качестве ответа, и это действительно очень просто, поэтому я решил представить ее.
SELECT D1, D2, D3, MIN(C4), MAX(C4), MIN(C5), MAX(C5), MIN(C6), MAX(C6)
FROM BAD_TABLE
GROUP BY D1, D2, D3
HAVING MIN(C4) <> MAX(C4)
OR MIN(C5) <> MAX(C5)
OR MIN(C6) <> MAX(C6)
Это покажет все ключи дубликатов на клавише, но с разницей в не-ключах, причем диапазон различий дублируется.
Чтобы увидеть все строки внутри этого, вам нужно будет присоединиться к BAD_TABLE в качестве примера в исходном вопросе.