我有一个表MODELS
,其中有几个ITEMS
可以归属。 ITEMS
表是一个分层表,在PARENT
列上具有自联接。根级别项目在Null
中将为PARENT
。物品可以深入任何层次。
create table MODELS (
MODELID int identity,
MODELNAME nvarchar(200) not null,
constraint PK_MODELS primary key (MODELID)
)
go
create table ITEMS (
ITEMID int identity,
MODELID int not null,
PARENT int null,
ITEMNUM nvarchar(20) not null,
constraint PK_ITEMS primary key (ITEMID)
)
go
alter table ITEMS
add constraint FK_ITEMS_MODEL foreign key (MODELID)
references MODELS (MODELID)
go
alter table ITEMS
add constraint FK_ITEMS_ITEMS foreign key (PARENT)
references ITEMS (ITEMID)
go
我希望创建存储过程以将MODELS
表中的行复制到新行中,并同时复制ITEMS
中的整个结构。
例如,如果我在ITEMS
中有以下内容:
ITEMID MODELID PARENT ITEMNUM
1 1 Null A
2 1 Null B
3 1 Null C
4 1 1 A.A
5 1 2 B.B
6 1 4 A.A.A
7 1 4 A.A.B
8 1 3 C.A
9 1 3 C.B
10 1 9 C.B.A
我想创建新的模型行和10个项目的副本,如下所示:
ITEMID MODELID PARENT ITEMNUM
11 2 Null A
12 2 Null B
13 2 Null C
14 2 11 A.A
15 2 12 B.B
16 2 14 A.A.A
17 2 14 A.A.B
18 2 13 C.A
19 2 13 C.B
20 2 19 C.B.A
我将MODELID
作为参数传递给存储过程。棘手的部分是正确设置PARENT
列。我认为这需要递归完成。
有什么建议吗?
答案 0 :(得分:3)
此处描述的解决方案可在多用户环境中正常运行。您不需要锁定整个表。您不需要禁用自引用外键。你不需要递归。
(ab)将MERGE
与OUTPUT
子句一起使用。
MERGE
可以INSERT
,UPDATE
和DELETE
行。在我们的例子中,我们只需要INSERT
。 1 = 0始终为false,因此始终执行NOT MATCHED BY TARGET
部分。一般来说,可能还有其他分支,请参阅docs。 WHEN MATCHED
通常用于UPDATE
; WHEN NOT MATCHED BY SOURCE
通常用于DELETE
,但我们在这里不需要它们。
这种错综复杂的MERGE
形式相当于简单INSERT
,但与简单INSERT
不同,它的OUTPUT
子句允许引用我们需要的列。它允许从源表和目标表中检索列,从而保存旧ID和新ID之间的映射。
示例数据
DECLARE @Items TABLE (
ITEMID int identity,
MODELID int not null,
PARENT int null,
ITEMNUM nvarchar(20) not null
)
INSERT INTO @Items (MODELID, PARENT, ITEMNUM) VALUES
(1, Null, 'A'),
(1, Null, 'B'),
(1, Null, 'C'),
(1, 1 , 'A.A'),
(1, 2 , 'B.B'),
(1, 4 , 'A.A.A'),
(1, 4 , 'A.A.B'),
(1, 3 , 'C.A'),
(1, 3 , 'C.B'),
(1, 9 , 'C.B.A');
我省略了复制Model
行的代码。最终你将获得原始模型和新模型的ID。
DECLARE @SrcModelID int = 1;
DECLARE @DstModelID int = 2;
声明一个表变量(或临时表)来保存旧项目ID和新项目ID之间的映射。
DECLARE @T TABLE(OldItemID int, NewItemID int);
制作Items
的副本,记住表变量中ID的映射并保留旧的PARENT
值。
MERGE INTO @Items
USING
(
SELECT ITEMID, PARENT, ITEMNUM
FROM @Items AS I
WHERE MODELID = @SrcModelID
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT (MODELID, PARENT, ITEMNUM)
VALUES
(@DstModelID
,Src.PARENT
,Src.ITEMNUM)
OUTPUT Src.ITEMID AS OldItemID, inserted.ITEMID AS NewItemID
INTO @T(OldItemID, NewItemID)
;
使用新ID更新旧的PARENT
值
WITH
CTE
AS
(
SELECT I.ITEMID, I.PARENT, T.NewItemID
FROM
@Items AS I
INNER JOIN @T AS T ON T.OldItemID = I.PARENT
WHERE I.MODELID = @DstModelID
)
UPDATE CTE
SET PARENT = NewItemID
;
检查结果
SELECT * FROM @Items;
答案 1 :(得分:0)
你可以在没有递归的情况下完成。但是你需要先锁定表,以确保它工作正常。
insert into items (Modelid, Parent, ITEMNUM)
select 2 as modelId,
MAP.currId as Parent,
MO.ITEMNUM
from (
( select * from items where MODELID = 1) MO
left join
( select IDENT_CURRENT('ITEMS') + ROW_NUMBER() OVER(ORDER BY itemid ) currID ,
i.ItemID
from ITEMS i
where modelid = 1 ) MAP
on MO.Parent= MAP.ItemID
) ORDER BY MO.ItemID
背后的想法是我们从ITEM表中的原始模型中选择所有行,并为它们生成假ID。
假身份证是:
Row 1 = current identity + 1,
Row 2 = current identity + 2,
etc.
之后我们有了映射:oldid - > NEWID
然后我们将原始模型插入到ITEM表中,但是我们从映射中用记录替换Parent。
我可以看到的问题是,当我们插入行时,Parent的某些ItemID可能仍然不存在(即我们插入的行将具有ItemID 20但其Parent为21)。为此,我们可能需要在执行此插入时禁用Parent的约束。之后我们应该再次启用它。数据当然是正确的。