У меня была похожая проблема с гораздо большим количеством таблиц. Мы действительно можем избежать создания курсора для каждой строки, которая будет скопирована. Единственный курсор предназначен для зацикливания списка задействованных имен таблиц. Нам также понадобится динамический SQL для этого. Вся операция выполняется очень быстро по сравнению с традиционным решением с циклом курсора.
Хитрость заключается в том, чтобы вставить соответствующие строки в те же таблицы; и затем обновите его столбец FK до его родителя. Как мы можем получить массовую @@ идентичность, используя ключевое слово output во время вставки и сохраняя их во временную таблицу #refTrack. Позже мы присоединяемся к #refTrack с таблицами, используемыми для обновления их FK.
Мы знаем, что:
create table #refTrack
(
tbl sysname,
id int,
refId int
)
insert InvoiceDetail (refId, invoiceNum, sequence, description, price)
output 'InvoiceDetail', inserted.id, inserted.refId into #refTrack
select invoiceNum, invoiceNum, sequence, description, price from InvoiceDetail
where custID = 808 -- denormalized original Bob^s custID
заполнит временную таблицу #refTrack списком вновь созданных автоматических номеров. Наша задача - просто сделать этот запрос вставки динамическим.
Единственным недостатком этого метода является то, что нам нужны согласованности, для каждой таблицы мы должны иметь:
- Свой первичный ключ с именем 'id'. В этом случае нам нужно переименовать: Customer.custID, чтобы стать Customer.id; Invoice.invoiceNum, чтобы стать Invoice.id; и новый столбец «id int identity (1, 1) первичный ключ» в InvoiceDetail.
- Денормализованный столбец custID. Для таблиц, перечисленных с 'глубиной'> 1, для таблицы потребуется текущее приложение внешнего интерфейса для заполнения этого нового вспомогательного столбца. «Триггер вставки» сделает нашу работу немного сложнее.
- Столбец с именем 'refId', определяемый как: int null. Этот столбец предназначен для того, чтобы связать строки, принадлежащие «Bob2», как копию «Bob».
Предпринятые шаги:
а. Вывести все имена таблиц в табличную переменную @tList
declare @tList table
(
tbl sysname primary key,
fkTbl sysname,
fkCol sysname,
depth int
)
insert @tList select 'Customer', null, null, 0
insert @tList select 'Invoice', 'Customer', 'custID', 1
insert @tList select 'InvoiceDetail', 'Invoice', 'invoiceNum', 2
Я бы хотел абстрагироваться от простого заполнения столбца 'tbl' во время вставки выше; и динамически заполнять остальные столбцы, обновляя их результатом рекурсивного CTE представлений information_schema. Однако это может быть не относящимся к делу. Предположим, у нас есть таблица со списком имен таблиц, упорядоченная по способу ее заполнения.
B. Зацикливание таблицы @tList в курсоре.
declare
@depth int,
@tbl sysname,
@fkTbl sysname,
@fkCol sysname,
@exec nvarchar(max),
@insCols nvarchar(max),
@selCols nvarchar(max),
@where nvarchar(max),
@newId int,
@mainTbl sysname,
@custId int
select @custId = 808 -- original Bob^s custID to copy from
select @mainTbl = tbl from @tList where fkTbl is null
declare dbCursor cursor local forward_only read_only for
select tbl, fkTbl, fkCol, depth from @tlist order by depth
open dbCursor
fetch next from dbCursor into @tbl, @fkTbl, @fkCol, @depth
while @@fetch_status = 0
begin
set @where = case when @depth = 0 then 'Id' else 'custId' end + ' = ' +
cast(@custId as nvarchar(20))
set @insCols = dbo.FnGetColumns(@tbl)
set @selCols = replace
(
@insCols,
'refId',
'Id'
)
set @exec = 'insert ' + @tbl + ' (' + @insCols + ') ' +
'output ''' + @tbl + ''', inserted.id, inserted.refId into #refTrack ' +
'select ' + @selCols + ' from ' + @tbl + ' where ' + @where
print @exec
exec(@exec)
-- remap parent
if isnull(@fkTbl, @mainTbl) != @mainTbl -- third level onwards
begin
set @exec = 'update ' + @tbl + ' set ' + @tbl + '.' + @fkCol + ' = rf.Id from ' +
@tbl + ' join #refTrack as rf on ' + @tbl + '.' + @fkCol + ' = rf.refId and rf.tbl = ''' +
@fkTbl + ''' where ' + @tbl + '.custId = ' + cast(@newId as nvarchar(20))
print @exec
exec(@exec)
end
if @depth = 0 select @newId = Id from #refTrack
fetch next from dbCursor into @tbl, @fkTbl, @fkCol, @depth
end
close dbCursor
deallocate dbCursor
select * from @tList order by depth
select * from #refTrack
drop table #refTrack
C. Содержимое FnGetColumns ():
create function FnGetColumns(@tableName sysname)
returns nvarchar(max)
as
begin
declare @cols nvarchar(max)
set @cols = ''
select @cols = @cols + ', ' + column_name
from information_schema.columns
where table_name = @tableName
and column_name <> 'id' -- non PK
return substring(@cols, 3, len(@cols))
end
Я уверен, что мы сможем улучшить эти сценарии, чтобы они стали более динамичными. Но для решения проблемы это было бы минимальным требованием.
Приветствия
Ари.