두 테이블 또는 쿼리간에 다른 행을 쉽게 표시


19

동일한 데이터를 갖거나 리턴해야하는 두 개의 다른 테이블 / 쿼리가 있다고 가정하십시오. 이것을 확인하고 싶습니다. 모든 열을 비교하여 아래 예와 같이 각 테이블에서 일치하지 않는 행을 표시하는 쉬운 방법은 무엇입니까? 테이블에 30 개의 열이 있고 그 중 많은 열이 NULL을 가정합니다.

PK가 없거나 PK 당 중복이있을 수있는 경우 PK 열만 조인하는 것만으로는 충분하지 않으며 NULL을 올바르게 처리하는 30 개의 조인 조건과 불완전한 WHERE 조건으로 전체 조인을 수행해야하는 것은 재앙입니다. 일치하는 행을 제외합니다.

일반적으로 스크러빙되지 않거나 완전히 이해되지 않은 데이터에 대해 새 쿼리를 작성하는 경우 문제가 최악이며 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');

답변:


17

FULL OUTER JOIN여기에 30 개의 조인 조건이 필요하지 않습니다 .

PK에서 전체 외부 조인을 수행하고 하나 이상의 차이점이있는 행을 보존 하고 ed 행의 양쪽 을 개별 행 으로 피벗 해제 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) 

22

EXCEPT 및 / 또는 INTERSECT를 사용하여 처리 할 수 ​​있습니다. http://msdn.microsoft.com/en-us/library/ms188055.aspx

먼저 표 2에없는 table1에있는 모든 레코드를 찾은 다음 표 1에없는 표 2에있는 모든 레코드를 찾으십시오.

SELECT * FROM table1
EXCEPT
SELECT * FROM table2

UNION

SELECT * FROM table2
EXCEPT
SELECT * FROM table1

이 작업을 수행하는 데 더 효율적인 방법은 의심 할 여지가 없지만, 머릿속에서 처음으로 "빠르고 더러운"솔루션입니다. 또한 * 와일드 카드를 사용하지 않는 것이 좋지만 여기서는 간결성을 위해 적합합니다.

또는 INTERSECT 연산자를 사용하여 모든 결과를 제외 할 수 있습니다.


6
SELECT ... EXCEPT ... SELECT서로 SELECT문 테이블 이름을 추가, 즉 SELECT "table1", T1.* FROM (SELECT ... EXCEPT ... SELECT) T1다음 UNION그 쿼리의 다른 절반.
Simon Righarts 1

7

데이터 비교와 같은 타사 도구를 사용하여 쉽게 수행하거나 클라이언트에서 수행 할 수 있습니다. 저장 프로 시저 단위 테스트와 관련하여 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());
      }
   }

4

요청한 내용을 표시하는 방법은 다음과 같습니다.

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
;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.