it-swarm.com.de

Suchen Sie "n" aufeinanderfolgende freie Nummern aus der Tabelle

Ich habe eine Tabelle mit Zahlen wie diesen (Status ist entweder KOSTENLOS oder ZUGEWIESEN)

 id_set number status 
 ----------------------- 
 1 000001 ASSIGNED 
 1 000002 KOSTENLOS 
 1 000003 ZUGEWIESEN 
 1 000004 KOSTENLOS 
 1 000005 KOSTENLOS 
 1 000006 ZUGEWIESEN 
 1 000007 ZUGEWIESEN 
 1 000008 KOSTENLOS 
 1 000009 FREE 
 1 000010 FREE 
 1 000011 ASSIGNED 
 1 000012 ASSIGNED 
 1 000013 ASSIGNED 
 1 000014 FREE 
 1 000015 ZUGEWIESEN

und ich muss "n" fortlaufende Zahlen finden, so dass für n = 3 die Abfrage zurückkehren würde

 1 000008 KOSTENLOS 
 1 000009 KOSTENLOS 
 1 000010 KOSTENLOS 

Es sollte nur die erste mögliche Gruppe jedes id_set zurückgeben (tatsächlich würde es nur für id_set pro Abfrage ausgeführt werden).

Ich habe WINDOW-Funktionen überprüft und einige Abfragen wie COUNT(id_number) OVER (PARTITION BY id_set ROWS UNBOUNDED PRECEDING) ausprobiert, aber das ist alles, was ich habe :) Ich konnte mir keine Logik vorstellen, wie das in Postgres geht.

Ich habe darüber nachgedacht, eine virtuelle Spalte mit WINDOW-Funktionen zu erstellen, die vorhergehende Zeilen für jede Nummer zählen, bei der status = 'FREE' ist, und dann die erste Nummer ausgewählt, bei der count meiner "n" -Nummer entspricht.

Oder gruppieren Sie die Nummern nach Status, aber nur von einer ZUGEWIESENEN zu einer anderen ZUGEWIESENEN und wählen Sie nur Gruppen aus, die mindestens "n" Nummern enthalten

[~ # ~] edit [~ # ~]

Ich habe diese Abfrage gefunden (und ein wenig geändert)

WITH q AS
(
  SELECT *,
         ROW_NUMBER() OVER (PARTITION BY id_set, status ORDER BY number) AS rnd,
         ROW_NUMBER() OVER (PARTITION BY id_set ORDER BY number) AS rn
  FROM numbers
)
SELECT id_set,
       MIN(number) AS first_number,
       MAX(number) AS last_number,
       status,
       COUNT(number) AS numbers_count
FROM q
GROUP BY id_set,
         rnd - rn,
         status
ORDER BY
     first_number

das erzeugt Gruppen von FREE/ASSIGNED-Nummern, aber ich möchte alle Nummern nur von der ersten Gruppe haben, die die Bedingung erfüllt

SQL Fiddle

17
boobiq

Dies ist ein Lücken und Inseln Problem. Angenommen, es gibt keine Lücken oder Duplikate in derselben id_set - Menge:

WITH partitioned AS (
  SELECT
    *,
    number - ROW_NUMBER() OVER (PARTITION BY id_set) AS grp
  FROM atable
  WHERE status = 'FREE'
),
counted AS (
  SELECT
    *,
    COUNT(*) OVER (PARTITION BY id_set, grp) AS cnt
  FROM partitioned
)
SELECT
  id_set,
  number
FROM counted
WHERE cnt >= 3
;

Hier ist eine SQL Fiddle Demo* * Link für diese Abfrage: http://sqlfiddle.com/#!1/a2633/1 .

[~ # ~] Update [~ # ~]

Um nur einen Satz zurückzugeben, können Sie in einer weiteren Rangliste hinzufügen:

WITH partitioned AS (
  SELECT
    *,
    number - ROW_NUMBER() OVER (PARTITION BY id_set) AS grp
  FROM atable
  WHERE status = 'FREE'
),
counted AS (
  SELECT
    *,
    COUNT(*) OVER (PARTITION BY id_set, grp) AS cnt
  FROM partitioned
),
ranked AS (
  SELECT
    *,
    RANK() OVER (ORDER BY id_set, grp) AS rnk
  FROM counted
  WHERE cnt >= 3
)
SELECT
  id_set,
  number
FROM ranked
WHERE rnk = 1
;

Hier ist auch eine Demo für dieses: http://sqlfiddle.com/#!1/a2633/2 .

Wenn Sie jemals einen Satz machen müssen per id_set, ändern Sie den Aufruf RANK() wie folgt:

RANK() OVER (PARTITION BY id_set ORDER BY grp) AS rnk

Außerdem können Sie die Abfrage so einstellen, dass sie den kleinsten übereinstimmenden Satz zurückgibt (d. H. Zuerst versuchen, den ersten Satz mit genau drei aufeinander folgenden Zahlen zurückzugeben, falls vorhanden, andernfalls vier, fünf usw.).

RANK() OVER (ORDER BY cnt, id_set, grp) AS rnk

oder so (eine pro id_set):

RANK() OVER (PARTITION BY id_set ORDER BY cnt, grp) AS rnk

* Die in dieser Antwort verknüpften SQL Fiddle -Demos verwenden die 9.1.8-Instanz, da die 9.2.1-Instanz derzeit nicht zu funktionieren scheint.

17
Andriy M

Eine einfache und schnelle Variante:

SELECT min(number) AS first_number, count(*) AS ct_free
FROM (
    SELECT *, number - row_number() OVER (PARTITION BY id_set ORDER BY number) AS grp
    FROM   tbl
    WHERE  status = 'FREE'
    ) x
GROUP  BY grp
HAVING count(*) >= 3  -- minimum length of sequence only goes here
ORDER  BY grp
LIMIT  1;
  • Erfordert eine lückenlose Folge von Zahlen in number (wie in der Frage angegeben).

  • Funktioniert für eine beliebige Anzahl möglicher Werte in status außer 'FREE', Auch mit NULL.

  • Das Hauptmerkmal besteht darin, row_number() von number zu subtrahieren, nachdem nicht qualifizierende Zeilen entfernt wurden. Aufeinanderfolgende Zahlen enden in derselben grp - und grp ist garantiert auch in aufsteigender Reihenfolge.

  • Dann können Sie GROUP BY grp Die Mitglieder zählen. Da Sie das Vorkommen zuerst zu wollen scheinen, ORDER BY grp LIMIT 1 Und Sie erhalten Startposition und Länge der Sequenz (kann> = n sein) .

Satz von Zeilen

Um einen tatsächlichen Satz von Zahlen zu erhalten, schauen Sie nicht ein anderes Mal in der Tabelle nach. Viel billiger mit generate_series() :

SELECT generate_series(first_number, first_number + ct_free - 1)
    -- generate_series(first_number, first_number + 3 - 1) -- only 3
FROM  (
   SELECT min(number) AS first_number, count(*) AS ct_free
   FROM  (
      SELECT *, number - row_number() OVER (PARTITION BY id_set ORDER BY number) AS grp
      FROM   tbl
      WHERE  status = 'FREE'
      ) x
   GROUP  BY grp
   HAVING count(*) >= 3
   ORDER  BY grp
   LIMIT  1
   ) y;

Wenn Sie tatsächlich eine Zeichenfolge mit führenden Nullen möchten, wie Sie sie in Ihren Beispielwerten anzeigen, verwenden Sie to_char() mit dem Modifikator FM (Füllmodus):

SELECT to_char(generate_series(8, 11), 'FM000000')

SQL Fiddle mit erweitertem Testfall und beiden Abfragen.

Eng verwandte Antwort:

10

Dies ist ein ziemlich allgemeiner Weg, dies zu tun.

Beachten Sie, dass dies davon abhängt, ob Ihre Spalte number fortlaufend ist. Wenn es sich nicht um eine Fensterfunktion und/oder eine CTE-Lösung handelt, wird wahrscheinlich Folgendes benötigt:

SELECT 
    number
FROM
    mytable m
CROSS JOIN
   (SELECT 3 AS consec) x
WHERE 
    EXISTS
       (SELECT 1 
        FROM mytable
        WHERE number = m.number - x.consec + 1
        AND status = 'FREE')
    AND NOT EXISTS
       (SELECT 1 
        FROM mytable
        WHERE number BETWEEN m.number - x.consec + 1 AND m.number
        AND status = 'ASSIGNED')
8
JNK

Dies gibt nur die erste der 3 Zahlen zurück. Es ist nicht erforderlich, dass die Werte von number aufeinanderfolgend sind. Getestet bei SQL-Fiddle:

WITH cte3 AS
( SELECT
    *,
    COUNT(CASE WHEN status = 'FREE' THEN 1 END) 
        OVER (PARTITION BY id_set ORDER BY number
              ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
      AS cnt
  FROM atable
)
SELECT
  id_set, number
FROM cte3
WHERE cnt = 3 ;

Und dies zeigt alle Zahlen (wo es 3 oder mehr aufeinanderfolgende 'FREE' Positionen gibt):

WITH cte3 AS
( SELECT
    *,
    COUNT(CASE WHEN status = 'FREE' THEN 1 END) 
        OVER (PARTITION BY id_set ORDER BY number
              ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
      AS cnt
  FROM atable
)
, cte4 AS
( SELECT
    *, 
    MAX(cnt) 
        OVER (PARTITION BY id_set ORDER BY number
              ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
      AS maxcnt
  FROM cte3
)
SELECT
  id_set, number
FROM cte4
WHERE maxcnt >= 3 ;
5
ypercubeᵀᴹ
select r1.number from some_table r1, 
some_table r2,
some_table r3,
some_table r4 
where r3.number <= r2.number 
and r3.number >= r1.number 
and r3.status = 'FREE' 
and r2.number = r1.number + 4 
and r4.number <= r2.number 
and r4.number >= r1.number 
and r4.status = 'ASSIGNED'
group by r1.number, r2.number having count(r3.number) = 5 and count(r4.number) = 0 order by r1.number asc limit 1 ;

In diesem Fall 5 aufeinanderfolgende Zahlen - daher muss die Differenz 4 oder mit anderen Worten count(r3.number) = n und r2.number = r1.number + n - 1 Sein.

Mit Joins:

select r1.number 
from some_table r1 join 
 some_table r2 on (r2.number = r1.number + :n -1) join
 some_table r3 on (r3.number <= r2.number and r3.number >= r1.number) join
 some_table r4 on (r4.number <= r2.number and r4.number >= r1.number)
where  
 r3.status = 'FREE' and
 r4.status = 'ASSIGNED'
group by r1.number, r2.number having count(r3.number) = :n and count(r4.number) = 0 order by r1.number asc limit 1 ;
0
Ununoctium