Làm cách nào để tránh sử dụng truy vấn Hợp nhất khi tăng nhiều dữ liệu bằng tham số xml?


9

Tôi đang cố gắng cập nhật một bảng với một loạt các giá trị. Mỗi mục trong mảng chứa thông tin khớp với một hàng trong bảng trong cơ sở dữ liệu SQL Server. Nếu hàng đã tồn tại trong bảng, chúng tôi cập nhật hàng đó với thông tin trong mảng đã cho. Khác, chúng tôi chèn một hàng mới trong bảng. Về cơ bản tôi đã mô tả upert.

Bây giờ, tôi đang cố gắng đạt được điều này trong một thủ tục được lưu trữ có tham số XML. Lý do tôi đang sử dụng XML và không phải là tham số có giá trị bảng là vì, sau này, tôi sẽ phải tạo loại tùy chỉnh trong SQL và liên kết loại này với thủ tục được lưu trữ. Nếu tôi từng thay đổi một cái gì đó trong thủ tục được lưu trữ hoặc lược đồ db của tôi trên đường, tôi sẽ phải làm lại cả thủ tục được lưu trữ và loại tùy chỉnh. Tôi muốn tránh tình trạng này. Ngoài ra, tính ưu việt của TVP so với XML không hữu ích cho trường hợp của tôi bởi vì, kích thước mảng dữ liệu của tôi sẽ không bao giờ vượt quá 1000. Điều này có nghĩa là tôi không thể sử dụng giải pháp được đề xuất ở đây: Cách chèn nhiều bản ghi bằng XML trong máy chủ SQL 2008

Ngoài ra, một cuộc thảo luận tương tự ở đây ( UPSERT - Có cách nào khác thay thế cho MERGE hoặc @@ rowcount không? ) Khác với những gì tôi đang hỏi bởi vì, tôi đang cố gắng đưa nhiều hàng vào một bảng.

Tôi đã hy vọng rằng tôi chỉ đơn giản sẽ sử dụng bộ truy vấn sau đây để tăng giá trị từ xml. Nhưng điều này sẽ không làm việc. Cách tiếp cận này chỉ được cho là hoạt động khi đầu vào là một hàng đơn.

begin tran
   update table with (serializable) set select * from xml_param
   where key = @key

   if @@rowcount = 0
   begin
      insert table (key, ...) values (@key,..)
   end
commit tran

Thay thế tiếp theo là sử dụng một EXISTS đầy đủ hoặc một trong các biến thể của hình thức sau đây. Nhưng, tôi từ chối điều này trên cơ sở hiệu quả dưới mức tối ưu:

IF (SELECT COUNT ... ) > 0
    UPDATE
ELSE
    INSERT

Tùy chọn tiếp theo là sử dụng câu lệnh Hợp nhất như được mô tả ở đây: http://www.databasejournal.com/features/mssql/USE-the-merge-statement-to-perform-an-upsert.html . Nhưng, sau đó tôi đọc về các vấn đề với truy vấn Hợp nhất tại đây: http://www.mssqltips.com/sqlservertip/3074/use-caestion-with-sql-servers-merge-statement/ . Vì lý do này, tôi đang cố gắng tránh Hợp nhất.

Vì vậy, bây giờ câu hỏi của tôi là: có bất kỳ tùy chọn nào khác hoặc cách tốt hơn để đạt được nhiều lần sử dụng tham số XML trong thủ tục lưu trữ SQL Server 2008 không?

Xin lưu ý rằng dữ liệu trong tham số XML có thể chứa một số bản ghi không được lưu trữ do cũ hơn bản ghi hiện tại. Có một ModifiedDatetrường trong cả XML và bảng đích cần được so sánh để xác định xem bản ghi sẽ được cập nhật hay loại bỏ.


Cố gắng tránh thực hiện các thay đổi đối với Proc trong tương lai không thực sự là một lý do chính đáng để không sử dụng TVP. nếu dữ liệu được chuyển trong các thay đổi, cuối cùng bạn sẽ thực hiện thay đổi mã.
Max Vernon

1
@MaxVernon tôi đã có suy nghĩ tương tự tại đầu tiên và gần như đã viết nhận xét rất giống nhau vì rằng mình không phải là một lý do để tránh TVP. Nhưng họ phải nỗ lực hơn một chút và với lời cảnh báo "không bao giờ vượt quá 1000 hàng" (đôi khi ngụ ý, hoặc thậm chí có thể thường xuyên?), Đó là một chút rắc rối. Tuy nhiên, tôi cho rằng tôi nên đủ điều kiện trả lời để nói rằng <1000 hàng tại một thời điểm không quá khác biệt so với XML miễn là nó không được gọi là 10 nghìn lần liên tiếp. Sau đó, sự khác biệt hiệu suất nhỏ chắc chắn làm tăng lên.
Solomon Rutzky 17/1/2015

Các vấn đề MERGEmà Bertrand chỉ ra hầu hết là các trường hợp cạnh và không hiệu quả, không hiển thị các nút chặn - MS sẽ không phát hành nó nếu đó là một bãi mìn thực sự. Bạn có chắc chắn rằng các kết quả bạn đang trải qua để tránh MERGEkhông tạo ra nhiều lỗi tiềm ẩn hơn là chúng đang lưu không?
Jon của tất cả các giao dịch

@Jonof ALLTrades Công bằng mà nói, những gì tôi đề xuất không thực sự phức tạp so với MERGE. Các bước CHERTN và CẬP NHẬT của MERGE vẫn được xử lý riêng. Sự khác biệt chính trong cách tiếp cận của tôi là biến bảng chứa ID bản ghi được cập nhật và truy vấn XÓA sử dụng biến bảng đó để xóa các bản ghi đó khỏi bảng tạm thời của dữ liệu đến. Và tôi cho rằng NGUỒN có thể được chuyển trực tiếp từ @ XMLparam.nodes () thay vì đổ vào bảng tạm thời, nhưng vẫn không có nhiều thứ để bạn không phải lo lắng về việc gặp phải một trong những trường hợp cạnh đó; - ).
Solomon Rutzky

Câu trả lời:


11

Cho dù nguồn là XML hay TVP không tạo ra sự khác biệt lớn. Các hoạt động tổng thể là về cơ bản:

  1. CẬP NHẬT các hàng hiện có
  2. CHỌN hàng bị thiếu

Bạn làm theo thứ tự đó bởi vì nếu bạn CHỌN trước, thì tất cả các hàng tồn tại để có được CẬP NHẬT và bạn sẽ thực hiện công việc lặp lại cho bất kỳ hàng nào vừa được chèn.

Ngoài ra, có nhiều cách khác nhau để thực hiện điều này và nhiều cách khác nhau để điều chỉnh một số hiệu quả bổ sung từ nó.

Hãy bắt đầu với mức tối thiểu. Do trích xuất XML có thể là một trong những phần tốn kém hơn của hoạt động này (nếu không phải là đắt nhất), chúng tôi không muốn phải làm điều đó hai lần (vì chúng tôi có hai thao tác để thực hiện). Vì vậy, chúng tôi tạo một bảng tạm thời và trích xuất dữ liệu ra khỏi XML vào đó:

CREATE TABLE #TempImport
(
  Field1 DataType1,
  Field2 DataType2,
  ...
);

INSERT INTO #TempImport (Field1, Field2, ...)
  SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
         tab.col.value('XQueryForField2', 'DataType') AS [Field2],
         ...
  FROM   @XmlInputParam.nodes('XQuery') tab(col);

Từ đó, chúng tôi thực hiện CẬP NHẬT và sau đó là CHERTN:

UPDATE tab
SET    tab.Field1 = tmp.Field1,
       tab.Field2 = tmp.Field2,
       ...
FROM   [SchemaName].[TableName] tab
INNER JOIN #TempImport tmp
        ON tmp.IDField = tab.IDField
        ... -- more fields if PK or alternate key is composite

INSERT INTO [SchemaName].[TableName]
  (Field1, Field2, ...)
  SELECT tmp.Field1, tmp.Field2, ...
  FROM   #TempImport tmp
  WHERE  NOT EXISTS (
                       SELECT  *
                       FROM    [SchemaName].[TableName] tab
                       WHERE   tab.IDField = tmp.IDField
                       ... -- more fields if PK or alternate key is composite
                     );

Bây giờ chúng tôi có hoạt động cơ bản xuống, chúng tôi có thể làm một số điều để tối ưu hóa:

  1. chụp @@ ROWCOUNT của chèn vào bảng tạm thời và so sánh với @@ ROWCOUNT của CẬP NHẬT. Nếu chúng giống nhau thì chúng ta có thể bỏ qua INSERT

  2. nắm bắt các giá trị ID được cập nhật thông qua mệnh đề OUTPUT và XÓA chúng từ bảng tạm thời. Sau đó, INSERT không cầnWHERE NOT EXISTS(...)

  3. NẾU có bất kỳ hàng nào trong dữ liệu đến không nên được đồng bộ hóa (nghĩa là không được chèn cũng không được cập nhật), thì những bản ghi đó sẽ bị xóa trước khi thực hiện CẬP NHẬT

CREATE TABLE #TempImport
(
  Field1 DataType1,
  Field2 DataType2,
  ...
);

DECLARE @ImportRows INT;
DECLARE @UpdatedIDs TABLE ([IDField] INT NOT NULL);

BEGIN TRY

  INSERT INTO #TempImport (Field1, Field2, ...)
    SELECT tab.col.value('XQueryForField1', 'DataType') AS [Field1],
           tab.col.value('XQueryForField2', 'DataType') AS [Field2],
           ...
    FROM   @XmlInputParam.nodes('XQuery') tab(col);

  SET @ImportRows = @@ROWCOUNT;

  IF (@ImportRows = 0)
  BEGIN
    RAISERROR('Seriously?', 16, 1); -- no rows to import
  END;

  -- optional: test to see if it helps or hurts
  -- ALTER TABLE #TempImport
  --   ADD CONSTRAINT [PK_#TempImport]
  --   PRIMARY KEY CLUSTERED (PKField ASC)
  --   WITH FILLFACTOR = 100;


  -- optional: remove any records that should not be synced
  DELETE tmp
  FROM   #TempImport tmp
  INNER JOIN [SchemaName].[TableName] tab
          ON tab.IDField = tmp.IDField
          ... -- more fields if PK or alternate key is composite
  WHERE  tmp.ModifiedDate < tab.ModifiedDate;

  BEGIN TRAN;

  UPDATE tab
  SET    tab.Field1 = tmp.Field1,
         tab.Field2 = tmp.Field2,
         ...
  OUTPUT INSERTED.IDField
  INTO   @UpdatedIDs ([IDField]) -- capture IDs that are updated
  FROM   [SchemaName].[TableName] tab
  INNER JOIN #TempImport tmp
          ON tmp.IDField = tab.IDField
          ... -- more fields if PK or alternate key is composite

  IF (@@ROWCOUNT < @ImportRows) -- if all rows were updates then skip, else insert remaining
  BEGIN
    -- get rid of rows that were updates, leaving only the ones to insert
    DELETE tmp
    FROM   #TempImport tmp
    INNER JOIN @UpdatedIDs del
            ON del.[IDField] = tmp.[IDField];

    -- OR, rather than the DELETE, maybe add a column to #TempImport for:
    -- [IsUpdate] BIT NOT NULL DEFAULT (0)
    -- Then UPDATE #TempImport SET [IsUpdate] = 1 JOIN @UpdatedIDs ON [IDField]
    -- Then, in below INSERT, add:  WHERE [IsUpdate] = 0

    INSERT INTO [SchemaName].[TableName]
      (Field1, Field2, ...)
      SELECT tmp.Field1, tmp.Field2, ...
      FROM   #TempImport tmp
  END;

  COMMIT TRAN;

END TRY
BEGIN CATCH
  IF (@@TRANCOUNT > 0)
  BEGIN
    ROLLBACK;
  END;

  -- THROW; -- if using SQL 2012 or newer, use this and remove the following 3 lines
  DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE();
  RAISERROR(@ErrorMessage, 16, 1);
  RETURN;
END CATCH;

Tôi đã sử dụng mô hình này nhiều lần trên Nhập khẩu / ETL có hơn 1000 hàng hoặc có thể 500 trong một đợt trong tổng số 20 nghìn - hơn một triệu hàng. Tuy nhiên, tôi chưa kiểm tra sự khác biệt về hiệu suất giữa XÓA các hàng được cập nhật ra khỏi bảng tạm thời so với chỉ cập nhật trường [IsUpdate].


Xin lưu ý về quyết định sử dụng XML qua TVP do có nhiều nhất 1000 hàng để nhập tại một thời điểm (được đề cập trong câu hỏi):

Nếu điều này được gọi một vài lần ở đây và ở đó, thì rất có thể mức tăng hiệu suất nhỏ trong TVP có thể không xứng đáng với chi phí bảo trì bổ sung (cần phải bỏ Proc trước khi thay đổi Loại bảng do người dùng xác định, thay đổi mã ứng dụng, v.v.) . Nhưng nếu bạn đang nhập 4 triệu hàng, gửi 1000 lần, thì đó là 4000 lần thực thi (và 4 triệu hàng XML để phân tích cho dù nó bị hỏng như thế nào) và thậm chí một sự khác biệt hiệu năng nhỏ khi chỉ được thực hiện vài lần thêm vào một sự khác biệt đáng chú ý.

Điều đó đang được nói, phương pháp như tôi đã mô tả không thay đổi bên ngoài thay thế CHỌN TỪ @XmlInputParam thành CHỌN TỪ @TVP. Vì TVP ở chế độ chỉ đọc, bạn sẽ không thể xóa khỏi chúng. Tôi đoán bạn có thể chỉ cần thêm một WHERE NOT EXISTS(SELECT * FROM @UpdateIDs ids WHERE ids.IDField = tmp.IDField)vào CHỌN cuối cùng đó (gắn với INSERT) thay vì đơn giản WHERE IsUpdate = 0. Nếu bạn đã sử dụng @UpdateIDsbiến bảng theo cách này, thì bạn thậm chí có thể thoát khỏi việc không bỏ các hàng đến vào bảng tạm thời.

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.