แสดงแถวที่แตกต่างระหว่างสองตารางหรือคิวรีได้อย่างง่ายดาย


19

ลองนึกภาพคุณมีสองตาราง / แบบสอบถามที่แตกต่างกันที่ควรจะมี / ส่งคืนข้อมูลที่เหมือนกัน คุณต้องการตรวจสอบสิ่งนี้ วิธีง่ายๆในการแสดงแถวที่ไม่ตรงกันจากแต่ละตารางเหมือนกับตัวอย่างด้านล่างคือการเปรียบเทียบทุกคอลัมน์ สมมติว่ามี 30 คอลัมน์ในตารางซึ่งหลายแห่งเป็น NULLable

เมื่อไม่มี PK หรืออาจมีการทำซ้ำต่อ PK การเข้าร่วมในคอลัมน์ PK เพียงอย่างเดียวไม่เพียงพอและจะเป็นความหายนะที่จะต้องทำการเข้าร่วมแบบเต็มโดยมีเงื่อนไขการเข้าร่วม 30 รายการที่จัดการ NULL ได้อย่างเหมาะสมรวมถึงเงื่อนไขที่น่ารังเกียจ เพื่อยกเว้นแถวที่ตรงกัน

โดยปกติแล้วคือเมื่อฉันเขียนแบบสอบถามใหม่กับข้อมูลที่ไม่มีการ unscrbed หรือไม่เข้าใจว่าปัญหานั้นเลวร้ายที่สุดและความน่าจะเป็นของ 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

คุณไม่ต้องการเงื่อนไขการเข้าร่วม 30 รายการสำหรับFULL OUTER JOINที่นี่

คุณสามารถเข้าร่วมด้านนอกแบบเต็มบน PK รักษาแถวที่มีความแตกต่างอย่างน้อยหนึ่งด้วย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) 

22

สิ่งนี้สามารถจัดการได้โดยใช้ข้อยกเว้นและ / หรือ INTERSECT http://msdn.microsoft.com/en-us/library/ms188055.aspx

ก่อนอื่นค้นหาระเบียนทั้งหมดที่อยู่ใน table1 ที่ไม่ได้อยู่ในตารางที่ 2 จากนั้นค้นหาระเบียนทั้งหมดที่อยู่ในตารางที่ 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

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.