复杂的 TSQL 合并

发布于 2024-12-06 01:27:26 字数 1069 浏览 4 评论 0原文

我“继承了”一段出色的 TSQL 代码,它可以执行以下操作:

  • 在游标上逐行循环。
  • 游标包含表 A 中需要合并(更新插入)的数据
  • 。对于游标中的每个行循环,都会调用一个存储过程。过程:
    • 如果表 A 中存在相应行,则更新该行
    • 如果这样的行不存在,则:
      • 在不同的表 B 中插入一行。
      • 获取新生成的 ID(假设其名为 IDB)
      • 在表 A 中插入一行。表 A 插入需要 IDB(该字段不为空,它应该仅具有来自表 B 的值,但没有 FK 约束)

显然这很糟糕(性能和优雅原因)!

问题 乍一看,这看起来像是 MERGE 使用的标准情况。我尝试这样做:

MERGE [dbo].[TableA] AS Target
USING <cursor data set as a select statement> as Src on target.IDA = Src.IDA
WHEN MATCHED 
  //update
WHEN NOT MATCHED
//insert <------ Fails because obviously a new IDB is required

还尝试了各种方法,例如在 OUTPUT 上发送 IDB 的嵌套选择,但它失败了,因为 IDB 是 PK。

其他类型的合并也失败了,例如:

MERGE Table A with <cursor data set as a select statement>
...
MERGE Table A with Table B
WHEN NOT MATCHED
//insert on Table A
WHEN NOT MATCHED
// Update Table B

有人对此有想法吗?本质上,我认为如果我们概括问题将是:

我可以在一个可以嵌套在其他语句中的语句中插入并返回 PK

提前感谢

乔治的任何回复

I have 'inherited' a brilliant piece of TSQL code that does this:

  • Loops row-by-row over a cursor.
  • The cursor contains data that need to be merged (Upserted) in Table A
  • For each row loop in the cursor a stored proc is called. The proc:
    • If a corresponding row exists in Table A then it is updated
    • If such a row does not exist then:
      • Inserts a single row in in a different Table B.
      • Fetches the newly generated ID (say its called IDB)
      • Inserts a single row in Table A. Table A insertions need an IDB (the field is not null, it is supposed to have values ONLY from table B, but no FK constraint is in place)

Obviously this sucks (performance & elegance reasons)!!

Question
At first this looks like a standard case of MERGE usage. I tried doing:

MERGE [dbo].[TableA] AS Target
USING <cursor data set as a select statement> as Src on target.IDA = Src.IDA
WHEN MATCHED 
  //update
WHEN NOT MATCHED
//insert <------ Fails because obviously a new IDB is required

Also tried various approaches like a nested select that sends IDB on the OUTPUT but it fails because IDB is a PK.

Other kinds of merges also failed eg:

MERGE Table A with <cursor data set as a select statement>
...
MERGE Table A with Table B
WHEN NOT MATCHED
//insert on Table A
WHEN NOT MATCHED
// Update Table B

Does anyone have an idea on this? Essentially I think if we generalise the question would be:

Can I insert and return the PK in one statement that can be nested in other statements

Thanks in advance for any replies

George

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浅暮の光 2024-12-13 01:27:26

如果您在 TableB 上有自动生成的 PK,则可以使用与此类似的代码。否则,只需将 INSERT 更改为 TableA,即可先从 TableB 中获取 PK。

DECLARE @OldData CHAR(10)
SET @OldData = 'Old'
DECLARE @NewData CHAR(10)
SET @NewData = 'New'

CREATE TABLE #TableA 
(
    IDA INT IDENTITY(1,1) PRIMARY KEY,
    IDB INT NOT NULL,
    DataA CHAR(10)
)

CREATE TABLE #TableB 
(
    IDB INT IDENTITY(1,1) PRIMARY KEY,
    DataB CHAR(10)
)

DECLARE @IDsToUpsert TABLE
(
    ID INT
)

-- Add test values for existing rows 
INSERT INTO #TableB
OUTPUT INSERTED.IDB, @OldData
INTO #TableA
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData 

-- Add test values for the rows to upsert
INSERT INTO @IDsToUpsert
SELECT 1 UNION -- exists
SELECT 3 UNION -- exists
SELECT 5 UNION -- does not exist
SELECT 7 UNION -- does not exist
SELECT 9       -- does not exist

-- Data Before
SELECT * From #TableA
SELECT * From #TableB

DECLARE rows_to_update CURSOR
    FOR SELECT ID FROM @IDsToUpsert

DECLARE @rowToUpdate INT
DECLARE @existingIDB INT

OPEN rows_to_update;

FETCH NEXT FROM rows_to_update 
INTO @rowToUpdate;

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRANSACTION

        IF NOT EXISTS 
        (
            SELECT 1 FROM #TableA WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
            WHERE IDA = @rowToUpdate            
        )
        BEGIN
            -- Insert into B, then insert new val into A
            INSERT INTO #TableB
            OUTPUT INSERTED.IDB, INSERTED.DataB 
            INTO #TableA
            SELECT @NewData
            -- Change code here if PK on TableB is not autogenerated
        END
        ELSE
        BEGIN
            -- Update
            UPDATE #TableA
            SET DataA = @NewData
            WHERE IDA = @rowToUpdate
        END

    COMMIT TRANSACTION

    FETCH NEXT FROM rows_to_update 
    INTO @rowToUpdate;
END

CLOSE rows_to_update;
DEALLOCATE rows_to_update;

SELECT * FROM #TableA
SELECT * FROM #TableB

DROP TABLE #TableA
DROP TABLE #TableB

If you have an autogenerated PK on TableB, you can use code similar to this. Otherwise, just change the INSERT into TableA to grab the PK from TableB first.

DECLARE @OldData CHAR(10)
SET @OldData = 'Old'
DECLARE @NewData CHAR(10)
SET @NewData = 'New'

CREATE TABLE #TableA 
(
    IDA INT IDENTITY(1,1) PRIMARY KEY,
    IDB INT NOT NULL,
    DataA CHAR(10)
)

CREATE TABLE #TableB 
(
    IDB INT IDENTITY(1,1) PRIMARY KEY,
    DataB CHAR(10)
)

DECLARE @IDsToUpsert TABLE
(
    ID INT
)

-- Add test values for existing rows 
INSERT INTO #TableB
OUTPUT INSERTED.IDB, @OldData
INTO #TableA
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData UNION ALL
SELECT @OldData 

-- Add test values for the rows to upsert
INSERT INTO @IDsToUpsert
SELECT 1 UNION -- exists
SELECT 3 UNION -- exists
SELECT 5 UNION -- does not exist
SELECT 7 UNION -- does not exist
SELECT 9       -- does not exist

-- Data Before
SELECT * From #TableA
SELECT * From #TableB

DECLARE rows_to_update CURSOR
    FOR SELECT ID FROM @IDsToUpsert

DECLARE @rowToUpdate INT
DECLARE @existingIDB INT

OPEN rows_to_update;

FETCH NEXT FROM rows_to_update 
INTO @rowToUpdate;

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRANSACTION

        IF NOT EXISTS 
        (
            SELECT 1 FROM #TableA WITH (UPDLOCK, ROWLOCK, HOLDLOCK)
            WHERE IDA = @rowToUpdate            
        )
        BEGIN
            -- Insert into B, then insert new val into A
            INSERT INTO #TableB
            OUTPUT INSERTED.IDB, INSERTED.DataB 
            INTO #TableA
            SELECT @NewData
            -- Change code here if PK on TableB is not autogenerated
        END
        ELSE
        BEGIN
            -- Update
            UPDATE #TableA
            SET DataA = @NewData
            WHERE IDA = @rowToUpdate
        END

    COMMIT TRANSACTION

    FETCH NEXT FROM rows_to_update 
    INTO @rowToUpdate;
END

CLOSE rows_to_update;
DEALLOCATE rows_to_update;

SELECT * FROM #TableA
SELECT * FROM #TableB

DROP TABLE #TableA
DROP TABLE #TableB
血之狂魔 2024-12-13 01:27:26

要回答您的一般性问题 - “我可以在一个可以嵌套在其他语句中的语句中插入并返回 PK” - 是的,绝对可以。但这取决于你的PK创建背后的逻辑。在这种情况下,似乎生成一个 PK,您需要插入到另一个表中,然后从那里获取新生成的 ID。除非有非常具体的原因,否则这不是很有效(恕我直言)。自动增量、GUID 等往往作为 PK 效果更好。如果您可以简化/更改其背后的逻辑,并且可以找到一种更简单的方法来实现这一点,那么 PK“可以”在一个语句/函数中生成,从而可以在其他语句中使用。

To answer your general question - 'Can I insert and return the PK in one statement that can be nested in other statements' - yes, absolutely. But it depends on the logic behind the creation of your PK. In this case, it seems like to generate a PK, you need to insert into a different table and then grab the newly generated ID from there. This is not very efficient (IMHO) unless there is a very specific reason for doing so. Autoincrements, GUIDs, etc tend to work better as PKs. If you can simplify/change the logic behind this, and you can find a simpler way to accomplish that, so the PK 'CAN' be generated in one statment/function and thus can be used in other statements.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文