2016-09-23 39 views
3

我寫一個數據泵,但未通過source_tableoz4codesome_dataoz1oz2oz3,信息,我這樣做是與相同的結構存儲target_table。同時,我想更新也具有不同的結構(比如oz_table)另一個表 - 記錄codeoz - 這是四個記錄,而不是一個記錄有四個值(最大,因爲空和NULL值對於ozX不被存儲)。如何使用MERGE,然後將一個源記錄拆分爲更多的目標記錄?

我正在使用MERGE命令(Microsoft T-SQL)作爲現有的target_table(一個記錄中的四盎司 - 舊方法)。使用OUTPUT機制將INSERT/UPDATE記錄收集到表變量@info_table中。 (如果源記錄消失的目標記錄被故意永遠不會被刪除,所以,不存在DELETE操作)

到目前爲止,我有這樣的代碼:

CREATE PROCEDURE dbo.data_pump 
AS 
BEGIN 
    SET NOCOUNT ON 
    DECLARE @result int = -555 -- init (number of affected records) 

    DECLARE @info_table TABLE (
     action nvarchar(10), 
     code int, 
     oz1 nvarchar(40), 
     oz2 nvarchar(40), 
     oz3 nvarchar(40), 
     oz4 nvarchar(40) 
    ) 

    BEGIN TRANSACTION tran_data_pump 
    BEGIN TRY 
     MERGE target_table AS target 
     USING (SELECT code, some_data, oz1, oz2, oz3, oz4 
       FROM source_table) AS source 
     ON target.code = source.code 
     WHEN MATCHED AND (COALESCE(target.some_data, '') != COALESCE(source.some_data, '') 
          OR COALESCE(target.oz1, '') != COALESCE(source.oz1, '') 
          OR COALESCE(target.oz2, '') != COALESCE(source.oz2, '') 
          OR COALESCE(target.oz3, '') != COALESCE(source.oz3, '') 
          OR COALESCE(target.oz4, '') != COALESCE(source.oz4, '') 
         ) THEN 
      UPDATE 
      SET target.some_data = source.some_data, 
       target.oz1 = source.oz1, 
       target.oz2 = source.oz2, 
       target.oz3 = source.oz3, 
       target.oz4 = source.oz4 
     WHEN NOT MATCHED THEN 
      INSERT (code, some_data, 
        oz1, oz2, oz3, oz4) 
      VALUES (source.code, source.some_data, 
        source.oz1, source.oz2, source.oz3, source.oz4) 
     OUTPUT 
      $action AS action, -- INSERT or UPDATE 
      inserted.code AS code, 
      inserted.oz1 AS oz1, 
      inserted.oz2 AS oz2, 
      inserted.oz3 AS oz3, 
      inserted.oz4 AS oz4 
     INTO @info_table; 

     SET @result = @@ROWCOUNT 

     COMMIT TRANSACTION tran_data_pump 
    END TRY 
    BEGIN CATCH 
     ROLLBACK TRANSACTION tran_data_pump 
     SET @result = -1 -- transaction-failed indication 
    END CATCH 
    RETURN @result -- OK, number of the transfered records 
END 

它工作正常,到目前爲止, 。現在我想處理@info_table來插入/更新oz_table。對於動作UPDATE,應先刪除帶有code的記錄,並插入新記錄。順序不重要,插入記錄的新數可能不同。 NULL或以盎司爲單位的空串不應產生任何記錄。對於INSERT操作,插入新記錄的情況更簡單。

更新:該問題稍作修改以澄清問題的核心。數據表可以這樣定義:?

CREATE TABLE dbo.source_table (
    ID int IDENTITY PRIMARY KEY NOT NULL, 
    code int, 
    some_data nvarchar(50), 
    oz1 nvarchar(40), 
    oz2 nvarchar(40), 
    oz3 nvarchar(40), 
    oz4 nvarchar(40) 
) 

CREATE TABLE dbo.target_table (
    ID int IDENTITY PRIMARY KEY NOT NULL, 
    code int, 
    some_data nvarchar(50), 
    oz2 nvarchar(40), 
    oz3 nvarchar(40), 
    oz1 nvarchar(40), 
    oz4 nvarchar(40) 
) 

CREATE TABLE dbo.oz_table (
    ID int IDENTITY PRIMARY KEY NOT NULL, 
    code int, 
    oz nvarchar(40) NOT NULL 
) 

看到完整的測試腳本(創建數據庫,表,調用data_pumphttp://pastebin.com/wBz3Tzwn

如何很好地做到這一點,我需要的高效解決方案日期的量可能大,而且操作要儘可能快

+2

你說_Now我想處理'@ info_table'插入/更新'alternative_table'_,是它除了'source_table','target_table','@ info_table'第四屆表?這似乎是另一個MERGE操作。順便說一句,如果你能提供一個輸入/輸出的例子,那理解起來會更好。 –

+1

你能提供另一個表的結構嗎? –

+0

@MincongHuang:是的。查看更新的問題。您還可以找到完整代碼片段的鏈接,包括測試數據。只有在代碼片段的oz_table定義中用'code'替換'sourceID'(我的錯誤)。沒有輸出示例,但它應該是具有「代碼」的未轉義輸入(如果插入或修改了源記錄)。 – pepr

回答

2

既然你在談論效率,那麼起初應該有適當的指標。

source_tabletarget_table應該在code上有唯一索引。 它應該是唯一的,否則主要MERGE會嘗試多次更新同一行時失敗。

oz_table應該在code上有非唯一索引。

@info_table應該有code作爲主鍵。 這是不可能從一個MERGE有相同code兩個不同的動作,所以code應該是唯一的:

DECLARE @info_table TABLE 
(
    action nvarchar(10), 
    code int PRIMARY KEY, 
    oz1 nvarchar(40), 
    oz2 nvarchar(40), 
    oz3 nvarchar(40), 
    oz4 nvarchar(40) 
); 

無需額外的臨時表。我們已經有一個 - @info_table

MERGE後,當@info_table填充我們需要做兩個步驟:1)從oz_table刪除某些行,2)一些行添加到oz_table

首先從oz_table刪除那些由MERGE更新的行。 事實上,由MERGE插入的那些行將不會以任何方式存在於oz_table中,因此我們可以使用簡單的DELETE語句。 沒有必要通過UPDATE操作進行過濾。這樣的過濾器不會刪除任何行。

如果在數據泵過程之外有可能更改oz_table,則需要額外的過濾器。

​​

這兩個表上的索引code將有助於有效地加入它們。

然後,只需插入更新和插入的行。

INSERT INTO dbo.oz_table(code, oz) 
SELECT 
    T.code 
    ,CA.oz 
FROM 
    @info_table AS T 
    CROSS APPLY 
    (
     VALUES 
      (T.oz1), 
      (T.oz2), 
      (T.oz3), 
      (T.oz4) 
    ) AS CA (oz) 
WHERE 
    CA.oz IS NOT NULL 
    AND CA.oz <> '' 
    -- The NULL or empty strings should not produce any record 
; 

我更喜歡用CROSS APPLY and VALUES instead of UNPIVOT。我們想要刪除NULL和空值,所以我認爲最好是明確地編寫過濾器,以便每個讀取代碼的人都能看到它。用UNPIVOT您需要知道它隱式刪除NULL。你仍然需要添加一個過濾器來刪除空值。在這種情況下,性能最可能是相同的,但你最好檢查真實的數據。


如果有機會的話,這個數據泵程序可以在同一時間被調用一次以上,那麼你必須採取額外的步驟,以防止可能出現的併發問題(不正確的結果或死鎖)。我更喜歡使用sp_getapplock來保證只有一個存儲過程的實例可以隨時運行。

+1

+1總是很好的瞭解解決方案的不同方法,並通過'UNPIVOT'和' CROSS APPLY' – Abhishek

+0

感謝弗拉基米爾對此發表評論。我知道這些索引(只想保持簡單的問題)。坦率地說,我原本想避免做一個遊標和循環。我也瞭解併發問題。無論如何,交易是否會阻止可能的混淆?對於刪除,我確實使用了INNER JOIN和'@ info_table'。 IN是否更有效率? – pepr

+1

@pepr,是的,所有步驟('MERGE','DELETE','INSERT')都應該包含在一個事務中。我沒有深入分析,如果你的案例中可能存在僵局。這可能取決於你的數據。例如,兩個進程是否可以嘗試同時更新相同的「代碼」?如果死鎖是可能的,那麼只有一個事務(具有默認隔離級別)可能不足以阻止它們。你必須測試它。 'JOIN'與'IN' - 嘗試並比較執行計劃。他們很可能會是一樣的。對我而言,「IN」更容易閱讀/理解。 –

4

如果我理解正確的話,然後下面的方法您的問題聲明可能是解決方法之一 -

-- declare the temp tables 
    DECLARE @info_table TABLE (
     action nvarchar(10), 
     ID int, 
     oz1 nvarchar(40), 
     oz2 nvarchar(40), 
     oz3 nvarchar(40), 
     oz4 nvarchar(40) 
    ) 
    --create intermediate table to store the results 
    CREATE TABLE #temp_alternative_table (ID int,oz nvarchar(40)) 
    -- insert some dummy values 
    INSERT INTO @info_table (action,ID,oz1,oz2,oz3,oz4) 
    VALUES 
     ('INSERT',1, '85', '94', '78', '90'), 
     ('UPDATE',2, '75', '88', '91', '78') 
    --SELECT * FROM @info_table 
    -- doing unpivot and transforming one row many columns to many rows one column and inserting into intermediate temp table 
    INSERT INTO #temp_alternative_table 
    SELECT * 
    FROM (
     SELECT 
       Action 
      , ID 
      , [Oz] 
     FROM @info_table 
     UNPIVOT 
     (
      [Oz] FOR tt IN (oz1, oz2, oz3, oz4) 
     ) unpvt 
    ) t 
    -- delete from main table all the records for which the action is UPDATE (stored in intermediate temp table for the same ID as of main table) 
    DELETE at 
    FROM alternative_table at 
    INNER JOIN #temp_alternative_table tat 
     ON at.ID = tat.ID 
    WHERE tat.action = 'UPDATE' 
    -- now insert all the records in main table 
    INSERT INTO alternative_table (ID,Oz) 
    SELECT ID,Oz 
    FROM #temp_alternative_table 

讓我知道這是你在找什麼。希望這可以幫助。

+0

你是第一個,你確實建議UNPIVOT。但是,200美元的獎金相當多,只有當您根據更新後的問題重新思考解決方案時,我纔會給您提供 - 請參閱測試代碼片段,以作爲問題中的鏈接。我已經有了一個更整潔的解決方案。我不會告訴你,因爲你可以找到更好的;)但我已經準備好在更多的努力後給你賞金。 – pepr

+2

@pepr請定義「更整齊」。任何順便說一句,如果您在7天內(加上24小時的寬限期)不獎勵您的賞金,在獎金開始後創建的最高投票回答將以最低2分開始,將獲得賞金金額的一半。威脅賞金並扣留可幫助開發人員找到更適合您的問題的理想解決方案的信息並不好。 – scsimon

+0

@scsimon:至少,對我來說「更整潔」是當它不包含不必要的構造或句法「垃圾」時。例如,有UNPIVOT的人可以這樣寫:'INSERT INTO oz_table SELECT code,oz FROM @info_table UNPIVOT(oz FOR x IN(oz1,oz2,oz3,oz4))AS t' – pepr

1

需要考慮的稍微不同的方法是在您的target_table上定義更新/插入觸發器。使用這種方法,您的數據泵只需要考慮您的初始目標表。觸發器將轉換並將合併的數據插入alternative_table表中。

如果您願意承擔將原始oz列名作爲搜索關鍵字存儲在替代表中的額外數據存儲成本,那麼您可以使用另一個將改善觸發器整體性能的合併語句,如下所示:

-- Create example table 
CREATE TABLE [dbo].[alternative_table](
    [ID] [int] NOT NULL, 
    [ColumnKey] [nvarchar](5) NOT NULL, 
    [oz] [nvarchar](100) NULL, 
CONSTRAINT [PK_alternative_table] PRIMARY KEY CLUSTERED 
(
    [ID] ASC, 
    [ColumnKey] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

GO 

-- Create trigger responsible for populating Alternative table. 
CREATE TRIGGER dbo.MergeWithAlternative 
    ON dbo.target_table 
    AFTER INSERT, UPDATE 
AS 
BEGIN 

    SET NOCOUNT ON; 


    MERGE [alternative_table] AS [target] 
    USING (
       SELECT 
         [ID] 
        , [ColumnKey] 
        , [Oz] 
       FROM inserted 
       UNPIVOT 
       (
        [Oz] FOR [ColumnKey] IN (oz1, oz2, oz3, oz4) 
       ) unpvt 
      ) AS [source] 
    ON [target].ID = [source].ID AND [target].[ColumnKey] = [source].[ColumnKey] 
    WHEN MATCHED THEN 
     UPDATE 
     SET [target].oz = [source].[Oz] 
    WHEN NOT MATCHED THEN 
     INSERT 
     (
      ID 
      ,[ColumnKey] 
      ,[oz] 
     ) 
     VALUES 
     (
      source.ID 
      ,source.[ColumnKey] 
      ,source.[Oz] 
     ); 

END 

如果你不願意列名存儲爲一個鍵查找,然後簡單的刪除/插入動作也能發揮作用:

-- Create example table 
CREATE TABLE [dbo].[alternative_table](
    [ID] [int] NOT NULL, 
    [oz] [nvarchar](100) NULL 
) ON [PRIMARY] 

GO 

-- Create trigger responsible for populating Alternative table. 
CREATE TRIGGER dbo.MergeWithAlternative 
    ON dbo.target_table 
    AFTER INSERT, UPDATE 
AS 
BEGIN 

    SET NOCOUNT ON; 


    DELETE [dbo].[alternative_table] 
    WHERE [ID] IN (SELECT ID FROM deleted) 

    INSERT INTO [dbo].[alternative_table] 
    (
     [ID] 
     ,[oz] 
    ) 
    SELECT [ID] 
      ,[Oz] 
    FROM inserted 
    UNPIVOT 
    (
     [Oz] FOR [ColumnKey] IN (oz1, oz2, oz3, oz4) 
    ) unpvt 

END 

表變量和填充它的輸出條款不應該是nee用這種方法再也行不通了。

+0

謝謝埃德蒙。我更喜歡非觸發解決方案,因爲可能有大量數據,解決方案必須快速。另外,通過OUTPUT,我確切地知道目標記錄應該受到什麼影響以及如何。 +1爲你的幫助,併爲unpivot。 – pepr

+0

我只需要基於'code'進行合併(在問題更新之前是'ID')。這是否是UPDATE或INSERT的唯一標準。 INSERT的決定很簡單。但是,更新只應在特定列不同時才能完成 - 盎司值屬於俱樂部。受影響記錄的處理也應該儘可能高效。我會在問題下面添加一些評論。 – pepr

+0

@pepr只是幾個問題。源表是一個緩存/臨時表,它被修剪以保持其尺寸相對較小或者隨着時間的推移而不斷增長?你使用的是什麼版本的SQL Server?你還使用企業版還是標準版?可以稍微修改源表模式(如添加額外的列)? –

0

由於我們需要通過替換舊記錄並添加新記錄來構建oz_table,我寧願先截斷它,然後通過插入所有記錄重新構建它。 我將使用兩個cte,第一個一個讀取@info_table,而另一個讀取UNION所有四個盎司列的所有行。然後將UNION插入oz_Table。

Truncate table dbo.oz_table 

with cte as(
    Select Code, oz1, oz2, oz3, oz4 from @info_table 
), cte2 as(
      Select Code, oz1 as oz From cte 
    UNION ALL Select Code, oz2 as oz From cte 
    UNION ALL Select Code, oz3 as oz From cte 
    UNION ALL Select Code, oz4 as oz From cte 
) 
Insert into dbo.oz_table(Code, oz) 
select Code, oz from cte2 
Where oz is not null and oz<>'' 
相關問題