2012-11-13 64 views
1

有沒有更好的SQL Server 2008 R2的技術來編寫以下結合INSERT & UPDATE程序,允許NULL INSERTT-SQL開發人員插入和合並存儲過程可以修改爲NULL

我很感興趣的是看看其他開發人員如何編寫能夠處理NULL插入的程序(想象用戶想要撤消一個條目)。我很欣賞將使用MERGE或某些事務回滾技術的更復雜和優雅的解決方案,但我確實要求您從第一原則構建您的示例,因爲這可能會導致該帖子具有更廣泛的吸引力不管什麼讀者T-SQL級別。

這個簡單化的例子的基礎是Orders表跟蹤股票購買。該程序應只允許UPDATESOrderStatus相同或增加..

OrderStatus Explanation 
------------------------- 
    0  Creation 
    1  Checking 
    2  Placement 
    3  Execution 
    ... 
    8  Settlement 

表結構:

CREATE TABLE Orders(
    OrderID INT IDENTITY, 
    Ticker VARCHAR(20) NOT NULL, 
    Size DECIMAL(31,15) NULL, 
    Price DECIMAL(31,15) NULL, 
    OrderStatus TINYINT NOT NULL) 

而且,假設我們有以下數據,以便我們可以測試修改數據:

SET IDENTITY_INSERT [dbo].[Orders] ON 
INSERT INTO [dbo].[Orders] ([OrderID], [Ticker], [Size], [Price], [OrderStatus]) 
VALUES (1, N'MSFT', CAST(1 AS Decimal(31, 15)), NULL, 0) 
     ,(2, N'GOOG', CAST(2 AS Decimal(31, 15)), CAST(523 AS Decimal(31, 15)), 5) 
     ,(3, N'AAPL', CAST(1 AS Decimal(31, 15)), NULL, 0) 
SET IDENTITY_INSERT [dbo].[Orders] OFF 

您應該具有以下數據:

OrderID Ticker Size Price OrderStatus 
----------------------------------------------- 
1  MSFT 1.000 NULL 0 
2  GOOG 2.000 523.000 5 
3  AAPL 1.000 NULL 0 

現在的有趣的部分。這是我盡力設計可以處理NULL插入(即允許用戶撤消一個條目)的組合程序。請注意,我需要一個輸入參數來區分NULL的輸入值是否是有意的,並且需要將該值寫入表中,而將NULL作爲缺少的輸入參數顯示。希望這很清楚爲什麼我問這個問題,因爲我發現我的技術非常冗長。

CREATE PROCEDURE [dbo].[Upsert_Orders] @isNullInsert BIT = 0 
    ,@OrderID INT = NULL 
    ,@Ticker VARCHAR(20) = NULL 
    ,@Size VARCHAR(100) = NULL 
    ,@Price VARCHAR(100) = NULL 
    ,@OrderStatus TINYINT = NULL 
AS 
BEGIN 
    IF (@OrderID IS NOT NULL) 
     -- First check if @OrderID exists 
     IF (
       SELECT OrderID 
       FROM dbo.Orders 
       WHERE OrderID = @OrderID 
       ) IS NULL 
     BEGIN 
      -- @OrderID does not exist therefore replace with NULL 
      SET @OrderID = NULL 
      PRINT 'spUO. Replaced OrderID ' + CAST(@OrderID AS VARCHAR) + ' input parameter with NULL.' 
     END 

    IF @OrderID IS NULL 
    BEGIN 
     -- @OrderID IS NULL so INSERT a new record 
     PRINT 'spUO Inserting a new record into the Orders' 

     INSERT INTO Orders (
      -- OrderID not needed as IDENTITY new record. 
      Ticker 
      ,Size 
      ,Price 
      ,OrderStatus 
      ) 
     VALUES (
      -- @OrderID not needed as IDENTITY new record 
      @Ticker 
      ,@Size 
      ,@Price 
      ,@OrderStatus 
      ) 
    END 
    ELSE 
    BEGIN 
     -- @OrderID IS NOT NULL therefore UPDATE the record @OrderID 
     PRINT 'spUO Modifying existing record with OrderID ' + CAST(@OrderID AS VARCHAR) 

     -- Declare CurrentVariables for @OrderID 
     DECLARE -- @CurrentOrderID INT not needed as @OrderID Found 
      @CurrentTicker VARCHAR(20) 
      ,@CurrentSize DECIMAL(31, 15) 
      ,@CurrentPrice DECIMAL(31, 15) 
      ,@CurrentOrderStatus TINYINT 

     -- Populate Current Variables from Table Orders 
     SELECT -- @CurrentOrderID = OrderID not needed as @OrderID Found 
      @CurrentTicker = Ticker 
      ,@CurrentSize = Size 
      ,@CurrentPrice = Price 
      ,@CurrentOrderStatus = OrderStatus 
     FROM Orders 
     WHERE OrderID = @OrderID 

     IF ISNULL(@OrderStatus, @CurrentOrderStatus) >= @CurrentOrderStatus 
     BEGIN 
      -- Update @OrderID if not moving backwards 
      IF @isNullInsert = 0 
      BEGIN 
       -- We are not updating the record with NULL 
       PRINT 'spUO NULL Parameter Input Values get replaced with the existing entries' 

       UPDATE Orders 
       SET -- OrderID = ISNULL(@OrderID, @CurrentOrderID) not needed as @OrderID Found 
        Ticker = ISNULL(@Ticker, @CurrentTicker) 
        ,Size = ISNULL(@Size, @CurrentSize) 
        ,Price = ISNULL(@Price, @CurrentPrice) 
        ,OrderStatus = ISNULL(@OrderStatus, @CurrentOrderStatus) 
       WHERE OrderID = @OrderID 
      END 
      ELSE 
      BEGIN 
       -- We are potentially overwritting the record with NULL 
       PRINT 'spUO Old entries may be overwritten with NULL' 

       UPDATE Orders 
       SET -- OrderID = ISNULL(@OrderID, @CurrentOrderID) not needed as @OrderID Found 
        Ticker = @Ticker 
        ,Size = @Size 
        ,Price = @Price 
        ,OrderStatus = @OrderStatus 
       WHERE OrderID = @OrderID 
      END 
     END 
     ELSE 
      -- User is trying to re-write hostory. Do Nothing 
      PRINT 'spUO You do not have permissions to roll back the OrderStatus.' 
    END 
END 

現在,我們有一個UPSERT程序,讓我來舉例說明它的用法:

步驟1:插入一個新行,顯示有意向購買一些福特的股票:

EXEC dbo.Upsert_Orders @Ticker = 'F', 
         @Size = 1, 
         @Price = 10, 
         @OrderStatus = 2 

步驟2:讓我們看看OrderStatus不能回捲

EXEC dbo.Upsert_Orders @OrderID = 4, 
         @Ticker = 'F', 
         @Size = 1, 
         @Price = 10, 
         @OrderStatus = 1 

這產生了des紅外發光二極管輸出:

spUO Modifying existing record with OrderID 4 
spUO You do not have permissions to roll back the OrderStatus. 

的數據現在看起來像:

OrderID Ticker Size Price OrderStatus 
----------------------------------------------- 
1  MSFT 1.000 NULL 0 
2  GOOG 2.000 523.000 5 
3  AAPL 1.000 NULL 0 
4  F  1.000 10.000 2 

步驟3:最後,讓我們假設用戶想要刪除一階的股份,然後在我的程序不幸的方法需要其他默認參數將被傳遞並且需要將@isNULLInsert BIT設置爲1。

EXEC dbo.Upsert_Orders @isNullInsert = 1, 
         @OrderID = 1, 
         @Ticker = 'MSFT', 
         @Size = NULL, 
         @Price = NULL, 
         @OrderStatus = 0 

希望這個完整的例子示出了在添加新記錄,更新現有記錄和刪除記​​錄的一個字段中的概念。對這篇文章的長度抱歉,但這是我能夠製作的最簡潔的代碼!

最終數據:

OrderID Ticker Size Price OrderStatus 
------------------------------------------------ 
1  MSFT NULL NULL 0 
2  GOOG 2.000 523.000 5 
3  AAPL 1.000 NULL 0 
4  F  1.000 10.000 2 

感謝所有,

伯蒂。

p.s.這將從Excel VBA調用。

+0

多個用戶會一次調用嗎? – Laurence

+1

愚蠢的問題:你爲什麼不只是刪除你想「撤消」的行?爲什麼所有這些複雜的在現有行中插入NULL值?看起來過於複雜和不必要...... –

+0

訂單的生命週期是這樣的,訂單有很多步驟(即OrderStatus),通常跨越多天。在大公司中,許多人可以參與執行訂單以達成和解(因此鎖定可能是一個問題)。在我讀過的幾本書中,人們主張編寫存儲過程來執行INSERT和UPDATE操作。此外,財務作爲受監管的行業意味着我已經將商業/投訴邏輯編入程序(即不能改變歷史交易)。另外,當真正需要執行UPDATE時,我不喜歡過度使用DEL和INS。謝謝 – Bertie

回答

5

這是使用合併的答案。

Create Procedure [dbo].[Upsert_Orders2] 
    @IsNullInsert Bit = 0, 
    @OrderID Int = Null, 
    @Ticker Varchar(20) = Null, 
    @Size Decimal(31,15) = Null, 
    @Price Decimal(31,15) = Null, 
    @OrderStatus Tinyint = Null 
As 

Declare @OrderStatusChange Table(Oldstatus int, NewStatus int) 

Begin Transaction 

Merge 
    dbo.Orders As target 
Using 
    (Select @OrderID As OrderID) As source 
On 
    (target.OrderID = source.OrderID) 
When Matched Then 
    Update Set 
    Ticker = Case When @IsNullInsert = 0 Then IsNull(@Ticker, target.Ticker) Else @Ticker End, 
    Size = Case When @IsNullInsert = 0 Then IsNull(@Size, target.Size) Else @Size End, 
    Price = Case When @IsNullInsert = 0 Then IsNull(@Price, target.Price) Else @Price End, 
    OrderStatus = Case When @IsNullInsert = 0 Then IsNull(@OrderStatus, target.OrderStatus) Else @OrderStatus End 
When Not Matched Then 
    Insert 
    (Ticker, Size, Price, OrderStatus) 
    Values 
    (@Ticker, @Size, @Price, @OrderStatus) 
Output 
    deleted.OrderStatus, inserted.OrderStatus into @OrderStatusChange; 

If Exists (Select 'x' From @OrderStatusChange Where NewStatus < OldStatus) 
    -- Evil History Changer! 
    Rollback Transaction 
Else 
    Commit Transaction 
+0

感謝勞倫斯,我能夠根據我的需求調整這個出色的答案。非常優雅的解決方案(與我非常詳細的過程相比)。出於興趣,您是否使用這種Merge方法作爲插入數據庫的標準方法? – Bertie

+0

@Bertie我在主 - 細節記錄中發現了更多用途,用戶可以在批處理中添加/刪除/編輯詳細記錄。我可能只是有兩個不同的程序。你知道你需要根據你是否有OrderID來調用。我會考慮嘗試更新OrderID不再存在的訂單,因爲值得提及給用戶。 – Laurence