2010-08-31 2 views
4

Я создаю хранимую процедуру SQL 2008 R2 для дублирования строки и всех ее дочерних элементов.TSQL Parent> Child> Дублирование суб-ребенка без курсора

Это трехуровневая настройка с родителем, дочерним элементом и дочерним элементом Учитывая идентификатор родителя, мне нужно создать дубликат.

Я решил это, используя fast_forwardcursor.

Я знаю, что я также могу сделать это с циклом while через строки, но я не верю, что это будет быстрее, чем этот метод курсора. Что ты думаешь?

Есть ли лучший способ выполнить эту задачу без использования курсоров?

EDIT: Еще один вариант, который я рассматривал, заключался в создании временной таблицы, содержащей старые/новые PKID из записей TBLACStages.

TBLACStages может быть от 1 до 20 соответствующих строк (и TBLACUpgrade вероятно, будет иметь 3 строк на TBLACStages подряд)

CREATE PROCEDURE [dbo].[spDuplicateACUnit] 
@pACUnitID bigint = 0 
AS BEGIN 
SET NOCOUNT ON; 

DECLARE @NewACUnitID bigint = 0 

INSERT INTO TBLACUnits ([col1] ,[col2] ,[...] ,[coln]) SELECT [col1] ,[col2] ,[...] ,[coln] FROM TBLACUnits WHERE ACUnitID = @pACUnitID 

SELECT @NewACUnitID = SCOPE_IDENTITY() 

DECLARE @ACStageID bigint = 0 
    DECLARE @NewACStageID bigint = 0 

DECLARE @ACUnitCursor CURSOR 

SET @ACUnitCursor = CURSOR LOCAL FAST_FORWARD FOR SELECT ACStageID FROM TBLACStages WHERE TBLACStages.ACUnitID = @pACUnitID 

OPEN @ACUnitCursor 

FETCH NEXT FROM @ACUnitCursor INTO @ACStageID 

WHILE @@FETCH_STATUS = 0 
BEGIN 

INSERT INTO TBLACStages ([ACUnitID] ,[col1] ,[col2] ,[...] ,[coln]) SELECT @NewACUnitID ,[col1] ,[col2] ,[...] ,[coln] FROM TBLACStages WHERE TBLACStages.ACStageID = @ACStageID 

SELECT @NewACStageID = SCOPE_IDENTITY() 

INSERT INTO TBLACUpgrade ([ACStageID] ,[col1] ,[col2] ,[...] ,[coln]) SELECT @NewACStageID ,[col1] ,[col2] ,[...] ,[coln] FROM TBLACUpgrade WHERE TBLACUpgrade.[ACStageID] = @ACStageID 

FETCH NEXT FROM @ACUnitCursor INTO @ACStageID 
END 

CLOSE @ACUnitCursor DEALLOCATE @ACUnitCursor 

END 

GO 
+0

Извините, что форматирование выглядит плохо, копирование/вставка в SO не так-то просто. – Matthew

+0

У вас есть ключи-кандидаты (уникальный столбец) в TBLACStages, кроме ACStageID? –

+0

Нет, единственный гарантированный уникальный столбец - это ПК, который является ACStageID – Matthew

ответ

6

Это должно дать вам идею:

CREATE TABLE t_parent (id INT NOT NULL PRIMARY KEY IDENTITY, value VARCHAR(100)) 
CREATE TABLE t_child (id INT NOT NULL PRIMARY KEY IDENTITY, parent INT NOT NULL, value VARCHAR(100)) 
CREATE TABLE t_grandchild (id INT NOT NULL PRIMARY KEY IDENTITY, child INT NOT NULL, value VARCHAR(100)) 

INSERT 
INTO t_parent (value) 
VALUES ('Parent 1') 

INSERT 
INTO t_parent (value) 
VALUES ('Parent 2') 

INSERT 
INTO t_child (parent, value) 
VALUES (1, 'Child 2') 

INSERT 
INTO t_child (parent, value) 
VALUES (2, 'Child 2') 

INSERT 
INTO t_grandchild (child, value) 
VALUES (1, 'Grandchild 1') 

INSERT 
INTO t_grandchild (child, value) 
VALUES (1, 'Grandchild 2') 

INSERT 
INTO t_grandchild (child, value) 
VALUES (2, 'Grandchild 3') 

DECLARE @parent TABLE (oid INT, nid INT) 
DECLARE @child TABLE (oid INT, nid INT) 

MERGE 
INTO t_parent 
USING (
     SELECT id, value 
     FROM t_parent 
     ) p 
ON  1 = 0 
WHEN NOT MATCHED THEN 
INSERT (value) 
VALUES (value) 
OUTPUT p.id, INSERTED.id 
INTO @parent; 
SELECT * 
FROM @parent 
MERGE 
INTO t_child 
USING (
     SELECT c.id, p.nid, c.value 
     FROM @parent p 
     JOIN t_child c 
     ON  c.parent = p.oid 
     ) c 
ON  1 = 0 
WHEN NOT MATCHED THEN 
INSERT (parent, value) 
VALUES (nid, value) 
OUTPUT c.id, INSERTED.id 
INTO @child; 
SELECT * 
FROM @child; 
INSERT 
INTO t_grandchild (child, value) 
SELECT c.nid, gc.value 
FROM @child c 
JOIN t_grandchild gc 
ON  gc.child = c.oid 
SELECT * 
FROM t_grandchild 
+0

Считаете ли вы, что три вложенных оператора 'MERGE' будут быстрее, чем' CURSOR' на максимум 20 строк? Эпилептически теперь заданы накладные расходы на две временные таблицы для чтения (родительский и детский), а не один в моем решении (ребенок)? – Matthew

+1

@Matthew: Операторы 'MERGE' на самом деле являются просто' INSERTS' ('INSERT' не позволяет вернуть старый' id', поэтому вы должны использовать 'MERGE'). – Quassnoi

+1

@Matthew: вы можете повторно использовать временную таблицу в моем решении. Один запрос к таблице «20'-записей будет определенно быстрее, чем« 20 »запросов к отдельным записям. Вы всегда должны использовать решения на основе набора, если это возможно. – Quassnoi

0

Чтобы увеличить скорость вашего SP вы можете добавить еще одно заявление FOR READ ONLY

Так что ваш SP будет так:

... 

SET @ACUnitCursor = CURSOR LOCAL FAST_FORWARD FOR 

SELECT ACStageID FROM TBLACStages WHERE TBLACStages.ACUnitID = @pACUnitID 

FOR READ ONLY -- add this to increase the speed 

OPEN @ACUnitCursor 

FETCH NEXT FROM @ACUnitCursor INTO @ACStageID 

... 
+0

Может быть, я не понимаю вашего ответа ... 'FAST_FORWARD' по определению доступен только для чтения – Matthew

+0

Вы правы, я печатаю слишком быстро, и я не заметил, что вы установили курсор как' FAST_FORWARD'. Btw я предлагаю вместо этого вставлять ваши записи в TBLACStages, TBLACUpgrade, ... напрямую, вставлять их в «временную таблицу как @ TBLACStages», а затем, когда цикл закончен, только с 1 SELECT INTO вы можете поместить все записи из @TBLACStages> TBLACStages одним выстрелом.Это значительно сокращает работу SQL Server, поскольку TEMP TABLE хранятся в памяти и не записываются на диск. Я использовал этот tecnique, как в вашем случае. Это всего лишь подсказка. –

1

Хорошо, это MERGE Я пришел на основе решения Кваснуи. Я должен работать надлежащим образом без CURSOR

DECLARE @parent TABLE (oid BIGINT, nid BIGINT) 
DECLARE @child TABLE (oid BIGINT, nid BIGINT) 

MERGE 
INTO TBLACUnits T 
USING (SELECT [col1], [...], [coln] FROM TBLACUnits WHERE ID = @pID) S 

ON  1 = 0 
WHEN NOT MATCHED THEN 
INSERT ([ACUnitID] 
    ,[col1] 
    ,[...] 
    ,[coln]) 
VALUES (S.[ACUnitID] 
    ,S.[col1] 
    ,S.[...] 
    ,S.[coln]]) 
OUTPUT S.ACUnitID, INSERTED.ACUnitID 
INTO @parent; 

MERGE 
INTO TBLACStages T 
USING (
    SELECT tt.[nid] 
         ,TBLACStages.[col1] 
         ,TBLACStages.[...] 
         ,TBLACStages.[coln] 
    FROM TBLACStages 
    JOIN @parent tt ON tt.oid = TBLACStages.ACUnitID 
) S 
ON  1 = 0 
WHEN NOT MATCHED THEN 
INSERT ([ACUnitID] 
    ,[col1] 
    ,[...] 
    ,[coln]) 
VALUES ([nid] 
    ,[col1] 
    ,[...] 
    ,[coln]) 
OUTPUT S.[ACStageID], INSERTED.[ACStageID] 
INTO @child; 

INSERT INTO TBLACUpgrade 
([ACStageID] 
    ,[col1] 
    ,[...] 
    ,[coln]) 
SELECT c.[nid] 
    ,TBLACUpgrade.[col1] 
    ,TBLACUpgrade.[...] 
    ,TBLACUpgrade.[coln] 
FROM @child c 
JOIN TBLACUpgrade 
ON  TBLACUpgrade.ACStageID = c.oid 
1

Я видел этот пост я чуть не задохнулась от сложности, но уверен, что она хорошо выглядит. Когда мне нужно клонировать или копировать таблицы с детьми или внуками, я просто добавляю новый столбец в таблицу с именем PreCloneControl, а затем ссылаюсь на это поле в дочернем запросе новой таблицы, чтобы быстро и легко найти старые родительские данные. Легко. Но если у вас нет доступа к добавлению столбцов в таблицу, часто может использоваться быстрый хак. Одним из примеров является поле Last Modified User, часто nvarchar из 100 символов. Обычно нам нужно обновлять это поле в любом случае, поэтому отбросьте свой старый контрольный номер туда и обратно. Не забудьте выполнить быстрое обновление в поле Last Modified User, когда вы закончите. Вот пример, я использую таблицы Temp для тестирования, но вы должны использовать реальные таблицы.

Declare @OldControl int = 123456 

Declare @TT1 Table 
(
TT1Control [int] IDENTITY(1,1) NOT NULL, 
SomeData nvarchar(20) 
) 

insert into @TT1 
(
SomeData 
) 
Select SomeDate from LiveTable where LTControl = @OldControl 


Declare @NewControl int = SCOPE_IDENTITY() 


Declare @TempTT2 Table 
(
TT2Control int IDENTITY(1,1) NOT NULL, 
TT2TT1FKControl int, 
TT2ChildData nvarchar(20), 
TT2ModUser nvarchar(100) 
) 

insert into @TempTT2 
(
TT2TT1FKControl,TT2ChildData,TT2ModUser 
) 
Select @NewControl, TT2ChildData, Cast(TT2Control as nvarchar(100)) 
From TT2 where TT2TT1FKControl = @OldControl 

Select * from @TempTT2 

Declare @TT3 Table 
(
TT3Control int IDENTITY(1,1) NOT NULL, 
TT3TT2FKControl int, 
TT3GrandChildData nvarchar(50), 
TT3OldTT2Control int 
) 

Insert Into @TT3 
(
TT3TT2FKControl,TT3GrandChildData,TT3OldTT2Control 
) 
Select t.TT2Control, BookItemItemNumber,TT2.TT2Control 
From TT2 inner join GrandChildTable on TT2Control = GCTFKControl 
     ,@TempTT2 as t 
Where 
TT2TT1FKControl = @OldControl 
and t.TT2ModUser = Cast(TT2Control as nvarchar(100)) 

Select * From @TT3 

Update @TempTT2 set TT2ModUser = 'UserName' Where TT2TT1FKControl = @NewControl 

Select * from @TempTT2