Sei que posso conseguir isso de várias maneiras - essa é uma questão puramente acadêmica para ver se é possível fazer isso em uma única instrução, a fim de aprofundar meus conhecimentos de SQL.
Estamos dividindo uma tabela antiga e larga em duas tabelas mais estreitas, uma pai e uma filha:
create table WorkTable
( ParentId int null
, ChildId int not null
, ParentField1 varchar(50) not null )
create table ParentTable
( Id int not null primary key identity
, Field1 varchar(50) not null )
create table ChildTable
( Id int not null primary key identity
, ParentId int not null foreign key references ParentTable ( Id ) )
WorkTable
contém vários registros da tabela original - o ID dessa tabela que queremos usar como o ID na tabela filho (via identity_insert
) e um campo dessa tabela que realmente queremos definir no pai. Para cada linha em WorkTable
, portanto, teremos 1 linha em ParentTable
e outra em ChildTable
.
Agora vou preencher ParentTable
e também obter os IDs de seus registros recém-inserido para a inserção subsequente em ChildTable
:
declare @IdsMap table
( ParentId int not null
, ChildId int not null )
merge ParentTable dst
using (
select ChildId
, ParentField1
from WorkTable
) src on 1 = 0
when not matched by target then
insert
( Field1 )
values
( src.ParentField1 )
output inserted.Id as [ParentId]
, src.ChildId -- can't do this with a standard INSERT...OUTPUT, hence the use of MERGE
into @IdsMap
;
update wkt
set wkt.ParentId = ids.ParentId
from WorkTable wkt
join
@IdsMap ids on ids.ChildId = wkt.ChildId
Isso funciona, mas é feio. Eu preferiria muito mais se pudesse simplificá-lo em uma instrução, em que os IDs inseridos de ParentTable
são atualizados diretamente de volta Worktable
- removendo assim a necessidade da @IdsMap
tabela var que existe apenas para realizar essa atualização.
Achei que poderia conseguir isso usando o merge
DML aninhado:
update wkt
set wkt.ParentId = cte.ParentId
from WorkTable wkt
join
(
merge ParentTable dst
using (
select ChildId
, ParentField1
from WorkTable
) src on 1 = 0
when not matched by target then
insert
( Field1 )
values
( src.ParentField1 )
output inserted.Id as [ParentId]
, src.ChildId
) cte on cte.ChildId = wkt.ChildId
mas o MSSQL diz que não:
A nested INSERT, UPDATE, DELETE, or MERGE statement is not
allowed on either side of a JOIN or APPLY operator.
O DML aninhado dentro de um CTE também falha:
;with cte as
(
select *
from
(
merge ParentTable dst
using (
select ChildId
, ParentField1
from WorkTable
) src on 1 = 0
when not matched by target then
insert
( Field1 )
values
( src.ParentField1 )
output inserted.Id as [ParentId]
, src.ChildId
) _
)
update wkt
set wkt.ParentId = cte.ParentId
from WorkTable wkt
join
cte on cte.ChildId = wkt.ChildId
A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed in a SELECT
statement that is not the immediate source of rows for an INSERT statement.
Existe alguma maneira de conseguir o que estou procurando?
Não com um relacionamento de chave estrangeira verificado, não. O DML aninhado não funciona quando a tabela está em um dos lados de um relacionamento FK imposto. Observe também que apenas um exterior
INSERT
é permitido.Não verificar temporariamente o FK quase funcionaria, mas a identidade na tabela filho apresenta outro obstáculo. A inserção de DML aninhada não é permitida para usar a inserção de identidade.
Mesmo desabilitar e restabelecer a chave estrangeira frustraria seu desejo de implementar a operação em uma única instrução. Dada essa exigência, acho que a resposta à sua pergunta é não.
Exemplo de quase funcionando (sem a identidade da criança):
Solução:
O SQL dinâmico é necessário para evitar um erro devido à restrição imposta no momento da compilação em lote.
Pai:
Criança:
Quando a tabela filho tem uma coluna de identidade, o erro é:
Por que você quer que seja uma consulta complicada?
A tarefa que você descreve pode ser facilmente resolvida por um loop simples:
E claro, dessa forma você pode fazer quantas manipulações e conversões de dados forem necessárias, entre a leitura da tabela de origem e a gravação nos destinos.
Além disso, você terá fácil leitura e modificação do script.