T-SQL开发人员插入并合并可以修改为NULL的存储过程

Ber*_*tie 1 sql t-sql vba stored-procedures sql-server-2008-r2

是否有更好的SQL Server 2008 R2技术来编写允许NULL 的以下组合INSERTUPDATE过程INSERT

我非常有兴趣了解其他开发人员如何编写INSERTUPDATE可以处理NULL插入的过程(想象用户想要撤消条目).我很欣赏将有更复杂和优雅的解决方案使用MERGE或者我有兴趣看到的一些事务回滚技术,但是,我确实要求你从第一原则建立你的例子,因为这可能导致帖子具有更广泛的吸引力,无论如何什么读者T-SQL级别.

这个简单示例的基础是Orders跟踪股票购买的表格.该程序应仅在相同或增加UPDATES时允许OrderStatus.

OrderStatus  Explanation
-------------------------
     0       Creation
     1       Checking
     2       Placement
     3       Execution
     ...
     8       Settlement
Run Code Online (Sandbox Code Playgroud)

表结构:

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)
Run Code Online (Sandbox Code Playgroud)

此外,让我们假设我们有以下数据,以便我们可以测试修改数据:

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
Run Code Online (Sandbox Code Playgroud)

您应该拥有以下数据:

OrderID Ticker  Size    Price   OrderStatus
-----------------------------------------------
1       MSFT    1.000   NULL    0
2       GOOG    2.000   523.000 5
3       AAPL    1.000   NULL    0
Run Code Online (Sandbox Code Playgroud)

现在为有趣的部分.这是我尽最大努力设计一个可以处理NULL插入的组合INSERT&UPDATE过程(即允许用户撤消条目).请注意,我需要一个输入参数来区分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
Run Code Online (Sandbox Code Playgroud)

现在我们有一个UPSERT程序,让我说明它的用法:

第1步:插入新行以显示购买一些福特股票的意图:

EXEC dbo.Upsert_Orders  @Ticker = 'F',
                        @Size = 1,
                        @Price = 10,
                        @OrderStatus = 2
Run Code Online (Sandbox Code Playgroud)

第2步:让我们看看它OrderStatus不能被卷回来

EXEC dbo.Upsert_Orders  @OrderID = 4,
                        @Ticker = 'F',
                        @Size = 1,
                        @Price = 10,
                        @OrderStatus = 1
Run Code Online (Sandbox Code Playgroud)

这会产生所需的输出:

spUO Modifying existing record with OrderID 4 
spUO You do not have permissions to roll back the OrderStatus.
Run Code Online (Sandbox Code Playgroud)

数据现在看起来像:

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
Run Code Online (Sandbox Code Playgroud)

第3步:最后,让我们假设用户想要删除第一个订单的共享,然后在我的程序下的不幸方法需要传递其他默认参数并且@isNULLInsert BIT需要设置为1.

EXEC dbo.Upsert_Orders  @isNullInsert = 1,
                        @OrderID = 1,
                        @Ticker = 'MSFT',
                        @Size = NULL,
                        @Price = NULL,
                        @OrderStatus = 0
Run Code Online (Sandbox Code Playgroud)

希望这个完整的例子说明了添加新记录,更新现有记录和删除记​​录字段的概念.对这篇文章的篇幅感到抱歉,但这是我能够制作的最简洁的代码!

最终数据:

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
Run Code Online (Sandbox Code Playgroud)

谢谢大家,

伯蒂.

ps这将从Excel VBA调用.

Lau*_*nce 6

这是使用merge的答案.

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
Run Code Online (Sandbox Code Playgroud)