Determina i nodi in rete con PostgreSQL

Oct 23 2020

Ho una tabella in cui ogni voce è un nodo e la tabella contiene le connessioni dirette di ogni nodo ad altri nodi. Sto cercando di creare una vista con una colonna per ogni nodo contenente tutti i nodi della catena, non solo i nodi a cui è connesso il nodo stesso.

Un esempio potrebbe essere la generazione della colonna Nodi in catena dalle prime due colonne della seguente tabella:

CREATE TABLE example
(
    node text,
    connections text[],
    nodes_in_chain text[]
)

INSERT INTO example VALUES
('a', ARRAY['a','b'],         null),
('b', ARRAY['a','b','c','d'], null),
('c', ARRAY['b','c'],         null),
('d', ARRAY['b','d'],         null),
('e', ARRAY['e','f'],         null),
('f', ARRAY['e','f'],         null);
Node   Connections    Nodes in Chain
"a"    "{a,b}"        "{a,b,c,d}"
"b"    "{a,b,c,d}"    "{a,b,c,d}"
"c"    "{b,c}"        "{a,b,c,d}"
"d"    "{b,d}"        "{a,b,c,d}"
"e"    "{e,f}"        "{e,f}"
"f"    "{e,f}"        "{e,f}"

Questa è una piccola versione semplificata del vero problema. Se riesco a risolvere l'esempio, la tabella completa non dovrebbe essere un problema.

I dati di questa tabella possono essere visualizzati nel modo seguente:

Ho esaminato diversi metodi per risolvere questo problema. Ho esaminato i CTE ricorsivi ma non sono riuscito a farli funzionare.

Ogni nodo è connesso a se stesso attualmente nel database. Non è un problema rimuovere la connessione a se stesso nel database, se necessario.

Probabilmente sfondo non necessario per il problema :

L'origine di questo problema deriva dal tentativo di identificare i veicoli nel traffico. Il database originale contiene la posizione e la velocità dei veicoli in ogni fase t in una determinata area. L'obiettivo è determinare il tempo trascorso a un semaforo. Per risolvere questo problema è stata individuata un'area di sosta per il semaforo. Ogni veicolo in quest'area con una velocità inferiore a una certa soglia è considerato in attesa del semaforo. A causa di una lunga coda, i veicoli potrebbero comunque essere in coda fuori da quest'area. Pertanto una linea di traffico ("Catena di nodi") viene realizzata con tutti i veicoli che si trovano entro una certa distanza l'uno dall'altro e hanno una bassa velocità al di sotto. Partendo da un veicolo all'interno dell'area di coda identificata. Il problema fa parte della ricerca scientifica sui tempi di taxi degli aerei. I veicoli sono quindi aerei e i semafori sono soglie di pista.

Per prima cosa ho eseguito il calcolo per identificare i veicoli in un'area con Python e panda. Tuttavia, l'esecuzione del codice ha richiesto 10 volte più tempo, il che lo ha reso proibitivo per il progetto. Il codice era molto semplice senza loop introdotti manualmente e quindi non poteva essere accelerato (credo). Confronterò anche la velocità di esecuzione dell'algoritmo di accodamento in Python rispetto a PostgreSQL.

Risposte

6 McNets Oct 24 2020 at 05:16

Approccio 1:

A prima vista, sembra che potrei applicare una soluzione di base perché, secondo i tuoi dati di esempio, ogni singola connessione è inclusa in un'altra connessione.

SELECT 
    e1.node, 
    e1.connections, 
    COALESCE(e2.connections, e1.connections) nodes_in_chain
FROM
    example e1
LEFT JOIN 
    example e2
    ON e2.node <> e1.node
    AND e1.connections <@ e2.connections;
nodo | connessioni | nodes_in_chain
: --- | : ---------- | : -------------
a | {a, b} | {a, b, c, d}     
b | {a, b, c, d} | {a, b, c, d}     
c | {b, c} | {a, b, c, d}     
d | {b, d} | {a, b, c, d}     
e | {e, f} | {e, f}         
f | {e, f} | {e, f}         

Approccio 2:

Ma, come ha sottolineato @ypercube , questa soluzione non funziona se ci sono 3 o più punti lineari di seguito.

Es: e -> f -> g -> h

Come riferimento per risolvere questa domanda ho usato le risposte in un'altra domanda correlata:

  • Raggruppamento su una qualsiasi delle più colonne in Postgres

Utilizza un metodo chiamato chiusura transitiva per risolvere il problema.

Chiusura transitiva

In matematica, la chiusura transitiva di una relazione binaria R su un insieme X è la più piccola relazione su X che contiene R ed è transitiva.

Ad esempio, se X è un insieme di aeroporti e xRy significa "c'è un volo diretto dall'aeroporto x all'aeroporto y" (per xey in X), allora la chiusura transitiva di R su X è la relazione R + tale che x R + y significa "è possibile volare da x a y in uno o più voli". Informalmente, la chiusura transitiva ti dà l'insieme di tutti i luoghi a cui puoi arrivare da qualsiasi punto di partenza.

Per prima cosa lasciami modificare i tuoi dati di esempio aggiungendo una connessione lineare di 4 nodi.

DELETE FROM example WHERE node = 'f';
INSERT INTO example VALUES
('f', ARRAY['e','f','g'],     null),
('g', ARRAY['f','g','h'],     null),
('h', ARRAY['g','h'],         null);

Ora applicando la matematica:

WITH RECURSIVE al (dst, src) AS --adjacent list or list of all related nodes
(
  SELECT e1.node, e2.node
  FROM   example e1
  JOIN   example e2
         ON e1.node = any(e2.connections)   
), tc (dst, src) AS
   (
     SELECT dst, src FROM al -- transitive closure
     UNION
     SELECT a1.dst, a2.src
     FROM   al as a1 
     JOIN   tc as a2 
            ON a1.src = a2.dst
   )
   SELECT   src, array_agg(DISTINCT dst ORDER BY dst) AS nodes_in_chain
   FROM     tc
   GROUP BY src;

Dacci questo risultato:

src | nodes_in_chain
:-- | :-------------
a   | {a,b,c,d}     
b   | {a,b,c,d}     
c   | {a,b,c,d}     
d   | {a,b,c,d}     
e   | {e,f,g,h}     
f   | {e,f,g,h}     
g   | {e,f,g,h}     
h   | {e,f,g,h}     

db <> fiddle qui

NOTA : la relazione originale ha solo le connessioni immediate, che possono essere viste come percorsi di lunghezza 1 (2 nodi ciascuno). L'approccio 1 trova tutti i percorsi di lunghezza 2 (3 nodi) poiché applica un metodo di connessione una volta. Per trovare percorsi di lunghezza N, devi applicare i metodi N-1 volte. Per trovare tutti i percorsi di lunghezza arbitraria (ahimè la chiusura transitiva), è necessaria una soluzione ricorsiva, o un ciclo while. Non può essere fatto con un semplice SQL. (ad esempio: una query senza CTE.)

@ypercube