将子行移至新的父 FK ID?

发布于 2024-09-09 04:44:33 字数 546 浏览 1 评论 0原文

SQL Server 2008。

我有一个 pk id 1 的父行。在阻止所有其他数据库用户时(这是一个清理操作,因此资源争用不是问题),我想插入一个新行,然后获取所有子行rows 并将其 fk 列更改为新行。以下面的 DDL 为例,我想插入一个新行并为所有 #chi.parid 值赋予值“3”,这样它们现在基本上属于新行,以便可以删除旧行。

帮助!

create table #par ( parid int identity(1,1) , note varchar(8) )
create table #chi ( chiid int identity(1,1) , parid int , thing varchar(8) )
insert into #par values ( 'note1' )
insert into #par values ( 'note2' )
insert into #chi values ( 1 , 'a' )
insert into #chi values ( 1 , 'b' )
insert into #chi values ( 1 , 'c' )

SQL Server 2008.

I have a parent row with pk id 1. While blocking all other DB users (this is a cleanup operation so resource contention is not an issue), I would like to insert a new row, then take all of the child rows and change their fk column to the new row. With the below DDL for example, I would like to insert a new row and give all of the #chi.parid values a value of '3' so they would essentially now belong to the new row so the old one can be deleted.

Help!

create table #par ( parid int identity(1,1) , note varchar(8) )
create table #chi ( chiid int identity(1,1) , parid int , thing varchar(8) )
insert into #par values ( 'note1' )
insert into #par values ( 'note2' )
insert into #chi values ( 1 , 'a' )
insert into #chi values ( 1 , 'b' )
insert into #chi values ( 1 , 'c' )

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

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

发布评论

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

评论(5

笑叹一世浮沉 2024-09-16 04:44:33

我倾向于避免使用代理键,而倾向于使用自然键或 FK;另外,我会避免使用 IDENTITY 作为人工标识符。老实说,我发现自己属于少数派,并且经常想知道如何使用 IDENTITY FK 实现批量插入。

根据 Alan Barker 的回答,您可以使用 SCOPE_IDENTITY(),但前提是您想要执行此 RBAR(逐行执行)。你说,“这是一个清理操作”,所以也许程序解决方案是可以接受的。

我自己解决这个问题的方法是手动生成一系列潜在的 IDENTITY 值(例如在临时表中),然后使用 SET IDENTITY_INSERT TargetTable ON 强制显然,我需要确保在发生 INSERT 时建议的值实际上不会被使用,因此仍然需要阻止所有其他用户。

有几件事值得关注。有时,IDENTITY 列上的强制性 UNIQUE 约束缺失,因此您可能需要自己检查是否存在冲突。另外,我发现,当值不连续(并且在正范围内!)时,喜欢代理的人可能会有点“慌乱”,或者更糟糕的是,存在依赖于完美的应用程序逻辑序列或已将 IDENTITY 值暴露给业务(在这种情况下,“伪造”企业关键值(例如订单号)可能会落入现实生活中的审计员手中)。

编辑:今天早上阅读另一个SO问题的答案让我想起了SQL Server 2008的OUTPUT子句来捕获表中所有自动生成的IDENTITY值,例如

CREATE TABLE #InsertedBooks
(
 ID INTEGER NOT NULL UNIQUE, -- surrogate
 isbn_13 CHAR(13) NOT NULL UNIQUE -- natural key
);

WITH InsertingBooks (isbn_13)
AS 
(
 SELECT '9781590597453'
 UNION ALL
 SELECT '9780596523060'
 UNION ALL
 SELECT '9780192801425'
)
INSERT INTO Books (isbn_13)
  OUTPUT inserted.ID, inserted.isbn_13   -- <--
  INTO #InsertedBooks (ID, isbn_13)      -- <--
SELECT isbn_13
  FROM InsertingBooks;

INSERT INTO AnotherTable...
SELECT T1.ID, ...
  FROM #InsertedBooks AS T1...;

DROP TABLE #InsertedBooks

I tend to shun surrogate keys in favour of natural keys or FKs; also, I would avoid IDENTITY for artificial identifiers. To be honest, I find myself in the minority and have often wondered myself how to achieve bulk inserts with IDENTITY FKs.

As per Alan Barker's answer, you can utilize SCOPE_IDENTITY() but only if you want to do this RBAR (row by agonizing row). You say, "this is a cleanup operation" so perhaps a procedural solution is acceptable.

The way I've got around the problem myself is to manually generate a sequence of potential IDENTITY values (e.g. in a staging table) then use SET IDENTITY_INSERT TargetTable ON to force the values in. Obviously, I need to ensure the proposed values will not actually be in use by the time the INSERT occurs so all other users will still need to be blocked.

A couple of things to watch. Sometimes the obligatory UNIQUE constraint on the IDENTITY column is missing so you may need to check there are no collisions yourself. Also, I've found that the kind of person who likes surrogates can get a bit 'flustered' when the values aren't sequential (and in the positive range!) or, much worse, there is application logic that relies on a perfect sequence or has exposed the IDENTITY values to the business (in which case 'faking' enterprise key values such as order numbers can fall fowl of real life auditors).

EDIT: reading an answer to another SO question this morning reminded me about SQL Server 2008's OUTPUT clause to capture all the auto-generated IDENTITY values in a table e.g.

CREATE TABLE #InsertedBooks
(
 ID INTEGER NOT NULL UNIQUE, -- surrogate
 isbn_13 CHAR(13) NOT NULL UNIQUE -- natural key
);

WITH InsertingBooks (isbn_13)
AS 
(
 SELECT '9781590597453'
 UNION ALL
 SELECT '9780596523060'
 UNION ALL
 SELECT '9780192801425'
)
INSERT INTO Books (isbn_13)
  OUTPUT inserted.ID, inserted.isbn_13   -- <--
  INTO #InsertedBooks (ID, isbn_13)      -- <--
SELECT isbn_13
  FROM InsertingBooks;

INSERT INTO AnotherTable...
SELECT T1.ID, ...
  FROM #InsertedBooks AS T1...;

DROP TABLE #InsertedBooks
物价感观 2024-09-16 04:44:33

我认为您只是想要更新,例如:

UPDATE chi SET parid = 2 WHERE parid = 1

FKeys 不应该成为这里的问题。

I think you just want an update, such as :

UPDATE chi SET parid = 2 WHERE parid = 1

FKeys shouldn't be an issue here.

烦人精 2024-09-16 04:44:33

除了 Kalium 的解决方案之外,您可以使用 SCOPE_IDENTITY() 函数来检索最后一个表插入的 IDENTITY 值。

begin tran    

   insert into #par values ('New Parent')  

   update #chi set parid= SCOPE_IDENTITY()

   delete from #par where parid = <OLD_ID>    

commit

这样您就可以将其编码为存储过程来完成整个事情:

CREATE PROCEDURE CleanUp @newNote varchar(8), @IDToDelete int 
AS
BEGIN

    BEGIN TRAN

    INSERT INTO #par VALUES (@newNote)      
    UPDATE #chi SET parid= SCOPE_IDENTITY()    
    DELETE FROM #par WHERE parid = @IDToDelete

    COMMIT
END

然后简单地:

exec CleanUp 'Alan',1

Further to Kalium's solution, you could use the SCOPE_IDENTITY() function to retrieve the IDENTITY value of the last table insert.

begin tran    

   insert into #par values ('New Parent')  

   update #chi set parid= SCOPE_IDENTITY()

   delete from #par where parid = <OLD_ID>    

commit

This way you could code this as a Stored Procedure to do the whole thing:

CREATE PROCEDURE CleanUp @newNote varchar(8), @IDToDelete int 
AS
BEGIN

    BEGIN TRAN

    INSERT INTO #par VALUES (@newNote)      
    UPDATE #chi SET parid= SCOPE_IDENTITY()    
    DELETE FROM #par WHERE parid = @IDToDelete

    COMMIT
END

And then simply:

exec CleanUp 'Alan',1
锦爱 2024-09-16 04:44:33

那么在这种情况下你可以简单地使用游标。性能不是最好的,但看起来这是一项停机清理工作:

CREATE PROCEDURE CleanUp
AS
BEGIN

    -- BUILD YOUR TEMP TABLE(S) HERE:
    --
    --

    DECLARE @delete_parent_id int;

    -- SELECT ON TEMP TABLE (AS A CURSOR):
    -- Put your specific Select statement here:
    DECLARE delete_cursor CURSOR FOR 
    SELECT parid
    FROM #TEMPTABLE
    WHERE <...> ;


    OPEN delete_cursor;

    BEGIN TRAN
        -- Loop round each selected parent, create new parent, update children and delete old parent.
        FETCH NEXT FROM delete_cursor 
        INTO @delete_parent_id;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            INSERT INTO #par VALUES ('Some new Text') --New Parent Row     
            UPDATE #chi SET parid= SCOPE_IDENTITY() where parid = @delete_parent_id   -- Adjust FK ref on child
            DELETE FROM #par WHERE parid = @delete_parent_id -- delete old parent.

            FETCH NEXT FROM delete_cursor 
            INTO @delete_parent_id;
        END

    COMMIT

    CLOSE delete_cursor;
    DEALLOCATE delete_cursor;
END

Well in that case you could simply use a Cursor. Not the best for performance but looks like this is a downtime-clean up job anyway:

CREATE PROCEDURE CleanUp
AS
BEGIN

    -- BUILD YOUR TEMP TABLE(S) HERE:
    --
    --

    DECLARE @delete_parent_id int;

    -- SELECT ON TEMP TABLE (AS A CURSOR):
    -- Put your specific Select statement here:
    DECLARE delete_cursor CURSOR FOR 
    SELECT parid
    FROM #TEMPTABLE
    WHERE <...> ;


    OPEN delete_cursor;

    BEGIN TRAN
        -- Loop round each selected parent, create new parent, update children and delete old parent.
        FETCH NEXT FROM delete_cursor 
        INTO @delete_parent_id;

        WHILE @@FETCH_STATUS = 0
        BEGIN

            INSERT INTO #par VALUES ('Some new Text') --New Parent Row     
            UPDATE #chi SET parid= SCOPE_IDENTITY() where parid = @delete_parent_id   -- Adjust FK ref on child
            DELETE FROM #par WHERE parid = @delete_parent_id -- delete old parent.

            FETCH NEXT FROM delete_cursor 
            INTO @delete_parent_id;
        END

    COMMIT

    CLOSE delete_cursor;
    DEALLOCATE delete_cursor;
END
葬﹪忆之殇 2024-09-16 04:44:33

感谢大家的意见。看来我“不能”使用 SQL Svr 2008 对此进行基于集合的操作,因此我使用循环进行了 RBAR 解决方案(我认为它的性能比游标更好)。任何可以评论如何使用 try..catch 使其更安全或启发我更多如何在集合中执行此操作的人,请发表评论。 :)

谢谢。

    Select 
                [parid]
            ,   [name]
    Into    #redo
    From    partable 
    Where   DateDiff( Hour , donewhen ,SysDateTimeOffset() ) > 23
Begin Transaction
Declare @rows int  = ( Select COUNT(*) From #redo )
Declare @parid int 
Create Clustered Index redoix on #redo([parid]) With FillFactor = 100
While @rows > 0
Begin
    Select Top 1 @parid = [parid] from #redo Order By parid Asc

        Insert partable 
            (
                [name]
            )
        Select 
                [name]      
        From #redo 
        Where parid = @parid
        Update chitable
            Set parid = Scope_Identity()
            Where   parid = @parid
        Delete From partable
            Where   parid = @parid

    Delete from #redo where [parid] = @parid 
    Set @rows  = ( Select COUNT(*) From #redo )
End
Commit Transaction

Thanks all for the input. It appears I "can't" do a set-based operation on this with SQL Svr 2008, so I did RBAR solution with a loop (I think it performs better than a cursor). Anyone who can comment on making this safer with try..catch or enlighten me more on doing this in a set, please comment. :)

Thanks.

    Select 
                [parid]
            ,   [name]
    Into    #redo
    From    partable 
    Where   DateDiff( Hour , donewhen ,SysDateTimeOffset() ) > 23
Begin Transaction
Declare @rows int  = ( Select COUNT(*) From #redo )
Declare @parid int 
Create Clustered Index redoix on #redo([parid]) With FillFactor = 100
While @rows > 0
Begin
    Select Top 1 @parid = [parid] from #redo Order By parid Asc

        Insert partable 
            (
                [name]
            )
        Select 
                [name]      
        From #redo 
        Where parid = @parid
        Update chitable
            Set parid = Scope_Identity()
            Where   parid = @parid
        Delete From partable
            Where   parid = @parid

    Delete from #redo where [parid] = @parid 
    Set @rows  = ( Select COUNT(*) From #redo )
End
Commit Transaction
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文