Chegando ao SQL de outras linguagens de programação, a estrutura de uma consulta recursiva parece bastante estranha. Ande por ela passo a passo, e parece desmoronar.
Considere o seguinte exemplo simples:
CREATE TABLE #NUMS
(N BIGINT);
INSERT INTO #NUMS
VALUES (3), (5), (7);
WITH R AS
(
SELECT N FROM #NUMS
UNION ALL
SELECT N*N AS N FROM R WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Vamos passar por isso.
Primeiro, o membro âncora é executado e o conjunto de resultados é colocado em R. Portanto, R é inicializado em {3, 5, 7}.
Em seguida, a execução cai abaixo do UNION ALL e o membro recursivo é executado pela primeira vez. Ele é executado em R (ou seja, no R que atualmente temos em mãos: {3, 5, 7}). Isso resulta em {9, 25, 49}.
O que isso faz com esse novo resultado? Ele anexa {9, 25, 49} ao {3, 5, 7} existente, rotula a união resultante R e continua com a recursão a partir daí? Ou redefine R para ser apenas esse novo resultado {9, 25, 49} e faz toda a união depois?
Nenhuma escolha faz sentido.
Se R agora for {3, 5, 7, 9, 25, 49} e executarmos a próxima iteração da recursão, terminaremos com {9, 25, 49, 81, 625, 2401} e teremos perdeu {3, 5, 7}.
Se R agora é apenas {9, 25, 49}, temos um problema de identificação incorreta. R é entendido como a união do conjunto de resultados do membro âncora e de todos os conjuntos de resultados do membro recursivo subsequentes. Enquanto {9, 25, 49} é apenas um componente de R. Não é o R completo que acumulamos até agora. Portanto, escrever o membro recursivo como selecionar R não faz sentido.
Eu certamente aprecio o que @Max Vernon e @Michael S. detalharam abaixo. Ou seja, que (1) todos os componentes são criados até o limite de recursão ou conjunto nulo e (2) todos os componentes são unidos. É assim que eu entendo a recursão do SQL para realmente funcionar.
Se estivéssemos redesenhando o SQL, talvez aplicássemos uma sintaxe mais clara e explícita, algo como isto:
WITH R AS
(
SELECT N
INTO R[0]
FROM #NUMS
UNION ALL
SELECT N*N AS N
INTO R[K+1]
FROM R[K]
WHERE N*N < 10000000
)
SELECT N FROM R ORDER BY N;
Mais ou menos como uma prova indutiva em matemática.
O problema com a recursão do SQL como está atualmente é que ele é escrito de uma maneira confusa. A maneira como está escrito diz que cada componente é formado pela seleção de R, mas isso não significa que o R completo que foi (ou, parece ter sido) construído até agora. Significa apenas o componente anterior.
fonte
Respostas:
A descrição da BOL de CTEs recursivas descreve a semântica da execução recursiva da seguinte maneira:
Portanto, cada nível tem apenas como entrada o nível acima, e não todo o conjunto de resultados acumulado até o momento.
O acima é como funciona logicamente . Atualmente, as CTEs fisicamente recursivas são sempre implementadas com loops aninhados e um spool de pilha no SQL Server. Isso é descrito aqui e aqui e significa que, na prática, cada elemento recursivo está apenas trabalhando com a linha pai do nível anterior, não com o nível inteiro. Mas as várias restrições à sintaxe permitida em CTEs recursivas significam que essa abordagem funciona.
Se você remover o
ORDER BY
da sua consulta, os resultados serão ordenados da seguinte maneiraIsso ocorre porque o plano de execução opera de maneira muito semelhante à seguinte
C#
NB1: Como acima pelo tempo que o primeiro filho do elemento de fixação
3
está sendo processado todas as informações sobre seus irmãos,5
e7
, e seus descendentes, já foi descartada da bobina e não está mais acessível.NB2: O C # acima possui a mesma semântica geral que o plano de execução, mas o fluxo no plano de execução não é idêntico, pois os operadores trabalham de maneira de exceção em pipeline. Este é um exemplo simplificado para demonstrar a essência da abordagem. Veja os links anteriores para obter mais detalhes sobre o próprio plano.
NB3: O próprio spool de pilha é aparentemente implementado como um índice clusterizado não exclusivo, com a coluna-chave do nível de recursão e os exclusivos adicionados conforme necessário ( origem )
fonte
IterateToDepthFirst
-Iterate(seed,rcsv)->PhysIterate(seed,rcsv)
. Apenas para sua informação. Excelente resposta.Este é apenas um palpite (semi) educado e provavelmente está completamente errado. Pergunta interessante, a propósito.
T-SQL é uma linguagem declarativa; talvez um CTE recursivo seja convertido em uma operação no estilo de cursor, em que os resultados do lado esquerdo do UNION ALL sejam anexados a uma tabela temporária; o lado direito do UNION ALL será aplicado aos valores no lado esquerdo.
Então, primeiro inserimos a saída do lado esquerdo do UNION ALL no conjunto de resultados, depois inserimos os resultados do lado direito do UNION ALL aplicados ao lado esquerdo e o inserimos no conjunto de resultados. O lado esquerdo é então substituído pela saída do lado direito e o lado direito é aplicado novamente ao "novo" lado esquerdo. Algo assim:
Você pode ver esse comportamento no plano de execução do CTE recursivo:
Esta é a etapa 1 acima, onde o lado esquerdo do UNION ALL é adicionado à saída:
Este é o lado direito do UNION ALL, onde a saída é concatenada para o conjunto de resultados:
fonte
A documentação do SQL Server , que menciona T i e T i + 1 , não é nem muito compreensível, nem uma descrição precisa da execução efectiva.
A idéia básica é que a parte recursiva da consulta analise todos os resultados anteriores, mas apenas uma vez .
Pode ser útil ver como outros bancos de dados implementam isso (para obter o mesmo resultado). A documentação do Postgres diz:
A documentação do SQLite sugere uma implementação um pouco diferente e esse algoritmo de uma linha por vez pode ser o mais fácil de entender:
fonte
Meu conhecimento está especificamente no DB2, mas observar os diagramas de explicação parece o mesmo com o SQL Server.
O plano vem daqui:
Veja-o em Colar o plano
O otimizador não literalmente executa uma união para cada consulta recursiva. Ele pega a estrutura da consulta e atribui a primeira parte da união toda a um "membro âncora" e, em seguida, percorre a segunda metade da união toda (chamada de "membro recursivo" recursivamente até atingir as limitações definidas. a recursão estiver concluída, o otimizador une todos os registros.
O otimizador aceita apenas uma sugestão para executar uma operação predefinida.
fonte