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 / 126181
Accepted
Diggity
Diggity
Asked: 2016-01-12 21:08:19 +0800 CST2016-01-12 21:08:19 +0800 CST 2016-01-12 21:08:19 +0800 CST

Profundidade descendente recursiva do PostgreSQL

  • 772

Preciso calcular a profundidade de um descendente de seu ancestral. Quando um registro possui object_id = parent_id = ancestor_id, ele é considerado um nó raiz (o ancestral). Eu tenho tentado obter uma WITH RECURSIVEconsulta em execução com o PostgreSQL 9.4 .

Eu não controlo os dados ou as colunas. O esquema de dados e tabelas vem de uma fonte externa. A tabela está crescendo continuamente . Agora em cerca de 30 mil registros por dia. Qualquer nó na árvore pode estar faltando e eles serão extraídos de uma fonte externa em algum momento. Eles geralmente são extraídos em created_at DESCordem, mas os dados são extraídos com trabalhos assíncronos em segundo plano.

Inicialmente, tínhamos uma solução de código para esse problema, mas agora com mais de 5 milhões de linhas, leva quase 30 minutos para ser concluída.

Exemplo de definição de tabela e dados de teste:

CREATE TABLE objects (
  id          serial NOT NULL PRIMARY KEY,
  customer_id integer NOT NULL,
  object_id   integer NOT NULL,
  parent_id   integer,
  ancestor_id integer,
  generation  integer NOT NULL DEFAULT 0
);

INSERT INTO objects(id, customer_id , object_id, parent_id, ancestor_id, generation)
VALUES (2, 1, 2, 1, 1, -1), --no parent yet
       (3, 2, 3, 3, 3, -1), --root node
       (4, 2, 4, 3, 3, -1), --depth 1
       (5, 2, 5, 4, 3, -1), --depth 2
       (6, 2, 6, 5, 3, -1), --depth 3
       (7, 1, 7, 7, 7, -1), --root node
       (8, 1, 8, 7, 7, -1), --depth 1
       (9, 1, 9, 8, 7, -1); --depth 2

Observe que object_idnão é único, mas a combinação (customer_id, object_id)é única.
Executando uma consulta como esta:

WITH RECURSIVE descendants(id, customer_id, object_id, parent_id, ancestor_id, depth) AS (
  SELECT id, customer_id, object_id, parent_id, ancestor_id, 0
  FROM objects
  WHERE object_id = parent_id

  UNION

  SELECT o.id, o.customer_id, o.object_id, o.parent_id, o.ancestor_id, d.depth + 1
  FROM objects o
  INNER JOIN descendants d ON d.parent_id = o.object_id
  WHERE
    d.id <> o.id
  AND
    d.customer_id = o.customer_id
) SELECT * FROM descendants d;

Eu gostaria que a generationcoluna fosse definida como a profundidade que foi calculada. Quando um novo registro é adicionado, a coluna de geração é definida como -1. Existem alguns casos em que um parent_idpode não ter sido puxado ainda. Se parent_idnão existir, deve deixar a coluna de geração definida como -1.

Os dados finais devem ficar assim:

id | customer_id | object_id | parent_id | ancestor_id | generation
2    1             2           1           1            -1
3    2             3           3           3             0
4    2             4           3           3             1
5    2             5           4           3             2
6    2             6           5           3             3
7    1             7           7           7             0
8    1             8           7           7             1
9    1             9           8           7             2

O resultado da consulta deve ser atualizar a coluna de geração para a profundidade correta.

Comecei a trabalhar com as respostas a esta pergunta relacionada no SO .

postgresql update
  • 2 2 respostas
  • 5006 Views

2 respostas

  • Voted
  1. Best Answer
    ypercubeᵀᴹ
    2016-01-14T14:58:30+08:002016-01-14T14:58:30+08:00

    A consulta que você tem está basicamente correta. O único erro está na segunda parte (recursiva) do CTE onde tem:

    INNER JOIN descendants d ON d.parent_id = o.object_id
    

    Deveria ser o contrário:

    INNER JOIN descendants d ON d.object_id = o.parent_id 
    

    Você deseja juntar os objetos com seus pais (que já foram encontrados).

    Assim, a consulta que calcula a profundidade pode ser escrita (nada mais alterado, apenas a formatação):

    -- calculate generation / depth, no updates
    WITH RECURSIVE descendants
      (id, customer_id, object_id, parent_id, ancestor_id, depth) AS
     AS ( SELECT id, customer_id, object_id, parent_id, ancestor_id, 0
          FROM objects
          WHERE object_id = parent_id
    
          UNION ALL
    
          SELECT o.id, o.customer_id, o.object_id, o.parent_id, o.ancestor_id, d.depth + 1
          FROM objects o
          INNER JOIN descendants d ON  d.customer_id = o.customer_id
                                   AND d.object_id = o.parent_id  
          WHERE d.id <> o.id
        ) 
    SELECT * 
    FROM descendants d
    ORDER BY id ;
    

    Para a atualização, basta substituir o último SELECT, pelo UPDATE, juntando o resultado do cte, de volta à tabela:

    -- update nodes
    WITH RECURSIVE descendants
        -- nothing changes here except
        -- ancestor_id and parent_id 
        -- which can be omitted form the select lists
        ) 
    UPDATE objects o 
    SET generation = d.depth 
    FROM descendants d
    WHERE o.id = d.id 
      AND o.generation = -1 ;          -- skip unnecessary updates
    

    Testado no SQLfiddle

    Comentários adicionais:

    • the ancestor_ide the parent_idnão precisam estar na lista de seleção (antepassado é óbvio, pai um pouco complicado para descobrir o porquê), então você pode mantê-los na SELECTconsulta se quiser, mas pode removê-los com segurança do arquivo UPDATE.
    • o (customer_id, object_id)parece ser um candidato a uma UNIQUErestrição. Se seus dados estiverem em conformidade com isso, adicione essa restrição. As uniões realizadas no CTE recursivo não fariam sentido se não fossem únicas (caso contrário, um nó poderia ter 2 pais).
    • se você adicionar essa restrição, (customer_id, parent_id)seria um candidato a uma FOREIGN KEYrestrição que REFERENCESo (exclusivo) (customer_id, object_id). Você provavelmente não deseja adicionar essa restrição FK, pois, pela sua descrição, você está adicionando novas linhas e algumas linhas podem fazer referência a outras que ainda não foram adicionadas.
    • Certamente há problemas com a eficiência da consulta, se ela for realizada em uma tabela grande. Não na primeira execução, pois quase toda a tabela será atualizada de qualquer maneira. Mas, na segunda vez, você deseja que apenas novas linhas (e aquelas que não foram tocadas pela 1ª execução) sejam consideradas para atualização. O CTE como está terá que construir um grande resultado.
      O AND o.generation = -1na atualização final garantirá que as linhas que foram atualizadas na 1ª execução não sejam atualizadas novamente, mas o CTE ainda é uma parte cara.

    A seguir, uma tentativa de resolver esses problemas: melhorar o CTE para considerar o menor número possível de linhas e usar (customer_id, obejct_id)em vez de (id)para identificar linhas (portanto, idé completamente removido da consulta. Pode ser usado como a 1ª atualização ou uma subsequente:

    WITH RECURSIVE descendants 
      (customer_id, object_id, depth) 
     AS ( SELECT customer_id, object_id, 0
          FROM objects
          WHERE object_id = parent_id
            AND generation = -1
    
          UNION ALL
    
          SELECT o.customer_id, o.object_id, p.generation + 1
          FROM objects o
            JOIN objects p ON  p.customer_id = o.customer_id
                           AND p.object_id = o.parent_id
                           AND p.generation > -1
          WHERE o.generation = -1
    
          UNION ALL
    
          SELECT o.customer_id, o.object_id, d.depth + 1
          FROM objects o
          INNER JOIN descendants d ON  o.customer_id = d.customer_id
                                   AND o.parent_id = d.object_id
          WHERE o.parent_id <> o.object_id
            AND o.generation = -1
        )
    UPDATE objects o 
    SET generation = d.depth 
    FROM descendants d
    WHERE o.customer_id = d.customer_id
      AND o.object_id = d.object_id
      AND o.generation = -1        -- this is not really needed
    

    Observe como o CTE tem 3 partes. As duas primeiras são as partes estáveis. A primeira parte encontra os nós raiz que não foram atualizados antes e ainda generation=-1devem ser nós adicionados recentemente. A 2ª parte encontra filhos (com generation=-1) de nós pais que foram atualizados anteriormente.
    A terceira parte, recursiva, encontra todos os descendentes das duas primeiras partes, como antes.

    Testado no SQLfiddle-2

    • 15
  2. Erwin Brandstetter
    2016-01-15T18:59:14+08:002016-01-15T18:59:14+08:00

    @ypercube já fornece ampla explicação, então vou direto ao ponto o que tenho a acrescentar.

    Se parent_idnão existir, deve deixar a coluna de geração definida como -1.

    Presumo que isso deva ser aplicado recursivamente, ou seja, o restante da árvore sempre ocorre generation = -1após qualquer nó ausente.

    Se algum nó na árvore pode estar faltando (ainda), precisamos encontrar linhas com generation = -1isso ...
    ... são nós raiz
    ... ou têm um pai com generation > -1.
    E atravesse a árvore a partir daí. Os nós filhos desta seleção também devem ter generation = -1.

    Pegue o generationdo pai incrementado em um ou volte para 0 para os nós raiz:

    WITH RECURSIVE tree AS (
       SELECT c.customer_id, c.object_id, COALESCE(p.generation + 1, 0) AS depth
       FROM   objects      c
       LEFT   JOIN objects p ON c.customer_id = p.customer_id
                            AND c.parent_id   = p.object_id
                            AND p.generation > -1
       WHERE  c.generation = -1
       AND   (c.parent_id = c.object_id OR p.generation > -1)
           -- root node ... or parent with generation > -1
    
       UNION ALL
       SELECT customer_id, c.object_id, p.depth + 1
       FROM   objects c
       JOIN   tree    p USING (customer_id)
       WHERE  c.parent_id  = p.object_id
       AND    c.parent_id <> c.object_id  -- exclude root nodes
       AND    c.generation = -1           -- logically redundant, but see below!
       )
    UPDATE objects o 
    SET    generation = t.depth
    FROM   tree t
    WHERE  o.customer_id = t.customer_id
    AND    o.object_id   = t.object_id;
    

    A parte não recursiva é única SELECTdessa maneira, mas logicamente equivalente ao two union'ed de @ypercube SELECT. Não tenho certeza qual é mais rápido, você terá que testar.
    O ponto muito mais importante para o desempenho é:

    Índice!

    Se você adicionar repetidamente linhas a uma tabela grande dessa maneira, adicione um índice parcial :

    CREATE INDEX objects_your_name_idx ON objects (customer_id, parent_id, object_id)
    WHERE  generation = -1;
    

    Isso alcançará mais desempenho do que todas as outras melhorias discutidas até agora - para pequenas adições repetidas a uma grande tabela.

    Adicionei a condição de índice à parte recursiva do CTE (embora logicamente redundante) para ajudar o planejador de consulta a entender que o índice parcial é aplicável.

    Além disso, você provavelmente também deve ter a UNIQUErestrição (object_id, customer_id)naquele @ypercube já mencionado. Ou, se você não puder impor exclusividade por algum motivo (por quê?) Adicione um índice simples. A ordem das colunas do índice é importante, btw:

    • Um índice composto também é bom para consultas no primeiro campo?
    • 3

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

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