web-dev-qa-db-ja.com

テーブルから "n"の連続する無料の番号を見つける

私はこのような番号を持ついくつかのテーブルを持っています(ステータスは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番号のグループを生成しますが、条件を満たす最初のグループのみからすべての番号を取得したいと思います

SQLフィドル

17
boobiq

これは ギャップと島 の問題です。同じ_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のインスタンスを使用します。

17
Andriy M

シンプルで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 拡張テストケースと両方のクエリ。

密接に関連する答え:

10

これは、これを行うためのかなり一般的な方法です。

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')
8
JNK

これは、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 ;
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 ;
_

この場合、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 ;
_
0
Ununoctium