将子行移动到新的父FK ID?

时间:2010-07-12 02:44:25

标签: sql sql-server parent-child relationship

SQL Server 2008。

我有一个pk id为1的父行。虽然阻塞了所有其他数据库用户(这是一个清理操作,因此资源争用不是问题),我想插入一个新行,然后获取所有子行并将其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' )

5 个答案:

答案 0 :(得分:3)

我倾向于避开代理键,而选择自然键或FK;另外,我会避免使用IDENTITY人工识别码。说实话,我发现自己处于少数,并经常想知道如何使用IDENTITY FK实现批量插入。

根据Alan Barker的回答,你可以利用SCOPE_IDENTITY(),但前提是你要做这个RBAR(通过痛苦排行)。你说,“这是一个清理操作”,所以也许程序解决方案是可以接受的。

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

有几件值得关注的事情。有时,UNIQUE列上的强制IDENTITY约束缺失,因此您可能需要检查自己是否没有碰撞。此外,我发现,当值不是连续的(并且在正范围内!)时,喜欢代理人的那种人可能会有点'慌乱',或者更糟糕的是,有依赖于完美的应用逻辑序列或已将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

答案 1 :(得分:1)

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

UPDATE chi SET parid = 2 WHERE parid = 1

FKeys不应成为问题。

答案 2 :(得分:0)

除了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

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

感谢大家的投入。看来我“不能”使用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