1

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

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

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

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 插入(即允许用户撤消条目)的组合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

现在我们有了一个 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

这会产生所需的输出:

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

第三步:最后,假设用户要删除第一笔订单的股份,那么我的程序下的不幸方法需要传递其他默认参数,并且@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

谢谢大家,

伯蒂。

ps 这将从 Excel VBA 中调用。

4

1 回答 1

7

这是使用合并的答案。

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
于 2012-11-15T22:43:51.920 回答