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' )
答案 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