2つのテーブルまたはクエリ間で異なる行を簡単に表示する
-
16-10-2019 - |
質問
同一のデータを持っている/返すことになっている2つの異なるテーブル/クエリがあると想像してください。あなたはこれを確認したいです。すべての列を比較して、以下の例のように、各テーブルから比類のない行を表示する簡単な方法は何ですか?テーブルには30の列があると仮定し、その多くは気付かれません。
PKがない場合、またはPKごとに重複する可能性がある場合、PK列のみに結合するだけでは十分ではなく、ヌルを適切に処理する30の結合条件で完全な結合を行う必要があることに加えて、条件がある場合の厄介な場合は災害になります。一致した行を除外します。
通常、問題が最悪であり、PKが論理的に利用可能である可能性が非常に低いのは、私が未使用または不明確なデータに対して新しいクエリを書いているときです。私は問題を解決してから結果を比較するために2つの異なる方法を調理し、その結果を比較します。これは、私が知らなかったデータの特別なケースを強調する違いです。
結果は次のようになる必要があります:
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が同じである必要がある1つの行が異なり、それぞれが他の行にない1つの行があることが簡単に確認できます。
上記の例では、最初の行を2回見ることは望ましくありません。
サンプルテーブルとデータをセットアップする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');
解決
aの30の結合条件は必要ありません FULL OUTER JOIN
ここ。
PKの完全な外側結合だけで、少なくとも1つの違いがある行を保持できます WHERE EXISTS (SELECT A.* EXCEPT SELECT B.*)
そして使用します CROSS APPLY (SELECT A.* UNION ALL SELECT B.*)
の両側を解放する JOIN
ED行は個々の行に入ります。
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
最初に、表2にないTable1にあるすべてのレコードを見つけてから、表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
;