私はこのような番号を持ついくつかのテーブルを持っています(ステータスはFREEまたはASSIGNEDのいずれかです)
id_set number status ----------------------- 1 000001 ASSIGNED 1 000002 FREE 1 000003 ASSIGNED 1 000004 FREE 1 000005 FREE 1 000006 ASSIGNED 1 000007 ASSIGNED 1 000008 FREE 1 000009 FREE 1 000010 FREE 1 000011 ASSIGNED 1 000012 ASSIGNED 1 000013 ASSIGNED 1 000014 FREE 1 000015 ASSIGNED
そして、私は "n"の連続番号を見つける必要があるので、n = 3の場合、クエリは
1 000008無料 1 000009無料 1 000010無料
各id_setの最初の可能なグループのみを返す必要があります(実際、クエリごとにid_setに対してのみ実行されます)
私はWINDOW関数をチェックし、COUNT(id_number) OVER (PARTITION BY id_set ROWS UNBOUNDED PRECEDING)
のようなクエリをいくつか試しましたが、それは私が得たすべてです:) Postgresでそれを行う方法、ロジックを考えることができませんでした。
Status = 'FREE'であるすべての数値の前の行をカウントするWINDOW関数を使用して仮想列を作成し、最初の数値を選択します。ここで、countは「n」の数値と同じです。
または、ステータスごとに番号をグループ化することもできますが、あるASSIGNEDから別のASSIGNEDへのみ行い、少なくとも「n」個の番号を含むグループのみを選択します。
[〜#〜]編集[〜#〜]
私はこのクエリを見つけました(そして少し変更しました)
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
fREE/ASSIGNED番号のグループを生成しますが、条件を満たす最初のグループのみからすべての番号を取得したいと思います
これは ギャップと島 の問題です。同じ_id_set
_セットにギャップまたは重複がないと仮定します。
_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
;
_
これがSQL Fiddle demo* このクエリのリンク: http://sqlfiddle.com/#!1/a2633/1 。
[〜#〜]更新[〜#〜]
1セットのみを返すには、ランク付けをもう1つ追加します。
_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
;
_
これもこのデモです: http://sqlfiddle.com/#!1/a2633/2 。
これを1セットにする必要がある場合は、per _id_set
_の場合、RANK()
呼び出しを次のように変更します。
RANK() OVER (PARTITION BY id_set ORDER BY grp) AS rnk
さらに、次のように、クエリが最小の一致するセットを返すようにすることもできます(つまり、最初に3つの連続する数値の最初のセットが存在する場合はそれを返し、それ以外の場合は4、5などを返します)。
RANK() OVER (ORDER BY cnt, id_set, grp) AS rnk
またはこのように(_id_set
_ごとに1つ):
RANK() OVER (PARTITION BY id_set ORDER BY cnt, grp) AS rnk
*この回答にリンクされているSQL Fiddleデモは、9.2.1のインスタンスが現時点では機能していないように見えるため、9.1.8のインスタンスを使用します。
シンプルでfastバリアント:
_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;
_
number
にギャップのない一連の数値が必要です(質問で提供されているとおり)。
status
を使用した場合でも、_'FREE'
_以外のNULL
の任意の数の値に対して機能します。
主な機能は、対象外の行を削除した後、number
からrow_number()
を減算することです。連続する数値は同じgrp
になり、grp
も昇順であることが保証されます。
次に、_GROUP BY grp
_を実行してメンバーをカウントできます。 firstの出現、_ORDER BY grp LIMIT 1
_が必要と思われるため、シーケンスの開始位置と長さを取得します(> =n)。
実際の数値のセットを取得するには、もう一度テーブルを参照しないでください。 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;
_
値の例に表示されているように、先頭にゼロが付いた文字列が実際に必要な場合は、_ to_char()
をFM
(フィルモード)修飾子とともに使用します。
_SELECT to_char(generate_series(8, 11), 'FM000000')
_
SQL Fiddle 拡張テストケースと両方のクエリ。
密接に関連する答え:
これは、これを行うためのかなり一般的な方法です。
number
列が連続していることに依存していることに注意してください。それがウィンドウ関数またはCTEタイプソリューションでない場合は、おそらく必要になります。
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')
これは、3つの数値の最初の数値のみを返します。 number
の値が連続している必要はありません。 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 ;
そして、これはすべての数字を表示します(3つ以上連続している場合'FREE'
位置):
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 ;
_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 ;
_
この場合、5つの連続した数値-したがって、差は4、つまりcount(r3.number) = n
と_r2.number = r1.number + n - 1
_でなければなりません。
結合あり:
_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 ;
_