테이블에서 "n"개의 연속 사용 가능한 빈 번호 찾기


17

나는 이와 같은 숫자가있는 테이블을 가지고 있습니다 (상태는 FREE 또는 ASSIGNED입니다)

id_set 번호 상태         
-----------------------
1 000001 할당
1 000002 무료
1 000003 할당
1 000004 무료
1 000005 무료
1 000006 할당
1 000007 할당
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 함수를 사용하여 가상 열을 생성 한 다음 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

이것은 문제입니다. 동일한 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는 .

최신 정보

한 세트 만 반환하려면 순위를 한 번 더 추가하면됩니다.

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 .

만약 당신이 한 세트를 만들 필요가 경우 에 따라id_set , 변경 RANK()이 같은 전화 :

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

또한 다음과 같이 쿼리가 가장 일치하는 가장 작은 집합을 반환하도록 할 수 있습니다 (즉, 먼저 세 개의 연속 숫자가있는 경우 첫 번째 집합, 그렇지 않으면 4, 5 등).

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

또는 이와 같이 ( id_set)

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

*이 답변에 연결된 SQL Fiddle 데모는 91.8 인스턴스가 현재 작동하지 않는 것이므로 9.1.8 인스턴스를 사용합니다.


대단히 감사합니다. 멋지지만 첫 번째 숫자 그룹 만 반환되도록 변경할 수 있습니까? cnt> = 2로 변경하면 5 개의 숫자 (2 개의 그룹 = 2 + 3 개의 숫자)가 표시됩니다.
boobiq

@boobiq : 당신은 하나 id_set또는 하나만 원하십니까? 처음부터 시작된 부분 인 경우 질문을 업데이트하십시오. (다른 사람들이 전체 요구 사항을보고 제안을 제안하거나 답변을 업데이트 할 수 있도록)
Andriy M

내 질문을 편집하고 (원하는 반환 후) 하나의 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;

예제 값에 표시하는 것처럼 0이 앞에 오는 문자열을 실제로 원한다면 (채우기 모드) 수정 자 to_char()와 함께 사용 하십시오 FM.

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

확장 된 테스트 사례 및 두 쿼리가 포함 된 SQL Fiddle

밀접한 관련 답변 :


8

이것은 매우 일반적인 방법입니다.

number연속되는 열에 따라 다릅니다 . Window 함수가 아니거나 CTE type-solution이 필요한 경우 :

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).
Andriy M

@AndriyM 글쎄, 그것은 "멋진"것이 아닙니다. 그것은 그 number필드의 순차적 인 값에 의존하기 때문에 깨지기 쉽습니다 . 수학을 잘 부르면 수정하겠습니다.
JNK

2
Postgres의 구문을 수정하기 위해 자유를 얻었습니다. 첫 번째 EXISTS는 단순화 될 수 있습니다. 우리는 확인해야하기 때문에 어떤 N 이전 행이 존재, 우리는 놓을 수 있습니다 AND status = 'FREE'. 그리고 나중에 추가 옵션에 대비하여 강화 EXISTS하기 status <> 'FREE'위해 두 번째 조건을 변경합니다 .
Erwin Brandstetter

5

세 숫자 중 첫 번째 숫자 만 반환합니다. 값 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에서 작동하는 솔루션을 제공했습니다.
우 누녹 티움

-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가 많은 수의 행을 반환해야합니다
Ravi Ramaswamy

코드를 강조 표시 { }하고 편집기 에서 버튼을 눌러 답변을 다시 포맷했습니다 . 즐겨!
jcolebrand

답을 편집하고 커서가 더 나은 성능을 제공한다고 생각하는 이유를 알 수도 있습니다.
jcolebrand

커서는 순차적 프로세스입니다. 그것은 한 번에 한 레코드 씩 플랫 파일을 읽는 것과 거의 같습니다. 상황 중 하나에서 MEM TEMP 테이블을 하나의 단일 커서로 바꿨습니다. 이로 인해 처리 시간이 26 시간에서 6 시간으로 줄었습니다. 결과 집합을 반복하기 위해 neseted WHILE을 사용해야했습니다.
라비 라마 스와미

가정을 테스트하기 위해 노력한 적이 있습니까? 당신은 놀랄 수 있습니다. 일반적인 경우를 제외하고 일반 SQL이 가장 빠릅니다.
Erwin Brandstetter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.