PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Righe intersecanti/sovrapposte di raggruppamento SQL

Supponendo che tutte le coppie esistano anche nella loro combinazione speculare (4,5) e (5,4) . Ma le seguenti soluzioni funzionano altrettanto bene senza duplicati con mirroring.

Caso semplice

Tutti i collegamenti possono essere allineati in una unica sequenza ascendente e complicazioni come ho aggiunto nel violino non sono possibili, possiamo usare questa soluzione senza duplicati in rCTE:

Comincio ottenendo un minimo di a_sno per gruppo, con il minimo associato b_sno :

SELECT row_number() OVER (ORDER BY a_sno) AS grp
     , a_sno, min(b_sno) AS b_sno
FROM   data d
WHERE  a_sno < b_sno
AND    NOT EXISTS (
   SELECT 1 FROM data
   WHERE  b_sno = d.a_sno
   AND    a_sno < b_sno
   )
GROUP  BY a_sno;

Questo richiede solo un singolo livello di query poiché una funzione di finestra può essere costruita su un aggregato:

Risultato:

grp  a_sno  b_sno
1    4      5
2    9      10
3    11     15

Evito i rami e le righe duplicate (moltiplicate), potenzialmente molto più costoso con catene lunghe. Uso ORDER BY b_sno LIMIT 1 in una sottoquery correlata per farlo volare in un CTE ricorsivo.

La chiave per le prestazioni è un indice di corrispondenza, già presente fornito dal vincolo PK PRIMARY KEY (a_sno,b_sno) :non il contrario (b_sno, a_sno) :

WITH RECURSIVE t AS (
   SELECT row_number() OVER (ORDER BY d.a_sno) AS grp
        , a_sno, min(b_sno) AS b_sno  -- the smallest one
   FROM   data d
   WHERE  a_sno < b_sno
   AND    NOT EXISTS (
      SELECT 1 FROM data
      WHERE  b_sno = d.a_sno
      AND    a_sno < b_sno
      )
   GROUP  BY a_sno
   )

, cte AS (
   SELECT grp, b_sno AS sno FROM t

   UNION ALL
   SELECT c.grp
       , (SELECT b_sno  -- correlated subquery
          FROM   data
          WHERE  a_sno = c.sno
          AND    a_sno < b_sno
          ORDER  BY b_sno
          LIMIT  1)
   FROM   cte  c
   WHERE  c.sno IS NOT NULL
   )
SELECT * FROM cte
WHERE  sno IS NOT NULL   -- eliminate row with NULL
UNION  ALL               -- no duplicates
SELECT grp, a_sno FROM t
ORDER  BY grp, sno;

Caso meno semplice

Tutti i nodi possono essere raggiunti in ordine crescente con uno o più rami dalla radice (il più piccolo sno ).

Questa volta, prendi tutti maggiore sno e deduplica i nodi che possono essere visitati più volte con UNION alla fine:

WITH RECURSIVE t AS (
   SELECT rank() OVER (ORDER BY d.a_sno) AS grp
        , a_sno, b_sno  -- get all rows for smallest a_sno
   FROM   data d
   WHERE  a_sno < b_sno
   AND    NOT EXISTS (
      SELECT 1 FROM data
      WHERE  b_sno = d.a_sno
      AND    a_sno < b_sno
      )
   )   
, cte AS (
   SELECT grp, b_sno AS sno FROM t

   UNION ALL
   SELECT c.grp, d.b_sno
   FROM   cte  c
   JOIN   data d ON d.a_sno = c.sno
                AND d.a_sno < d.b_sno  -- join to all connected rows
   )
SELECT grp, sno FROM cte
UNION                     -- eliminate duplicates
SELECT grp, a_sno FROM t  -- add first rows
ORDER  BY grp, sno;

A differenza della prima soluzione, qui non otteniamo un'ultima riga con NULL (causata dalla sottoquery correlata).

Entrambi dovrebbero funzionare molto bene, specialmente con catene lunghe/molti rami. Risultato desiderato:

SQL Fiddle (con righe aggiunte per dimostrare la difficoltà).

Grafico non orientato

Se ci sono minimi locali che non possono essere raggiunti dalla radice con attraversamento ascendente, le soluzioni di cui sopra non funzioneranno. Prendi in considerazione la soluzione di Farhęg in questo caso.