Легко показывать ряды, которые различаются между двумя таблицами или запросами
-
16-10-2019 - |
Вопрос
Представьте, что у вас есть две разные таблицы/запросы, которые должны иметь/возвращать идентичные данные. Вы хотите проверить это. Какой простой способ показать какие -либо непревзойденные ряды из каждой таблицы, как приведенный ниже пример, сравнивая каждый столбец? Предположим, что в таблицах существует 30 столбцов, многие из которых являются нулевыми.
Когда нет PK или может быть дубликаты на PK, соединение только на столбцах PK недостаточно, и было бы катастрофой, чтобы сделать полное соединение с 30 условиями соединения, которые должным образом обрабатывают нули, плюс неприятные, где условие Чтобы исключить соответствующие ряды.
Обычно это когда я пишу новый запрос против непредубеленных или недавно понятых данных, проблема является худшей, и вероятность того, что PK будет логически доступен, чрезвычайно низкая. Я готовлю два разных способа решения проблемы, а затем сравниваю их результаты, различия подчеркивают особые случаи в данных, о которых я не знал.
Результат должен выглядеть так:
Which Col1 Col2 Col3 ... Col30
------ ------ ------ ------ ------
TableA Cat 27 86 -- mismatch
TableB Cat 27 105 -- mismatch
TableB Cat 27 87 -- mismatch 2
TableA Cat 128 92 -- no corresponding row
TableB Lizard 83 NULL -- no corresponding row
Если [Col1, Col2]
Оказалось, что это композитный ключ, и мы заказываем им в нашем конечном результате, тогда мы можем легко увидеть, что у A и B есть один ряд другой, который должен быть одинаковой, и у каждого есть одна строка, которая не находится в другой.
В приведенном выше примере, увидеть первую строку дважды, нежелательно.
Вот DDL и DML, чтобы настроить образцы таблиц и данных:
CREATE TABLE dbo.TableA (
Col1 varchar(10),
Col2 int,
Col3 int,
Col4 varchar(10),
Col5 varchar(10),
Col6 varchar(10),
Col7 varchar(10),
Col8 varchar(10),
Col9 varchar(10),
Col10 varchar(10),
Col11 varchar(10),
Col12 varchar(10),
Col13 varchar(10),
Col14 varchar(10),
Col15 varchar(10),
Col16 varchar(10),
Col17 varchar(10),
Col18 varchar(10),
Col19 varchar(10),
Col20 varchar(10),
Col21 varchar(10),
Col22 varchar(10),
Col23 varchar(10),
Col24 varchar(10),
Col25 varchar(10),
Col26 varchar(10),
Col27 varchar(10),
Col28 varchar(10),
Col29 varchar(10),
Col30 varchar(10)
);
CREATE TABLE dbo.TableB (
Col1 varchar(10),
Col2 int,
Col3 int,
Col4 varchar(10),
Col5 varchar(10),
Col6 varchar(10),
Col7 varchar(10),
Col8 varchar(10),
Col9 varchar(10),
Col10 varchar(10),
Col11 varchar(10),
Col12 varchar(10),
Col13 varchar(10),
Col14 varchar(10),
Col15 varchar(10),
Col16 varchar(10),
Col17 varchar(10),
Col18 varchar(10),
Col19 varchar(10),
Col20 varchar(10),
Col21 varchar(10),
Col22 varchar(10),
Col23 varchar(10),
Col24 varchar(10),
Col25 varchar(10),
Col26 varchar(10),
Col27 varchar(10),
Col28 varchar(10),
Col29 varchar(10),
Col30 varchar(10)
);
INSERT dbo.TableA (Col1, Col2, Col3, Col4, Col5, Col6, Col7, Col8, Col9, Col10, Col11, Col12, Col13, Col14, Col15, Col16, Col17, Col18, Col19, Col20, Col21, Col22, Col23, Col24, Col25, Col26, Col27, Col28, Col29, Col30)
VALUES
('Cat', 27, 86, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Cat', 128, 92, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Porcupine', NULL, 42, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Tapir', NULL, NULL, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0')
;
INSERT dbo.TableB (Col1, Col2, Col3, Col4, Col5, Col6, Col7, Col8, Col9, Col10, Col11, Col12, Col13, Col14, Col15, Col16, Col17, Col18, Col19, Col20, Col21, Col22, Col23, Col24, Col25, Col26, Col27, Col28, Col29, Col30)
VALUES
('Cat', 27, 105, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Cat', 27, 87, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Lizard', 83, NULL, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Porcupine', NULL, 42, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0'),
('Tapir', NULL, NULL, 'a', 'b', 'c', 'd', 'e', 'f', 'g',' h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0');
Решение
Вам не нужно 30 условий соединения для FULL OUTER JOIN
здесь.
Вы можете просто полное внешнее соединение на PK, сохранить строки, по крайней мере, с одним отличием с WHERE EXISTS (SELECT A.* EXCEPT SELECT B.*)
и использовать CROSS APPLY (SELECT A.* UNION ALL SELECT B.*)
раскрыть обе стороны JOIN
Эд рядов в отдельные ряды.
WITH TableA(Col1, Col2, Col3)
AS (SELECT 'Dog',1,1 UNION ALL
SELECT 'Cat',27,86 UNION ALL
SELECT 'Cat',128,92),
TableB(Col1, Col2, Col3)
AS (SELECT 'Dog',1,1 UNION ALL
SELECT 'Cat',27,105 UNION ALL
SELECT 'Lizard',83,NULL)
SELECT CA.*
FROM TableA A
FULL OUTER JOIN TableB B
ON A.Col1 = B.Col1
AND A.Col2 = B.Col2
/*Unpivot the joined rows*/
CROSS APPLY (SELECT 'TableA' AS what, A.* UNION ALL
SELECT 'TableB' AS what, B.*) AS CA
/*Exclude identical rows*/
WHERE EXISTS (SELECT A.*
EXCEPT
SELECT B.*)
/*Discard NULL extended row*/
AND CA.Col1 IS NOT NULL
ORDER BY CA.Col1, CA.Col2
Дает
what Col1 Col2 Col3
------ ------ ----------- -----------
TableA Cat 27 86
TableB Cat 27 105
TableA Cat 128 92
TableB Lizard 83 NULL
Или версия, посвященная перемещенным целевым точкам.
SELECT DISTINCT CA.*
FROM TableA A
FULL OUTER JOIN TableB B
ON EXISTS (SELECT A.* INTERSECT SELECT B.*)
CROSS APPLY (SELECT 'TableA' AS what, A.* UNION ALL
SELECT 'TableB' AS what, B.*) AS CA
WHERE NOT EXISTS (SELECT A.* INTERSECT SELECT B.*)
AND CA.Col1 IS NOT NULL
ORDER BY CA.Col1, CA.Col2
Для таблиц со многими столбцами все еще может быть трудно определить конкретные столбцы, которые различаются. Для этого вы можете использовать ниже.
(Хотя просто на относительно небольших таблицах, как иначе этот метод, вероятно, не будет иметь достаточной производительности)
SELECT t1.primary_key,
y1.c,
y1.v,
y2.v
FROM t1
JOIN t2
ON t1.primary_key = t2.primary_key
CROSS APPLY (SELECT t1.*
FOR xml path('row'), elements xsinil, type) x1(x)
CROSS APPLY (SELECT t2.*
FOR xml path('row'), elements xsinil, type) x2(x)
CROSS APPLY (SELECT n.n.value('local-name(.)', 'sysname'),
n.n.value('.', 'nvarchar(max)')
FROM x1.x.nodes('row/*') AS n(n)) y1(c, v)
CROSS APPLY (SELECT n.n.value('local-name(.)', 'sysname'),
n.n.value('.', 'nvarchar(max)')
FROM x2.x.nodes('row/*') AS n(n)) y2(c, v)
WHERE y1.c = y2.c
AND EXISTS(SELECT y1.v
EXCEPT
SELECT y2.v)
Другие советы
Это можно обработать с помощью, кроме/или пересечения.http://msdn.microsoft.com/en-us/library/ms188055.aspx
Сначала найдите все записи, которые находятся в таблице 1, которых нет в таблице 2, а затем найдите все записи, которые находятся в таблице 2, которые не находятся в таблице первой.
SELECT * FROM table1
EXCEPT
SELECT * FROM table2
UNION
SELECT * FROM table2
EXCEPT
SELECT * FROM table1
Несомненно, есть более эффективный способ сделать это, но это первое «быстрое и грязное» решение с макушки. Кроме того, я не рекомендую использовать подстановочный знак, но он подходит здесь для краткости.
С другой стороны, вы можете использовать оператор пересечения и исключить все результаты из него.
Это легко сделать с помощью стороннего инструмента, такого как Data Compare, или просто сделать это на клиенте. В контексте хранимых процедур модульного тестирования мы только что написали код C#.
Вот код C#, который мы используем, цитируется из старой статьи:Закрыть эти лазейки - тестирование хранимых процедур
internal static class DataSetComparer
{
internal static bool Compare(DataSet one, DataSet two)
{
if(one.Tables.Count != two.Tables.Count)
return false;
for(int i = 0; i < one.Tables.Count; i++)
if(!CompareTables(one.Tables[i], two.Tables[i]))
return false;
return true;
}
private static bool CompareTables(DataTable one, DataTable two)
{
if(one.Rows.Count != two.Rows.Count)
return false;
for(int i = 0; i < one.Rows.Count; i++)
if(!CompareRows(one.Rows[i], two.Rows[i]))
return false;
return true;
}
private static bool CompareRows(DataRow one, DataRow two)
{
if(one.ItemArray.Length != two.ItemArray.Length)
return false;
for(int i = 0; i < one.ItemArray.Length; i++)
if(!CompareItems(one.ItemArray[i], two.ItemArray[i]))
return false;
return true;
}
private static bool CompareItems(object value1, object value2)
{
if(value1.GetType() != value2.GetType())
return false;
if(value1 is DBNull)
return true;
if(value1 is DateTime)
return ((DateTime) value1).CompareTo((DateTime) value2)
== 0;
if(value1 is byte[])
{
if(((byte[]) value1).Length != ((byte[]) value2).Length)
return false;
for(int i = 0; i < ((byte[]) value1).Length; i++)
if(((byte[]) value1)[i] != ((byte[]) value2)[i])
return false;
return true;
}
return value1.ToString().Equals(value2.ToString());
}
}
Вот способ показать, что спросили:
SELECT
Which = 'TableA',
*
FROM (
SELECT * FROM dbo.TableA
EXCEPT
SELECT * FROM dbo.TableB
) X
UNION ALL
SELECT
'TableB',
*
FROM (
SELECT * FROM dbo.TableB
EXCEPT
SELECT * FROM dbo.TableA
) X
ORDER BY
Col1, Col2, Col3, Col4, Col5, Col6, Col7, Col8, Col9, Col10, Col11, Col12, Col13, Col14, Col15, Col16, Col17, Col18, Col19, Col20, Col21, Col22, Col23, Col24, Col25, Col26, Col27, Col28, Col29, Col30
;