Suche nach einer einfacheren Alternative zu einer rekursiven Abfrage

7

Die eigentliche Abfrage ist komplizierter, aber das Problem, mit dem ich konfrontiert bin, kann folgendermaßen herausgearbeitet werden:

Eine Abfrage zum Filtern eines Rowsets monoton ansteigender Ganzzahlen, sodass - in der endgültigen Ergebnismenge row (n + 1) .value> = row (n) .value + 5 .

Für das eigentliche Problem, das ich lösen muss, liegt die Anzahl der Rowsets in den 1000ern.

Einige Beispiele zur Verdeutlichung:

  • Wenn die Zeilen: 1,2,3,4,5: lauten, sollte die Abfrage Folgendes zurückgeben: 1
  • Wenn die Zeilen: 1,5,7,10,11,12,13 sind, sollte die Abfrage Folgendes zurückgeben: 1,7,12
  • Wenn die Zeilen: 6,8,11,16,20,23 sind, sollte die Abfrage Folgendes zurückgeben: 6,11,16,23
  • Wenn die Zeilen: 6,8,12,16,20,23 sind, sollte die Abfrage Folgendes zurückgeben: 6,12,20

Ich habe es geschafft, die erforderlichen Ergebnisse mit der folgenden Abfrage zu erhalten, aber es scheint zu kompliziert. Kommentieren Sie die verschiedenen "..mit t (k) .." aus, um sie auszuprobieren.

Ich suche nach Vereinfachungen oder alternativen Ansätzen, um die gleichen Ergebnisse zu erzielen.

with recursive r(n, pri) as (
    with t(k) as (values (1),(2),(3),(4),(5))   -- the data we want to filter
    -- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
    -- with t(k) as (values (6),(8),(11),(16),(20),(23))
    -- with t(k) as (values (6),(8),(12),(16),(20),(23))
    select min(k), 1::bigint from t             -- bootstrap for recursive processing. 1 here represents rank().
    UNION
    select k, (rank() over(order by k)) rr      -- rank() is required just to filter out the rows we dont want from the final result set, and no other reason
    from r, t 
    where t.k >= r.n+5 and r.pri = 1            -- capture the rows we want, AND unfortunately a bunch of rows we dont want 
)
select n from r where pri = 1; 
sr33
quelle
Für die Regel row(n+1).value >= row(n).value + 5 und die Zeilen, die 1,5,7,10,11,12,13Sie sagen, sollte sie zurückgegeben werden, 1,7,12aber das stimmt nicht mit der Regel überein. Die Regel sollte auch besagen, dass Sie immer die erste Zeile zurückgeben. Wenn Sie also mit der ersten Zeile 1 beginnen, 5 >= 1 + 5ist dies falsch, sodass 5 ausgeschlossen ist. Dann 7 >= 5 + 5ist auch falsch, also sollte 7 gemäß der Regel ausgeschlossen werden, aber es ist enthalten. Ihre Regel lautet eher "Beginnen Sie mit der ersten Zeile und suchen Sie die nächste Zeile, die mindestens fünf mehr als der aktuelle Wert ist und wiederholen Sie sich"
Davos,

Antworten:

2

Das war schwer! Ich weiß nicht, ob dies einfacher ist , aber es verwendet zumindest keine Fensterfunktion und erzeugt keine Zeilen, die herausgefiltert werden müssen.

with recursive r(k, n) as (
    with t(k) as (values (1),(2),(3),(4),(5))   -- the data we want to filter
    -- with t(k) as (values (1),(5),(7),(10),(11),(12),(13))
    -- with t(k) as (values (6),(8),(11),(16),(20),(23))
    -- with t(k) as (values (6),(8),(12),(16),(20),(23))
         ,t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t) -- precalculate what's next
    select * from (select * from t2 limit 1) x   -- limit 1 directly fails in a union!
    UNION ALL
    select t2.* from r, t2 where t2.k = r.n      -- on each iteration, keep only the value that matches the previous precalculated next one
)
select k from r

Testen

Diese Alternative scheint für sehr kleine Sets weniger effizient zu sein, aber mehr oder weniger linear in der Leistung, während das Original exponentiell träger zu sein scheint.

drop table if exists t;
create temp table t(k) AS
with recursive r(n) as (
  select floor(random()*10)::int + 1
  UNION ALL
  select n + floor(random()*10)::int + 1
  from r
  where n < 100000)        -- change to increase or reduce set
select * from r;           -- surprisingly fast! Go PG!
create index on t(k);

with recursive r(n, pri) as (
    select min(k), 1::bigint from t
    UNION
    select k, (rank() over(order by k)) rr
    from r, t 
    where t.k >= r.n+5 and r.pri = 1
)
select count(*) from r where pri = 1; -- I aborted it after waiting for a minute

with recursive r(k, n) as (
    with t2(k,n) AS (select k, (select min(k) from t tt where k >= t.k+5) from t)
    select * from (select * from t2 limit 1) x
    UNION ALL
    select t2.* from r, t2 where t2.k = r.n
)
select count(*) from r -- 26" in my server
Ziggy Crueltyfree Zeitgeister
quelle
2

Normalerweise können Sie Fensteraggregatfunktionen verwenden, wenn Sie auf die Daten der vorherigen Zeile zugreifen müssen. Hier basiert die Berechnung der aktuellen Zeile jedoch auf dem vorherigen Ergebnis und nicht auf der Zeile. Für diese Art von Frage habe ich nie eine satzbasierte Lösung mit akzeptabler Leistung gefunden.

Es ist jedoch einfach, zeilenweise zu schreiben. Ich bevorzuge es, a ROW_NUMBERin einer temporären Tabelle zu materialisieren , dann ist das Finden der nächsten Zeile einfach n+1:

CREATE TABLE temp AS
 ( SELECT k, 
      ROW_NUMBER() OVER (ORDER BY k) AS rn 
   FROM t
 )
;
-- I don't know enough about PostgreSQL, but this is probably needed for performance
create unique index on temp(rn)
;

WITH RECURSIVE cte(k,rn, k1) AS 
 (
   SELECT k, rn, k AS k1
   FROM tmp 
   WHERE rn = 1  -- start with the minimum value
   UNION ALL
   SELECT tmp.k, tmp.rn,      
      CASE 
        WHEN tmp.k >= cte.k1 + 5 
        THEN tmp.k   -- use the new value
        ELSE cte.k1  -- keep the old value
      END 
   FROM tmp JOIN cte
     ON tmp.rn = cte.rn+1 -- next row
)
SELECT count(k) 
FROM cte
WHERE k = k1

Dies geht ziemlich schnell, ich habe @ ZiggyCrueltyfreeZeitgeisters Geige entführt .

Tatsächlich handelt es sich um eine Cursor-Logik-Verarbeitung alten Stils, und ein Cursor ist möglicherweise schneller (Sie benötigen keine temporäre Tabelle plus ROW_NUMBERund Rekursion). Meine PostgreSQL-Kenntnisse sind sehr begrenzt und ich weiß nicht, was in PG-Land bevorzugt wird :)

dnoeth
quelle