PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

SQL-Gruppierung sich überschneidender/überlappender Zeilen

Angenommen, alle Paare existieren auch in ihrer gespiegelten Kombination (4,5) und (5,4) . Aber die folgenden Lösungen funktionieren genauso gut ohne gespiegelte Duplikate.

Einfacher Fall

Alle Verbindungen können in einer einfach aufsteigenden Reihenfolge aneinandergereiht werden und Komplikationen, wie ich sie in der Geige hinzugefügt habe nicht möglich sind, können wir diese Lösung ohne Duplikate im rCTE verwenden:

Ich fange an, indem ich mindestens a_sno erhalte pro Gruppe, mit dem zugehörigen Minimum 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;

Dies erfordert nur eine einzige Abfrageebene, da eine Fensterfunktion auf einem Aggregat aufgebaut werden kann:

Ergebnis:

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

Ich vermeide Verzweigungen und doppelte (multiplizierte) Zeilen - möglicherweise viel teurer mit langen Ketten. Ich verwende ORDER BY b_sno LIMIT 1 in einer korrelierten Unterabfrage, um dies in einem rekursiven CTE fliegen zu lassen.

Der Schlüssel zur Leistung ist ein passender Index, der bereits durch die PK-Einschränkung PRIMARY KEY (a_sno,b_sno) bereitgestellt wird :nicht umgekehrt (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;

Weniger einfacher Fall

Alle Knoten sind in aufsteigender Reihenfolge mit einem oder mehreren Zweigen von der Wurzel aus erreichbar (kleinste sno ).

Holen Sie sich dieses Mal alle größer sno und deduplizieren Sie Knoten, die möglicherweise mehrmals besucht werden, mit UNION am Ende:

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;

Anders als bei der ersten Lösung erhalten wir hier keine letzte Zeile mit NULL (verursacht durch die korrelierte Unterabfrage).

Beides sollte sehr gut funktionieren - gerade bei langen Ketten / vielen Verzweigungen. Ergebnis wie gewünscht:

SQL-Fiddle (mit zusätzlichen Reihen, um die Schwierigkeit zu demonstrieren).

Ungerichteter Graph

Wenn es lokale Minima gibt, die von der Wurzel mit aufsteigender Traversierung nicht erreicht werden können, funktionieren die obigen Lösungen nicht. Betrachten Sie Farhęgs Lösung in diesem Fall.