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 / coding / Perguntas / 79555383
Accepted
GD_Java
GD_Java
Asked: 2025-04-04 20:59:15 +0800 CST2025-04-04 20:59:15 +0800 CST 2025-04-04 20:59:15 +0800 CST

Consulta recursiva para descobrir o registro que cria dependência circular

  • 772

Eu tenho uma tabela postgress incoming_records que contém 2 colunas supervisor_id, emp_id

create table incoming_records(supervisor_id,emp_id)as values
 (null,01) --top level resources with blank supervisor id
,(01,02)
   ,(02,03)
      ,(03,04)
         ,(04,05)
            ,(05,06)
               ,(06,07)
                  ,(07,08)
,(null,10) -- supervisor with 3 employees directly under them
     ,(10,11)
     ,(10,12)
     ,(10,13)
,(14,14) -- loop length 1
,(15,16)
   ,(16,15) -- loop length 2 
,(17,18)
   ,(18,19)
      ,(19,17) -- loop length 3
,(20,21)
   ,(21,22)
      ,(22,23)
         ,(23,20) -- loop length 4
,(null,24) -- supervisor with no employees
,(null,25) -- fork, then an employee with 2 supervisors
     ,(25,26),(26,28)
     ,(25,27),(27,28)
,(null,29) -- supervisor with a null employee
     ,(29,null)
,(null,null) --nobody working for nobody
,(99,30) -- somebody working for someone who doesn't exist
,(99,null); -- nobody working for someone who doesn't exist

Esta tabela recebe dados de um aplicativo externo e é totalmente atualizada todos os dias e contém cerca de 60.000 registros. Com base nesses registros, preciso construir uma hierarquia de recursos. Para construir a hierarquia, escrevi uma consulta recursiva e ela funciona bem. Mas às vezes minha consulta recursiva entra em loop infinito devido a dados errados. Veja o cenário abaixo, onde uma dependência circular está sendo criada. Neste caso, a consulta recursiva entra em loop infinito e nunca termina.

supervisor_id emp_id
rt08 rt09
rt09 rt08

Estou tentando escrever uma consulta (escrita abaixo) que pode descobrir esses registros defeituosos. Mas até agora não tive sucesso.

WITH recursive tmp AS (
   SELECT supervisor_id,
          emp_id
   FROM incoming_records
   UNION ALL
   SELECT tmp.supervisor_id,
          t.emp_id
   FROM tmp
       INNER JOIN incoming_records AS t
           ON t.supervisor_id = tmp.emp_id
   WHERE t.supervisor_id = tmp.emp_id) 
select * from tmp

Qualquer sugestão será apreciada

sql
  • 4 4 respostas
  • 91 Views

4 respostas

  • Voted
  1. Best Answer
    Zegarek
    2025-04-04T23:21:12+08:002025-04-04T23:21:12+08:00

    A detecção de ciclo é um recurso integrado de CTEs recursivos: demonstração em db<>fiddle

    1. CYCLEcláusula informa qual valor indica um loop se ele se repetir
    2. Ele é seguido por um booleancampo, preenchido para os registros com loops, depois um campo de matriz que contém todo o caminho/grupo com o ciclo.
    3. Como o terceiro campo pathacumula todas as chaves, ele pode crescer rapidamente em conjuntos maiores e mais complexos. Como uma alternativa otimizada, rápida e com uso eficiente de memória, considerepgrouting .
    WITH recursive tmp AS (
       SELECT supervisor_id,
              emp_id
       FROM incoming_records
       UNION ALL
       SELECT tmp.supervisor_id,
              t.emp_id
       FROM tmp
           INNER JOIN incoming_records AS t
               ON t.supervisor_id = tmp.emp_id
    )CYCLE emp_id SET is_cycle USING path ----- here
    SELECT * FROM tmp
    WHERE is_cycle;
    
    supervisor_id emp_id é_ciclo caminho
    rt14 rt14 para {(rt14),(rt14)}
    rt15 rt16 para {(rt16),(rt15),(rt16)}
    rt16 rt15 para {(rt15),(rt16),(rt15)}
    rt17 rt18 para {(rt18),(rt19),(rt17),(rt18)}
    rt18 rt19 para {(rt19),(rt17),(rt18),(rt19)}
    rt19 rt17 para {(rt17),(rt18),(rt19),(rt17)}
    rt20 rt21 para {(rt21),(rt22),(rt23),(rt20),(rt21)}
    rt21 rt22 para {(rt22),(rt23),(rt20),(rt21),(rt22)}
    rt22 rt23 para {(rt23),(rt20),(rt21),(rt22),(rt23)}
    rt23 rt20 para {(rt20),(rt21),(rt22),(rt23),(rt20)}

    Se você quiser evitá-los em vez de selecionar apenas esses, mude para WHERE NOT is_cycle;.

    A cláusula padrão SQL CYCLEpara detecção de ciclo integrada foi adicionada na v14 .

    • 3
  2. Zegarek
    2025-04-05T02:20:56+08:002025-04-05T02:20:56+08:00

    pgr_connectedComponents()

    Em comparação com CTEs recursivos do tipo "faça você mesmo", as funções que vêm na pgroutingextensão são executadas mais rapidamente, usando menos memória, graças a anos de desenvolvimento do Boost C++ .

    Em conjuntos de amostra com alguns milhares de linhas, ele roda algumas vezes mais rápido do que todas as alternativas aqui e é o único aplicável acima desse tamanho. Os CTEs recursivos ficam sem memória, vazam para o disco e, em seguida, ficam sem espaço nos milhares inferiores. Aqui está um teste em 7,5 mil linhas em que ele leva 143 ms , enquanto pgroutingtermina em 31 ms .
    Se você aumentar o tamanho da amostra acima disso, o db<>fiddle fica sem recursos necessários para manter o exemplo de CTE recursivo ativo.

    Em 50k amostras, pgroutingo exemplo abaixo encontra 3899 linhas de grupos com loops em 170 ms :
    demonstração em db<>fiddle

    with trees as(
        select supervisor_id
             , emp_id
             , every(supervisor_id is not null)over w1 as has_cycle
        from pgr_connectedComponents(
          $x$ select row_number()over() as id
                   , coalesce(emp_id,supervisor_id,1e7::int) as source
                   , coalesce(supervisor_id,emp_id,1e7::int) as target
                   , 1      as cost
                   , 1      as reverse_cost
              from incoming_records 
          $x$) as c(seq,subgraph,emp_id)
        join incoming_records using(emp_id)
        window w1 as (partition by subgraph) )
    select supervisor_id
         , emp_id
    from trees 
    where has_cycle;
    

    Note que isso pressupõe que você não pode tornar um funcionário seu próprio supervisor e que cada funcionário pode ter no máximo um supervisor por vez. Neste cenário, é suficiente estabelecer que o supervisor de ponta nula está faltando para determinar que uma árvore contém um loop.

    • 1
  3. Guillaume Outters
    2025-04-04T21:55:21+08:002025-04-04T21:55:21+08:00

    Exibindo o máximo possível até chegar ao loop

    Em vez de identificar os loops em uma tarefa separada,
    você pode obter um passo a passo à prova de falhas, lembrando-se do caminho para chegar a cada item e parando quando encontrar um funcionário já visto.

    A estrutura correta para mantê-lo é a do PostgreSQL array:

    WITH recursive tmp
    AS (
       -- We're starting from the top, so no need of supervisor_id anymore:
       SELECT emp_id,
              array[emp_id] path                -- ← Here init our path.
       FROM incoming_records
       WHERE supervisor_id IS NULL
       UNION ALL
       SELECT t.emp_id,
              path||t.emp_id                    -- ← Here append the current employee.
       FROM tmp
           INNER JOIN incoming_records AS t
               ON t.supervisor_id = tmp.emp_id
       WHERE t.supervisor_id = tmp.emp_id
       AND NOT t.emp_id = ANY(path)             -- ← Here ensure the new employee is not a supervisor (already in the path) of this one.
    ) select * from tmp;
    

    (veja-o tocando violino )

    Um trabalho de 2 etapas (primeiro identificando os loops e depois calculando a árvore quando não houver mais loops) exigiria, de qualquer forma, o cálculo do caminho durante o CTE recursivo,
    mas:

    • bloqueie sua ferramenta até que você manualmente deleteum dos culpados
    • dificultar seu diagnóstico (se você começar de baixo como tentou, não conseguirá subir até o topo e ver onde na hierarquia seus dois candidatos estariam aproximadamente)

    Com exibição de culpado

    Você pode até mesmo exibir a entrada em loop durante a marcação em vez de filtrar (… e então na próxima iteração, parar a entrada marcada, é claro):

    WITH recursive tmp
    AS (
       SELECT emp_id, array[emp_id] path, FALSE looping
       FROM incoming_records
       WHERE supervisor_id IS NULL
       UNION ALL
       SELECT t.emp_id, path||t.emp_id, t.emp_id = ANY(path) -- ← If detecting a loop, SELECT but tag.
       FROM tmp
           INNER JOIN incoming_records AS t
               ON t.supervisor_id = tmp.emp_id
       WHERE t.supervisor_id = tmp.emp_id
       AND NOT looping                                       -- ← If last iteration was tagged, do not look for its subordinates anymore.
    ) select * from tmp;
    
    
    emp_id caminho em loop
    rt01 {rt01} e
    rt10 {rt10} e
    rt02 {rt01,rt02} e
    rt11 {rt10,rt11} e
    rt12 {rt10,rt12} e
    rt13 {rt10,rt13} e
    rt03 {rt01,rt02,rt03} e
    rt04 {rt01,rt02,rt03,rt04} e
    rt05 {rt01,rt02,rt03,rt04,rt05} e
    rt06 {rt01,rt02,rt03,rt04,rt05,rt06} e
    rt07 {rt01,rt02,rt03,rt04,rt05,rt06,rt07} e
    rt08 {rt01,rt02,rt03,rt04,rt05,rt06,rt07,rt08} e
    rt09 {rt01,rt02,rt03,rt04,rt05,rt06,rt07,rt08,rt09} e
    rt08 {rt01,rt02,rt03,rt04,rt05,rt06,rt07,rt08,rt09,rt08} para

    Então você pode usá-lo tanto na sua consulta de produção (onde você filtrará com um WHERE NOT loopingpara manter apenas um rt08) quanto no seu diagnóstico manual.

    Limites

    Duplicatas

    Observe que ainda pode haver duplicatas se você tiver duas maneiras de entrar no loop.

    Por exemplo, se 8 e 9 estiverem abaixo de 7, e um abaixo do outro,
    você obterá 9 como 7 > 9e 7 > 8 > 9(o que não é um loop, pois 9ainda não está no caminho; haverá um loop na próxima iteração, com 7 > 8 > 9 > 8).

    Mas isso não é específico para loops, aqui é um problema de gráfico.

    Você pode evitar isso escolhendo um DISTINCT ON (emp_id) ORDER BY ARRAY_LENGTH(path, 1) DESC(isso elege o caminho mais curto para o funcionário, mas se dois caminhos levam ao mesmo funcionário (por exemplo, se 3 > 5e 4 > 5), um será eleito arbitrariamente; e os subordinados deste incerto não necessariamente escolherão o mesmo caminho, portanto você pode terminar com 3 > 5para, 5mas 4 > 5 > 6com 6).

    Nenhum ponto de entrada

    Este de cima para baixo requer que seus elementos de loop tenham múltiplos pais, com pelo menos um acessível a partir de um funcionário não supervisionado.
    Um loop puro nunca aparecerá nos resultados. (por exemplo: if 8 > 9e 9 > 8loop juntos com no 7 > 8para conectá-los ao resto do mundo).

    Neste caso, você não terá outra escolha a não ser ter um "detector de loop puro" separado, como visto na minha resposta dedicada .

    • 0
  4. Guillaume Outters
    2025-04-04T22:48:41+08:002025-04-04T22:48:41+08:00

    Detectando loops puros

    Se você tiver loops puros em circuito fechado (sem nenhuma maneira de chegar a um chefe de hierarquia sem um supervisor),
    minha abordagem de exibir o máximo possível não funcionará, porque requer um chefe de hierarquia como ponto de entrada.

    Nesse caso, você ainda pode usar matrizes do PostgreSQL para construir o caminho de baixo para cima para cada funcionário e
    parar assim que encontrar um funcionário já visto:

    WITH recursive tmp
    AS (
       SELECT supervisor_id, emp_id, array[]::text[] path -- ← Start with an empty path beyond each employee.
       FROM incoming_records
       UNION ALL
       SELECT t.supervisor_id, t.emp_id, tmp.emp_id||path -- ← Append the employee where we came from to the path.
       FROM tmp
           INNER JOIN incoming_records AS t
               ON tmp.supervisor_id = t.emp_id
       AND NOT tmp.emp_id = ANY(path)                     -- ← Do not continue for entries having appeared in their own path.
    ) select * from tmp where emp_id = ANY(path);         -- ← And show only those, not the intermediates who succeeded into reaching a head of hierarchy without loop.
    
    • 0

relate perguntas

  • Atualizando todas as linhas, exceto uma que tenha os mesmos valores em determinadas colunas

  • Existe uma maneira de inverter apenas os números quando eu retornar uma coluna sql? (hebraico)

  • SQL menor/maior comparação entre booleanos produz resultados inesperados

  • Como atualizar valores na tabela Postgres com base em uma correspondência em uma matriz

  • Como somar colunas no sql server

Sidebar

Stats

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

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

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