Estou tentando usar uma MERGE
instrução para inserir ou excluir linhas de uma tabela, mas só quero atuar em um subconjunto dessas linhas. A documentação para MERGE
possui um aviso muito fortemente redigido:
É importante especificar apenas as colunas da tabela de destino que são usadas para fins de correspondência. Ou seja, especifique colunas da tabela de destino comparadas à coluna correspondente da tabela de origem. Não tente melhorar o desempenho da consulta filtrando as linhas na tabela de destino na cláusula ON, como especificando AND NOT target_table.column_x = value. Fazer isso pode retornar resultados inesperados e incorretos.
mas é exatamente isso que parece que tenho que fazer para fazer meu MERGE
trabalho.
Os dados que tenho são uma tabela de junção muitos-para-muitos padrão de itens para categorias (por exemplo, quais itens estão incluídos em quais categorias), assim:
CategoryId ItemId
========== ======
1 1
1 2
1 3
2 1
2 3
3 5
3 6
4 5
O que preciso fazer é substituir efetivamente todas as linhas de uma categoria específica por uma nova lista de itens. Minha tentativa inicial de fazer isso é assim:
MERGE INTO CategoryItem AS TARGET
USING (
SELECT ItemId FROM SomeExternalDataSource WHERE CategoryId = 2
) AS SOURCE
ON SOURCE.ItemId = TARGET.ItemId AND TARGET.CategoryId = 2
WHEN NOT MATCHED BY TARGET THEN
INSERT ( CategoryId, ItemId )
VALUES ( 2, ItemId )
WHEN NOT MATCHED BY SOURCE AND TARGET.CategoryId = 2 THEN
DELETE ;
Isso parece estar funcionando nos meus testes, mas estou fazendo exatamente o que o MSDN me adverte explicitamente para não fazer. Isso me preocupa com o fato de ter problemas inesperados mais tarde, mas não vejo outra maneira de fazer com que minhas MERGE
únicas linhas afetem o valor do campo específico ( CategoryId = 2
) e ignore as linhas de outras categorias.
Existe uma maneira "mais correta" de alcançar esse mesmo resultado? E quais são os "resultados inesperados ou incorretos" sobre os quais o MSDN está me alertando?
fonte
Respostas:
A
MERGE
instrução tem uma sintaxe complexa e uma implementação ainda mais complexa, mas essencialmente a ideia é unir duas tabelas, filtrar para linhas que precisam ser alteradas (inseridas, atualizadas ou excluídas) e, em seguida, executar as alterações solicitadas. Dados os seguintes dados de amostra:Alvo
Fonte
O resultado desejado é substituir os dados no destino por dados da origem, mas apenas para
CategoryId = 2
. Seguindo a descriçãoMERGE
fornecida acima, devemos escrever uma consulta que une a origem e o destino apenas nas chaves e filtrar as linhas apenas nasWHEN
cláusulas:Isso fornece os seguintes resultados:
O plano de execução é:
Observe que as duas tabelas são verificadas completamente. Podemos achar isso ineficiente, porque apenas as linhas
CategoryId = 2
serão afetadas na tabela de destino. É aqui que entram os avisos no Books Online. Uma tentativa equivocada de otimizar para tocar apenas as linhas necessárias no destino é:A lógica na
ON
cláusula é aplicada como parte da junção. Nesse caso, a associação é uma associação externa completa (consulte esta entrada do Books Online para saber o porquê). A aplicação da verificação da categoria 2 nas linhas de destino como parte de uma junção externa resulta em linhas com um valor diferente sendo excluído (porque elas não correspondem à origem):A causa raiz é o mesmo motivo pelo qual os predicados se comportam de maneira diferente em uma
ON
cláusula de junção externa do que se especificado naWHERE
cláusula. AMERGE
sintaxe (e a implementação da junção, dependendo das cláusulas especificadas) apenas tornam mais difícil perceber que isso é verdade.As orientações nos Manuais Online (expandidas na entrada Otimizando desempenho ) oferecem orientações que garantirão que a semântica correta seja expressa usando a
MERGE
sintaxe, sem que o usuário precise necessariamente entender todos os detalhes da implementação ou que explique as maneiras pelas quais o otimizador pode legitimamente reorganizar coisas por razões de eficiência de execução.A documentação oferece três maneiras possíveis de implementar a filtragem antecipada:
A especificação de uma condição de filtragem na
WHEN
cláusula garante resultados corretos, mas pode significar que mais linhas são lidas e processadas a partir das tabelas de origem e destino do que o estritamente necessário (como visto no primeiro exemplo).A atualização através de uma visualização que contém a condição de filtragem também garante resultados corretos (já que as linhas alteradas devem estar acessíveis para atualização através da visualização), mas isso requer uma visualização dedicada e uma que siga as condições ímpares para atualizar as visualizações.
O uso de uma expressão de tabela comum acarreta riscos semelhantes à adição de predicados à
ON
cláusula, mas por razões ligeiramente diferentes. Em muitos casos, será seguro, mas requer análise especializada do plano de execução para confirmar isso (e testes práticos extensivos). Por exemplo:Isso produz resultados corretos (não repetidos) com um plano mais ideal:
O plano lê apenas linhas da categoria 2 da tabela de destino. Isso pode ser uma consideração importante de desempenho se a tabela de destino for grande, mas é muito fácil cometer erros usando a
MERGE
sintaxe.Às vezes, é mais fácil gravar as
MERGE
operações DML separadas. Essa abordagem pode até ter um desempenho melhor que um únicoMERGE
, fato que muitas vezes surpreende as pessoas.fonte