表から「n」個の連続した無料番号を見つける


16

このような数字のテーブルがあります(ステータスはFREEまたはASSIGNEDです)

id_set番号ステータス         
-----------------------
1 000001割り当て済み
1 000002無料
1 000003割り当て済み
1 000004無料
1 000005無料
1 000006割り当て済み
10007割り当て済み
1 000008無料
1 000009無料
1 000010無料
1 000011割り当て済み
1 000012割り当て済み
1 000013割り当て済み
1 000014無料
1 000015割り当て済み

「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関数を使用して仮想列を作成し、最初の番号を選択することを考えていました。

または、ステータスごとに番号をグループ化しますが、1つの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フィドル

回答:


16

これは、問題です。同じ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フィドルのデモです*:このクエリのリンク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セットにする必要がある場合につきid_set、変更RANK()、このような呼び出しを:

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

さらに、次のように、クエリが最小の一致セットを返すようにすることができます(つまり、存在する場合は正確に3つの連続した数字の最初のセットを返します)。

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

またはこのように(ごとに1つid_set):

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

*この回答にリンクされているSQL Fiddleデモでは、現時点では機能していないように見える9.2.1として9.1.8インスタンスを使用しています。


どうもありがとう、これは良さそうに見えますが、最初の数字のグループのみが返されるように変更することは可能ですか?cnt> = 2に変更すると、5つの数字が得られます(2つのグループ= 2 + 3つの数字)
boobiq

@boobiq:1つずつ必要ですか id_setつだけですか、それともつだけですか?これが最初からの一部である場合、質問を更新してください。(そうすれば、他の人が完全な要件を確認し、提案を提供したり、回答を更新したりできます。)
Andriy M

質問を編集した後(返品を希望)、1つのid_setに対してのみ実行されるため、最初の可能なグループのみが見つかりました
-boobiq

10

シンプルで高速なバリアント:

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

  • 主な機能は、非修飾行を削除row_number()したnumber後から減算することです。連続した数字が同じに終わるgrp-とgrpもであることが保証され昇順

  • その後GROUP BY grp、メンバーを数えることができます。あなたは最初の出現を望んでいるよう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(fill mode)修飾子を使用します。

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

拡張テストケースと両方のクエリを備えた SQL Fiddle

密接に関連した答え:


8

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

number列が連続していることに依存することに注意してください。Window関数や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')

宣言はPostgresのようには機能しません。
-a_horse_with_no_name

@a_horse_with_no_nameそれを修正してお気軽に:)
JNK

ウィンドウ関数はありません、とてもいいです!私はそれがあるべきだと思いますがM.number-consec+1(例えば10の場合はそうする必要があります10-3+1=8)。
アンドリーM

@AndriyMまあ、それはそのnumberフィールドのシーケンシャルな値に依存しているので、「いい」ではなく、壊れやすいです。数学の呼び出しは私がそれを修正します。
JNK

2
Postgresの構文を修正するために自由を取りました。最初のものEXISTSは単純化できます。私たちは確認する必要がありますので、任意の nは、以前の行が存在し、私たちがドロップすることができますAND status = 'FREE'。そして、私は第二の状態を変更しますEXISTSstatus <> 'FREE'、将来的に追加されたオプションに対してそれを硬化させます。
アーウィンブランドステッター

5

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

0
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) = nr2.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 ;

4ウェイのデカルト積はこれを行う効率的な方法だと思いますか?
JNK

あるいは、最新のJOIN構文で作成できますか?
JNK

さて、私はウィンドウ関数に依存したくありませんでしたし、sql-dbで動作するソリューションを提供しました。
Ununoctium

-1
CREATE TABLE #ConsecFreeNums
(
     id_set BIGINT
    ,number VARCHAR(10)
    ,status VARCHAR(10)
)

CREATE TABLE #ConsecFreeNumsResult
(
     Seq    INT
    ,id_set BIGINT
    ,number VARCHAR(10)
    ,status VARCHAR(10)
)

INSERT #ConsecFreeNums
SELECT 1, '000002', 'FREE' UNION
SELECT 1, '000003', 'ASSIGNED' UNION
SELECT 1, '000004', 'FREE' UNION
SELECT 1, '000005', 'FREE' UNION
SELECT 1, '000006', 'ASSIGNED' UNION
SELECT 1, '000007', 'ASSIGNED' UNION
SELECT 1, '000008', 'FREE' UNION
SELECT 1, '000009', 'FREE' UNION
SELECT 1, '000010', 'FREE' UNION
SELECT 1, '000011', 'ASSIGNED' UNION
SELECT 1, '000012', 'ASSIGNED' UNION
SELECT 1, '000013', 'ASSIGNED' UNION
SELECT 1, '000014', 'FREE' UNION
SELECT 1, '000015', 'ASSIGNED'

DECLARE @id_set AS BIGINT, @number VARCHAR(10), @status VARCHAR(10), @number_count INT, @number_count_check INT

DECLARE ConsecFreeNumsCursor CURSOR FAST_FORWARD FOR
SELECT
       id_set
      ,number
      ,status
 FROM
      #ConsecFreeNums
WHERE id_set = 1
ORDER BY number

OPEN ConsecFreeNumsCursor

FETCH NEXT FROM ConsecFreeNumsCursor INTO @id_set, @number, @status

SET @number_count_check = 3
SET @number_count = 0

WHILE @@FETCH_STATUS = 0
BEGIN
    IF @status = 'ASSIGNED'
    BEGIN
        IF @number_count = @number_count_check
        BEGIN
            SELECT 'Results'
            SELECT * FROM #ConsecFreeNumsResult ORDER BY number
            BREAK
        END
        SET @number_count = 0
        TRUNCATE TABLE #ConsecFreeNumsResult
    END
    ELSE
    BEGIN
        SET @number_count = @number_count + 1
        INSERT #ConsecFreeNumsResult SELECT @number_count, @id_set, @number, @status
    END
    FETCH NEXT FROM ConsecFreeNumsCursor INTO @id_set, @number, @status
END

CLOSE ConsecFreeNumsCursor
DEALLOCATE ConsecFreeNumsCursor

DROP TABLE #ConsecFreeNums
DROP TABLE #ConsecFreeNumsResult

パフォーマンスを向上させるためにカーソルを使用しています-SELECTが多数の行を返す場合
ラヴィラマスワミ

コードを強調表示{ }し、エディターのボタンを押して、回答を再フォーマットしました。楽しい!
jcolebrand

また、回答を編集して、カーソルがパフォーマンスを向上させると考える理由を伝えることもできます。
jcolebrand

カーソルは順次プロセスです。これは、フラットファイルを一度に1レコードずつ読み取ることに似ています。状況の1つでは、MEM TEMPテーブルを1つの単一のカーソルに置き換えました。これにより、処理時間が26時間から6時間に短縮されました。結果セットをループするには、設定済みのWHILEを使用する必要がありました。
ラヴィラマスワミー

あなたの仮定をテストケースしようとしたことがありますか?びっくりするかもしれません。コーナーケースを除き、プレーンSQLが最も高速です。
アーウィンブランドステッター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.