AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 226946
Accepted
UnLogicGuys
UnLogicGuys
Asked: 2019-01-12 11:49:33 +0800 CST2019-01-12 11:49:33 +0800 CST 2019-01-12 11:49:33 +0800 CST

Como a recursão SQL realmente funciona?

  • 772

Vindo para SQL de outras linguagens de programação, a estrutura de uma consulta recursiva parece bastante estranha. Ande por ela passo a passo, e ela 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 percorrê-lo.

Primeiro, o membro âncora é executado e o conjunto de resultados é colocado em R. Assim, R é inicializado como {3, 5, 7}.

Então, a execução cai abaixo de UNION ALL e o membro recursivo é executado pela primeira vez. Ele é executado em R (ou seja, no R que temos atualmente em mãos: {3, 5, 7}). Isso resulta em {9, 25, 49}.

O que ele faz com esse novo resultado? Ele anexa {9, 25, 49} ao {3, 5, 7} existente, rotula a união resultante R e então continua com a recursão a partir daí? Ou ele redefine R para ser apenas este novo resultado {9, 25, 49} e faz toda a união depois?

Nenhuma das escolhas 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}, então temos um problema de rotulagem incorreta. R é entendido como a união do conjunto de resultados do membro âncora e todos os conjuntos de resultados do membro recursivo subsequentes. Considerando que {9, 25, 49} é apenas um componente de R. Não é o R completo que acumulamos até agora. Portanto, escrever o membro recursivo selecionando de 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 então (2) todos os componentes são unidos. É assim que entendo a recursão SQL para realmente funcionar.

Se estivéssemos redesenhando o SQL, talvez pudéssemos impor uma sintaxe mais clara e explícita, algo assim:

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 SQL como está atualmente é que ela é escrita de maneira confusa. A forma como está escrito diz que cada componente é formado selecionando de R, mas isso não significa o R completo que foi (ou parece ter sido) construído até agora. Significa apenas o componente anterior.

sql-server cte
  • 4 4 respostas
  • 4692 Views

4 respostas

  • Voted
  1. Best Answer
    Martin Smith
    2019-01-12T15:56:09+08:002019-01-12T15:56:09+08:00

    A descrição BOL de CTEs recursivos descreve a semântica da execução recursiva da seguinte forma:

    1. Divida a expressão CTE em membros âncora e recursivos.
    2. Execute o(s) membro(s) âncora(s) criando a primeira chamada ou conjunto de resultados base (T0).
    3. Execute o(s) membro(s) recursivo(s) com Ti como entrada e Ti+1 como saída.
    4. Repita a etapa 3 até que um conjunto vazio seja retornado.
    5. Retorne o conjunto de resultados. Esta é uma UNION ALL de T0 a Tn.

    Portanto, cada nível tem como entrada apenas o nível acima e não todo o conjunto de resultados acumulado até o momento.

    O acima é como ele funciona logicamente . Atualmente, as CTEs recursivas fisicamente 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 BYda sua consulta, os resultados serão ordenados da seguinte maneira

    +---------+
    |    N    |
    +---------+
    |       3 |
    |       5 |
    |       7 |
    |      49 |
    |    2401 |
    | 5764801 |
    |      25 |
    |     625 |
    |  390625 |
    |       9 |
    |      81 |
    |    6561 |
    +---------+
    

    Isso ocorre porque o plano de execução funciona de maneira muito semelhante ao seguinteC#

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    
    public class Program
    {
        private static readonly Stack<dynamic> StackSpool = new Stack<dynamic>();
    
        private static void Main(string[] args)
        {
            //temp table #NUMS
            var nums = new[] { 3, 5, 7 };
    
            //Anchor member
            foreach (var number in nums)
                AddToStackSpoolAndEmit(number, 0);
    
            //Recursive part
            ProcessStackSpool();
    
            Console.WriteLine("Finished");
            Console.ReadLine();
        }
    
        private static void AddToStackSpoolAndEmit(long number, int recursionLevel)
        {
            StackSpool.Push(new { N = number, RecursionLevel = recursionLevel });
            Console.WriteLine(number);
        }
    
        private static void ProcessStackSpool()
        {
            //recursion base case
            if (StackSpool.Count == 0)
                return;
    
            var row = StackSpool.Pop();
    
            int thisLevel = row.RecursionLevel + 1;
            long thisN = row.N * row.N;
    
            Debug.Assert(thisLevel <= 100, "max recursion level exceeded");
    
            if (thisN < 10000000)
                AddToStackSpoolAndEmit(thisN, thisLevel);
    
            ProcessStackSpool();
        }
    }
    

    NB1: Como acima, no momento em que o primeiro filho do membro âncora 3está sendo processado, todas as informações sobre seus irmãos, 5e 7, e seus descendentes, já foram descartadas do spool e não estão mais acessíveis.

    NB2: O C# acima tem a mesma semântica geral do plano de execução, mas o fluxo no plano de execução não é idêntico, pois os operadores trabalham em uma forma de execução em pipeline. Este é um exemplo simplificado para demonstrar a essência da abordagem. Consulte os links anteriores para obter mais detalhes sobre o plano em si.

    NB3: O próprio spool de pilha é aparentemente implementado como um índice clusterizado não exclusivo com coluna de chave de nível de recursão e exclusivos adicionados conforme necessário ( source )

    • 27
  2. Hannah Vernon
    2019-01-12T12:31:26+08:002019-01-12T12:31:26+08:00

    Este é apenas um palpite (semi) educado e provavelmente está completamente errado. Pergunta interessante, diga-se de passagem.

    T-SQL é uma linguagem declarativa; talvez uma CTE recursiva seja traduzida em uma operação estilo cursor onde os resultados do lado esquerdo de UNION ALL são anexados em uma tabela temporária, então o lado direito de UNION ALL é 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 aplicado ao lado esquerdo e inserimos isso 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:

    1. {3,5,7} -> conjunto de resultados
    2. declarações recursivas aplicadas a {3,5,7}, que é {9,25,49}. {9,25,49} é adicionado ao conjunto de resultados e substitui o lado esquerdo de UNION ALL.
    3. instruções recursivas aplicadas a {9,25,49}, que é {81,625,2401}. {81.625.2401} é adicionado ao conjunto de resultados e substitui o lado esquerdo de UNION ALL.
    4. instruções recursivas aplicadas a {81.625.2401}, que é {6561,390625,5764801}. {6561,390625,5764801} é adicionado ao conjunto de resultados.
    5. O cursor está completo, pois a próxima iteração resulta na cláusula WHERE retornando false.

    Você pode ver esse comportamento no plano de execução do CTE recursivo:

    insira a descrição da imagem aqui

    Este é o passo 1 acima, onde o lado esquerdo de UNION ALL é adicionado à saída:

    insira a descrição da imagem aqui

    Este é o lado direito de UNION ALL onde a saída é concatenada ao conjunto de resultados:

    insira a descrição da imagem aqui

    • 6
  3. CL.
    2019-01-13T00:22:02+08:002019-01-13T00:22:02+08:00

    A documentação do SQL Server , que menciona T i e T i+1 , não é muito compreensível nem uma descrição precisa da implementação real.

    A ideia 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:

    Avaliação de consulta recursiva

    1. Avalie o termo não recursivo. Para UNION(mas não UNION ALL), descarte as linhas duplicadas. Inclua todas as linhas restantes no resultado da consulta recursiva e também as coloque em uma tabela de trabalho temporária .
    2. Enquanto a mesa de trabalho não estiver vazia, repita estas etapas:
      1. Avalie o termo recursivo, substituindo o conteúdo atual da tabela de trabalho pela auto-referência recursiva. Para UNION(mas não UNION ALL), descarte linhas duplicadas e linhas que dupliquem qualquer linha de resultado anterior. Inclua todas as linhas restantes no resultado da consulta recursiva e também as coloque em uma tabela intermediária temporária .
      2. Substitua o conteúdo da mesa de trabalho pelo conteúdo da mesa intermediária e esvazie a mesa intermediária.

    Nota
    Estritamente falando, este processo é iteração e não recursão, mas RECURSIVEé a terminologia escolhida pelo comitê de padrões SQL.

    A documentação do SQLite sugere uma implementação ligeiramente diferente, e este algoritmo de uma linha por vez pode ser o mais fácil de entender:

    O algoritmo básico para calcular o conteúdo da tabela recursiva é o seguinte:

    1. Execute o initial-selecte adicione os resultados a uma fila.
    2. Enquanto a fila não estiver vazia:
      1. Extraia uma única linha da fila.
      2. Insira essa única linha na tabela recursiva
      3. Finja que a única linha extraída é a única linha na tabela recursiva e execute o recursive-select, adicionando todos os resultados à fila.

    O procedimento básico acima pode ser modificado pelas seguintes regras adicionais:

    • Se um operador UNION conectar o initial-selectcom o recursive-select, apenas adicione linhas à fila se nenhuma linha idêntica tiver sido adicionada anteriormente à fila. As linhas repetidas são descartadas antes de serem adicionadas à fila, mesmo que as linhas repetidas já tenham sido extraídas da fila pela etapa de recursão. Se o operador for UNION ALL, todas as linhas geradas por the initial-selecte the recursive-selectserão sempre adicionadas à fila, mesmo que sejam repetidas.
      […]
    • 4
  4. Michael S.
    2019-01-12T12:45:30+08:002019-01-12T12:45:30+08:00

    Meu conhecimento é especificamente em DB2, mas olhando os diagramas de explicação parece ser o mesmo com o SQL Server.

    O plano vem daqui:

    Veja em Colar o Plano

    Plano de explicação do SQL Server

    O otimizador não executa literalmente uma união de todos para cada consulta recursiva. Ele pega a estrutura da consulta e atribui a primeira parte da união all a um "membro âncora" então ele percorrerá a segunda metade da união all (chamada de "membro recursivo" recursivamente até atingir as limitações definidas. a recursão está completa, o otimizador une todos os registros.

    O otimizador apenas toma como sugestão fazer uma operação pré-definida.

    • -1

relate perguntas

  • SQL Server - Como as páginas de dados são armazenadas ao usar um índice clusterizado

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve