【问题标题】:How can I auto increment the primary key in SQL Server 2016 merge insert without sequences?如何在没有序列的情况下自动增加 SQL Server 2016 合并插入中的主键?
【发布时间】:2018-06-12 13:31:37
【问题描述】:

我正在编写一个查询以将数据从一个表导入到一个新表。我需要插入新表中不存在的记录,并更新确实存在的记录。我正在尝试使用 MERGE "upsert" 方法。

由于客户端的数据库和应用程序结构,我遇到了一些独特的问题。我要插入的表有一个唯一 ID 字段,每个新行递增 1,但表不会自动递增; insert语句需要拉取目标表中的最高ID,新记录加1。

根据我的研究,我无法弄清楚如何使用 MERGE 做到这一点。我没有创建序列的数据库权限。我已经尝试了很多东西,但目前我的查询看起来像:

MERGE
    dbo.targetTable as target
USING
    dbo.sourceTable AS source
ON
    target.account_no = source.account_ID

WHEN NOT MATCHED THEN
    INSERT (
        ID,
        FIELD1,
        FIELD2,
        FIELD3
) VALUES (
        (SELECT MAX(ID) + 1 FROM dbo.targetTable),
        'field1',
        'field2',
        'field3'
)

然后我在使用此代码时遇到的问题是,它似乎只为新 ID 运行一次 select 语句。也就是说,如果目标表中的最高 ID 是 10,它将插入 ID 为 11 的每条新记录。这将不起作用,因为我得到了 Violation of PRIMARY KEY constraint. Cannot insert duplicate key in object 错误。我一直在做大量的谷歌搜索并尝试不同的事情,但一直无法弄清楚这一点。任何帮助表示赞赏,谢谢。

编辑:为澄清起见,唯一 ID 列不会自动填充。如果我没有为 ID 列插入值,我会得到 Cannot insert the value NULL into column 'ID', table 'dbo.targetTable'; column does not allow nulls. UPDATE fails.

同样,正如我最初提到的,我没有创建序列的权限。它只是抛出一个错误并说我没有权限这样做。

我同意将 ID 列自动更改为自动递增是完美的,但我也没有能力像那样修改表格。

【问题讨论】:

  • 您是否有机会将ID 列更改为具有IDENTITY 属性以便它自己处理?我认为这是一个完美的用例。

标签: sql sql-server primary-key sql-server-2016 sql-merge


【解决方案1】:

如果您不需要连续的 ID,您可以将最后一个可用的 ID 添加到 ROW_NUMBER() 以生成新的、不重复的 ID。

BEGIN TRANSACTION

    DECLARE @NextAvailableID INT = (SELECT ISNULL(MAX(ID), 0) FROM dbo.targetTable WITH (TABLOCKX))

    ;WITH SourceWithNewIDs AS
    (
        SELECT
            S.*,
            NewID = @NextAvailableID + ROW_NUMBER() OVER (ORDER BY S.account_ID)
        FROM
            dbo.sourceTable AS S
    )
    MERGE
        dbo.targetTable as target
    USING
        SourceWithNewIDs AS source
    ON
        target.account_no = source.account_ID
    WHEN NOT MATCHED THEN
        INSERT (
            ID,
            FIELD1,
            FIELD2,
            FIELD3
    ) VALUES (
            NewID,
            'field1',
            'field2',
            'field3'
    )

COMMIT

请记住,此示例缺少正确的回滚错误处理,用于检索最大 ID 的锁将阻止所有其他操作,直到提交或回滚。

如果您需要新行具有连续的 ID,那么您可以使用与常规 INSERT(使用 WHERE NOT EXISTS...)而不是 MERGE(必须单独编写 UPDATE)相同的方法。

【讨论】:

    【解决方案2】:

    这只是不使用合并的另一种方式。临时表不需要权限,所以我会使用一个来保存需要插入的帐号,并带有一个身份字段来帮助遍历。 while 循环可以遍历标识,将与源表的 account_no- 相关的值插入到目标表中。由于插入是在循环中完成的,因此 MAX 函数应该在每个循环中正确获取目标表的MAX(account_no)

    DECLARE @tempTable TABLE (pkindex int IDENTITY(1,1) PRIMARY KEY, account_no int)
    DECLARE @current int = 1
           ,@endcount int = 0
    
    --account_no's that should be inserted
    INSERT INTO @tempTable(account_no)
    SELECT account_no 
      FROM sourceTable 
     WHERE account_no NOT IN (SELECT account_no FROM targetTable)
    
    SET @endcount = (SELECT COUNT(*) FROM @tempTable)
    
    --looping condition, should select the MAX(ID) with each subsequent loop
    WHILE (@endcount > 0) AND (@current <= @endcount)
    BEGIN
    INSERT INTO dbo.targetTable(ID, FIELD1, FIELD2, FIELD3)
    SELECT (SELECT MAX(T2.ID) + 1 FROM dbo.targetTable T2) AS MAXID
          ,S.field1
          ,S.field2
          ,S.field3
      FROM @tempTable T INNER JOIN sourceTable S ON T.account_no = S.account_no
     WHERE T.pkindex = @current --traversing temp table by its identity
    
    SET @current += 1
    
    END
    

    【讨论】:

      猜你喜欢
      • 2010-11-06
      • 2018-04-30
      • 2016-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-17
      • 2012-12-14
      • 2022-01-02
      相关资源
      最近更新 更多