Cách hiệu quả nhất để tạo khác


8

Tôi có một bảng trong máy chủ SQL trông như thế này:

Id    |Version  |Name    |date    |fieldA   |fieldB ..|fieldZ
1     |1        |Foo     |20120101|23       |       ..|25334123
2     |2        |Foo     |20120101|23       |NULL   ..|NULL
3     |2        |Bar     |20120303|24       |123......|NULL
4     |2        |Bee     |20120303|34       |-34......|NULL

Tôi đang làm việc trên một thủ tục được lưu trữ để tìm khác biệt, lấy dữ liệu đầu vào và số phiên bản. Dữ liệu đầu vào có các cột từ Trường tăng tênZ. Hầu hết các cột trường được dự kiến ​​là NULL, tức là, mỗi hàng thường chỉ có dữ liệu cho một vài trường đầu tiên, phần còn lại là NULL. Tên, ngày và phiên bản tạo thành một ràng buộc duy nhất trên bảng.

Tôi cần phải phân biệt dữ liệu được nhập vào bảng này cho một phiên bản nhất định. Mỗi hàng cần phải được phân biệt - một hàng được xác định theo tên, ngày và phiên bản và bất kỳ thay đổi nào trong bất kỳ giá trị nào trong các cột trường sẽ cần hiển thị trong khác.

Cập nhật: tất cả các trường không cần phải là kiểu thập phân. Một số trong số họ có thể là nvarchar. Tôi thích sự khác biệt xảy ra mà không cần chuyển đổi loại, mặc dù đầu ra diff có thể chuyển đổi mọi thứ thành nvarchar vì nó chỉ được sử dụng để hiển thị có mục đích.

Giả sử đầu vào là như sau và phiên bản được yêu cầu là 2 ,:

Name    |date    |fieldA   |fieldB|..|fieldZ
Foo     |20120101|25       |NULL  |.. |NULL
Foo     |20120102|26       |27    |.. |NULL
Bar     |20120303|24       |126   |.. |NULL
Baz     |20120101|15       |NULL  |.. |NULL

Khác biệt cần phải ở định dạng sau:

name    |date    |field    |oldValue    |newValue
Foo     |20120101|FieldA   |23          |25
Foo     |20120102|FieldA   |NULL        |26
Foo     |20120102|FieldB   |NULL        |27
Bar     |20120303|FieldB   |123         |126
Baz     |20120101|FieldA   |NULL        |15

Giải pháp của tôi cho đến nay là trước tiên tạo ra một diff, sử dụng EXCEPT và UNION. Sau đó chuyển đổi diff sang định dạng đầu ra mong muốn bằng ỨNG DỤNG THAM GIA và CROSS. Mặc dù điều này dường như đang hoạt động, tôi tự hỏi liệu có một cách sạch hơn và hiệu quả hơn để làm điều này. Số lượng các trường gần bằng 100 và mỗi vị trí trong mã có ... thực sự là một số lượng lớn các dòng. Cả bảng đầu vào và bảng hiện có dự kiến ​​sẽ khá lớn theo thời gian. Tôi chưa quen với SQL và vẫn đang cố gắng học điều chỉnh hiệu năng.

Đây là SQL cho nó:

CREATE TABLE #diff
(   [change] [nvarchar](50) NOT NULL,
    [name] [nvarchar](50) NOT NULL,
    [date] [int] NOT NULL,
    [FieldA] [decimal](38, 10) NULL,
    [FieldB] [decimal](38, 10) NULL,
    .....
    [FieldZ] [decimal](38, 10) NULL
)

--Generate the diff in a temporary table
INSERT INTO #diff
SELECT * FROM
(

(
    SELECT
        'old' as change,
        name,
        date,
        FieldA,
        FieldB,
        ...,
        FieldZ
    FROM 
        myTable mt 
    WHERE 
        version = @version
        AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput) 
    EXCEPT
    SELECT 'old' as change,* FROM @diffInput
)
UNION

(
    SELECT 'new' as change, * FROM @diffInput
    EXCEPT
    SELECT
        'new' as change,
        name,
        date,
        FieldA, 
        FieldB,
        ...,
        FieldZ
    FROM 
        myTable mt 
    WHERE 
        version = @version 
        AND mt.name + '_' + CAST(mt.date AS VARCHAR) IN (SELECT name + '_' + CAST(date AS VARCHAR) FROM @diffInput) 
) 
) AS myDiff

SELECT 
d3.name, d3.date, CrossApplied.field, CrossApplied.oldValue, CrossApplied.newValue
FROM
(
    SELECT 
        d2.name, d2.date, 
        d1.FieldA AS oldFieldA, d2.FieldA AS newFieldA, 
        d1.FieldB AS oldFieldB, d2.FieldB AS newFieldB,
        ...
        d1.FieldZ AS oldFieldZ, d2.FieldZ AS newFieldZ,
    FROM #diff AS d1
    RIGHT OUTER JOIN #diff AS d2
    ON 
        d1.name = d2.name
        AND d1.date = d2.date
        AND d1.change = 'old'
    WHERE d2.change = 'new'
) AS d3
CROSS APPLY (VALUES ('FieldA', oldFieldA, newFieldA), 
                ('FieldB', oldFieldB, newFieldB),
                ...
                ('FieldZ', oldFieldZ, newFieldZ))
                CrossApplied (field, oldValue, newValue)
WHERE 
    crossApplied.oldValue != crossApplied.newValue 
    OR (crossApplied.oldValue IS NULL AND crossApplied.newValue IS NOT NULL) 
    OR (crossApplied.oldValue IS NOT NULL AND crossApplied.newValue IS NULL)  

Cảm ơn bạn!

Câu trả lời:


5

Đây là một cách tiếp cận khác:

SELECT
  di.name,
  di.date,
  x.field,
  x.oldValue,
  x.newValue
FROM
  @diffInput AS di
  LEFT JOIN dbo.myTable AS mt ON
    mt.version = @version
    AND mt.name = di.name
    AND mt.date = di.date
  CROSS APPLY
  (
    SELECT
      'fieldA',
      mt.fieldA,
      di.fieldA
    WHERE
      NOT EXISTS (SELECT mt.fieldA INTERSECT SELECT di.fieldA)

    UNION ALL

    SELECT
      'fieldB',
      mt.fieldB,
      di.fieldB
    WHERE
      NOT EXISTS (SELECT mt.fieldB INTERSECT SELECT di.fieldB)

    UNION ALL

    SELECT
      'fieldC',
      mt.fieldC,
      di.fieldC
    WHERE
      NOT EXISTS (SELECT mt.fieldC INTERSECT SELECT di.fieldC)

    UNION ALL

    ...
  ) AS x (field, oldValue, newValue)
;

Đây là cách nó hoạt động:

  1. Hai bảng được nối bằng cách sử dụng một phép nối ngoài, @diffInputnằm ở phía bên ngoài để khớp với phép nối bên phải của bạn.

  2. Kết quả của phép nối không có điều kiện bằng cách sử dụng CROSS ỨNG DỤNG, trong đó "có điều kiện" có nghĩa là mỗi cặp cột được kiểm tra riêng lẻ và chỉ được trả về nếu các cột khác nhau.

  3. Mẫu của từng điều kiện kiểm tra

    NOT EXISTS (SELECT oldValue INTERSECT SELECT newValue)

    tương đương với

    oldValue != newValue
    OR (oldValue IS NULL AND newValue IS NOT NULL)
    OR (oldValue IS NOT NULL AND newValue IS NULL)

    chỉ súc tích hơn. Bạn có thể đọc thêm về việc sử dụng INTERSECT này một cách chi tiết trong bài viết của Paul White Kế hoạch truy vấn không có giấy tờ: So sánh bình đẳng .

Trên một lưu ý khác, vì bạn đang nói,

Cả bảng đầu vào và bảng hiện có dự kiến ​​sẽ khá lớn theo thời gian

bạn có thể muốn xem xét thay thế biến bảng bạn đang sử dụng cho bảng đầu vào bằng một bảng tạm thời. Có một câu trả lời rất toàn diện của Martin Smith tìm hiểu sự khác biệt giữa hai điều này:

Tóm lại, một số thuộc tính nhất định của các biến bảng, ví dụ như không có thống kê cột, có thể làm cho chúng ít thân thiện với trình tối ưu hóa truy vấn cho kịch bản của bạn hơn các bảng tạm thời.


Nếu kiểu dữ liệu không giống nhau đối với các trường AZ, thì 2 trường trong các câu lệnh được chọn cần phải được chuyển đổi thành varchar hoặc câu lệnh union sẽ không hoạt động.
Andre

5

Chỉnh sửa liên quan đến các lĩnh vực có các loại khác nhau, không chỉ decimal.

Bạn có thể thử sử dụng sql_variantloại. Tôi không bao giờ sử dụng nó cá nhân, nhưng nó có thể là một giải pháp tốt cho trường hợp của bạn. Để thử nó chỉ cần thay thế tất cả [decimal](38, 10)bằng sql_varianttrong tập lệnh SQL. Bản thân truy vấn vẫn giữ nguyên chính xác, không cần chuyển đổi rõ ràng để thực hiện so sánh. Kết quả cuối cùng sẽ có một cột với các giá trị của các loại khác nhau trong đó. Rất có thể, cuối cùng bạn sẽ phải biết bằng cách nào đó loại nào trong trường nào sẽ xử lý kết quả trong ứng dụng của bạn, nhưng bản thân truy vấn sẽ hoạt động tốt mà không cần chuyển đổi.


Nhân tiện, đó là một ý tưởng tồi để lưu trữ ngày như int.

Thay vì sử dụng EXCEPTUNIONđể tính khác biệt, tôi sẽ sử dụng FULL JOIN. Đối với cá nhân tôi, thật khó để theo logic EXCEPTUNIONcách tiếp cận.

Tôi sẽ bắt đầu với việc không xoay vòng dữ liệu, thay vì thực hiện lần cuối (sử dụng CROSS APPLY(VALUES)như bạn làm). Bạn có thể thoát khỏi việc không xoay vòng đầu vào, nếu bạn thực hiện trước, về phía người gọi.

Bạn sẽ phải liệt kê tất cả 100 cột chỉ trong CROSS APPLY(VALUES).

Truy vấn cuối cùng khá đơn giản, vì vậy bảng tạm thời không thực sự cần thiết. Tôi nghĩ nó dễ viết và bảo trì hơn phiên bản của bạn. Đây là SQL Fiddle .

Thiết lập dữ liệu mẫu

DECLARE @TMain TABLE (
    [ID] [int] NOT NULL,
    [Version] [int] NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
    [dt] [date] NOT NULL,
    [FieldA] [decimal](38, 10) NULL,
    [FieldB] [decimal](38, 10) NULL,
    [FieldZ] [decimal](38, 10) NULL
);

INSERT INTO @TMain ([ID],[Version],[Name],[dt],[FieldA],[FieldB],[FieldZ]) VALUES
(1,1,'Foo','20120101',23,23  ,25334123),
(2,2,'Foo','20120101',23,NULL,NULL),
(3,2,'Bar','20120303',24,123 ,NULL),
(4,2,'Bee','20120303',34,-34 ,NULL);

DECLARE @TInput TABLE (
    [Name] [nvarchar](50) NOT NULL,
    [dt] [date] NOT NULL,
    [FieldA] [decimal](38, 10) NULL,
    [FieldB] [decimal](38, 10) NULL,
    [FieldZ] [decimal](38, 10) NULL
);

INSERT INTO @TInput ([Name],[dt],[FieldA],[FieldB],[FieldZ]) VALUES
('Foo','20120101',25,NULL,NULL),
('Foo','20120102',26,27  ,NULL),
('Bar','20120303',24,126 ,NULL),
('Baz','20120101',15,NULL,NULL);

DECLARE @VarVersion int = 2;

Truy vấn chính

CTE_Mainlà dữ liệu gốc không được xoay vòng được lọc đến nhất định Version. CTE_Inputlà bảng đầu vào, có thể được cung cấp ở định dạng này. Sử dụng truy vấn chính FULL JOIN, mà thêm vào các hàng kết quả với Bee. Tôi nghĩ rằng chúng nên được trả lại, nhưng nếu bạn không muốn nhìn thấy chúng, bạn có thể lọc chúng ra bằng cách thêm AND CTE_Input.FieldValue IS NOT NULLhoặc có thể sử dụng LEFT JOINthay vì FULL JOIN, tôi đã không xem xét chi tiết ở đó, vì tôi nghĩ chúng nên được trả lại.

WITH
CTE_Main
AS
(
    SELECT
        Main.ID
        ,Main.Version
        ,Main.Name
        ,Main.dt
        ,FieldName
        ,FieldValue
    FROM
        @TMain AS Main
        CROSS APPLY
        (
            VALUES
                ('FieldA', Main.FieldA),
                ('FieldB', Main.FieldB),
                ('FieldZ', Main.FieldZ)
        ) AS CA(FieldName, FieldValue)
    WHERE
        Main.Version = @VarVersion
)
,CTE_Input
AS
(
    SELECT
        Input.Name
        ,Input.dt
        ,FieldName
        ,FieldValue
    FROM
        @TInput AS Input
        CROSS APPLY
        (
            VALUES
                ('FieldA', Input.FieldA),
                ('FieldB', Input.FieldB),
                ('FieldZ', Input.FieldZ)
        ) AS CA(FieldName, FieldValue)
)

SELECT
    ISNULL(CTE_Main.Name, CTE_Input.Name) AS FullName
    ,ISNULL(CTE_Main.dt, CTE_Input.dt) AS FullDate
    ,ISNULL(CTE_Main.FieldName, CTE_Input.FieldName) AS FullFieldName
    ,CTE_Main.FieldValue AS OldValue
    ,CTE_Input.FieldValue AS NewValue
FROM
    CTE_Main
    FULL JOIN CTE_Input ON 
        CTE_Input.Name = CTE_Main.Name
        AND CTE_Input.dt = CTE_Main.dt
        AND CTE_Input.FieldName = CTE_Main.FieldName
WHERE
    (CTE_Main.FieldValue <> CTE_Input.FieldValue)
    OR (CTE_Main.FieldValue IS NULL AND CTE_Input.FieldValue IS NOT NULL)
    OR (CTE_Main.FieldValue IS NOT NULL AND CTE_Input.FieldValue IS NULL)
--ORDER BY FullName, FullDate, FullFieldName;

Kết quả

FullName    FullDate    FullFieldName   OldValue        NewValue
Foo         2012-01-01  FieldA          23.0000000000   25.0000000000
Foo         2012-01-02  FieldA          NULL            26.0000000000
Foo         2012-01-02  FieldB          NULL            27.0000000000
Bar         2012-03-03  FieldB          123.0000000000  126.0000000000
Baz         2012-01-01  FieldA          NULL            15.0000000000
Bee         2012-03-03  FieldB          -34.0000000000  NULL
Bee         2012-03-03  FieldA          34.0000000000   NULL
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.