Tìm số điện thoại liên tiếp miễn phí từ bảng


16

Tôi có một số bảng có các số như thế này (trạng thái là MIỄN PHÍ hoặc được gán)

trạng thái số id_set         
-----------------------
1 000001 đã ký
1 000002 MIỄN PHÍ
1 000003 Đã ký
1 000004 MIỄN PHÍ
1 000005 MIỄN PHÍ
1 000006 đã ký
1 000007 đã ký
1 000008 MIỄN PHÍ
1 000009 MIỄN PHÍ
1 000010 MIỄN PHÍ
1 000011 đã ký
1 000012 đã ký
1 000013 đã ký
1 000014 MIỄN PHÍ
1 000015 đã ký

và tôi cần tìm các số liên tiếp "n", vì vậy với n = 3, truy vấn sẽ trả về

1 000008 MIỄN PHÍ
1 000009 MIỄN PHÍ
1 000010 MIỄN PHÍ

Nó sẽ chỉ trả về nhóm có thể đầu tiên của mỗi id_set (trên thực tế, nó sẽ chỉ được thực thi cho id_set cho mỗi truy vấn)

Tôi đã kiểm tra các hàm WINDOW, đã thử một số truy vấn như thế COUNT(id_number) OVER (PARTITION BY id_set ROWS UNBOUNDED PRECEDING), nhưng đó là tất cả những gì tôi có :) Tôi không thể nghĩ về logic, làm thế nào để làm điều đó trong Postgres.

Tôi đã suy nghĩ về việc tạo cột ảo bằng cách sử dụng các hàm WINDOW đếm các hàng trước cho mỗi số trong đó status = 'MIỄN PHÍ', sau đó chọn số đầu tiên, trong đó số đếm bằng số "n" của tôi.

Hoặc có thể nhóm số theo trạng thái, nhưng chỉ từ một người được gán cho người khác được chỉ định và chỉ chọn các nhóm có ít nhất số "n"

BIÊN TẬP

Tôi tìm thấy truy vấn này (và thay đổi nó một chút)

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

nơi tạo ra các nhóm số MIỄN PHÍ / ĐƯỢC GỬI, nhưng tôi muốn có tất cả các số từ chỉ nhóm đầu tiên đáp ứng điều kiện

Câu đố SQL

Câu trả lời:


16

Đây là một vấn đề . Giả sử không có khoảng trống hoặc trùng lặp trong cùng một id_setbộ:

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
;

Đây là một liên kết demo * Fiddle SQL cho truy vấn này: http://sqlfiddle.com/#!1/a2633/1 .

CẬP NHẬT

Để chỉ trả lại một bộ, bạn có thể thêm một vòng xếp hạng nữa:

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
;

Đây cũng là bản demo cho cái này: http://sqlfiddle.com/#!1/a2633/2 .

Nếu bạn cần đặt mỗiid_set bộ một lần , hãy thay đổi RANK()cuộc gọi như sau:

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

Ngoài ra, bạn có thể làm cho truy vấn trả về bộ khớp nhỏ nhất (nghĩa là lần đầu tiên thử trả về bộ đầu tiên của chính xác ba số liên tiếp nếu nó tồn tại, nếu không thì bốn, năm, v.v.), như sau:

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

hoặc như thế này (một cho mỗi id_set):

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

* Các bản trình diễn SQL Fiddle được liên kết trong câu trả lời này sử dụng phiên bản 9.1.8 vì phiên bản 9.2.1 dường như không hoạt động vào lúc này.


Cảm ơn bạn rất nhiều, điều này có vẻ tốt, nhưng có thể thay đổi nó để chỉ nhóm số đầu tiên được trả về? Nếu tôi thay đổi nó để cnt> = 2, sau đó tôi nhận được 5 số (2 nhóm = 2 + 3 số)
boobiq

@boobiq: Bạn muốn một cái id_sethay chỉ một cái? Vui lòng cập nhật câu hỏi của bạn nếu điều này có nghĩa là một phần của nó ngay từ đầu. (Để những người khác có thể thấy các yêu cầu đầy đủ và đưa ra đề xuất hoặc cập nhật câu trả lời của họ.)
Andriy M

Tôi đã chỉnh sửa câu hỏi của mình (sau khi muốn quay trở lại), nó sẽ chỉ được thực hiện cho một id_set, vì vậy chỉ có nhóm đầu tiên có thể được tìm thấy
boobiq

10

Một biến thể đơn giản và nhanh chóng :

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;
  • Yêu cầu một dãy số không có khoảng cách trong number(như được cung cấp trong câu hỏi).

  • Hoạt động cho bất kỳ số lượng các giá trị có thể statusbên cạnh 'FREE', ngay cả với NULL.

  • Các tính năng chính là để trừ row_number()từ numbersau khi loại bỏ các hàng không được hội đủ điều kiện. Các số liên tiếp kết thúc giống nhau grp- và grpcũng được đảm bảo theo thứ tự tăng dần .

  • Sau đó, bạn có thể GROUP BY grpvà đếm các thành viên. Vì bạn dường như muốn lần xuất hiện đầu tiên , ORDER BY grp LIMIT 1và bạn có được vị trí bắt đầu và độ dài của chuỗi (có thể> = n ).

Tập hợp các hàng

Để có được một bộ số thực tế, đừng tìm bảng khác vào lúc khác. Rẻ hơn nhiều với 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;

Nếu bạn thực sự muốn một chuỗi có các số 0 đứng đầu như bạn hiển thị trong các giá trị mẫu của mình, hãy sử dụng to_char()với công cụ FMsửa đổi (chế độ điền):

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

SQL Fiddle với trường hợp thử nghiệm mở rộng và cả hai truy vấn.

Câu trả lời liên quan chặt chẽ:


8

Đây là một cách khá chung chung để làm điều này.

Hãy nhớ rằng nó phụ thuộc vào numbercột của bạn là liên tiếp. Nếu đó không phải là chức năng Window và / hoặc giải pháp loại CTE có thể sẽ cần thiết:

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

Tuyên bố sẽ không hoạt động như thế trong Postgres.
a_horse_with_no_name 18/03/13

@a_horse_with_no_name Xin vui lòng sửa nó sau đó :)
JNK

Không có chức năng cửa sổ, rất đẹp! Mặc dù tôi nghĩ nó nên như vậy M.number-consec+1(ví dụ cho 10 thì nó sẽ cần 10-3+1=8).
Andriy M

@AndriyM Chà nó không "đẹp", nó dễ vỡ vì nó phụ thuộc vào các giá trị tuần tự của numbertrường đó . Gọi tốt về toán tôi sẽ sửa nó.
JNK

2
Tôi lấy tự do để sửa cú pháp cho Postgres. cái đầu tiên EXISTScó thể được đơn giản hóa. Vì chúng ta chỉ cần đảm bảo bất kỳ n hàng nào trước đó tồn tại, chúng ta có thể bỏ qua AND status = 'FREE'. Và tôi sẽ thay đổi tình trạng này trong 2 EXISTSđể status <> 'FREE'làm vững chắc chống lại tùy chọn thêm trong tương lai.
Erwin Brandstetter 18/03/13

5

Điều này sẽ chỉ trả lại đầu tiên trong số 3 số. Nó không yêu cầu rằng các giá trị numberlà liên tiếp. Đã thử nghiệm tại 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 ;

Và điều này sẽ hiển thị tất cả các số (trong đó có 3 'FREE'vị trí liên tiếp trở lên ):

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 ;

Trong trường hợp này, 5 số liên tiếp - do đó sự khác biệt phải là 4 hoặc nói cách khác count(r3.number) = nr2.number = r1.number + n - 1.

Với sự tham gia:

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 ;

Bạn nghĩ rằng một sản phẩm cartesian 4 chiều là một cách hiệu quả để làm điều này?
JNK

Hoặc bạn có thể viết nó với JOINcú pháp hiện đại ?
JNK

Vâng, tôi không muốn dựa vào các chức năng của cửa sổ và đưa ra một giải pháp sẽ hoạt động trên bất kỳ sql-db nào.
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

Tôi đang sử dụng con trỏ để có hiệu suất tốt hơn - nếu CHỌN trả về số lượng lớn hàng
Ravi Ramaswamy

Tôi định dạng lại câu trả lời của bạn bằng cách tô sáng mã và nhấn { }nút trên trình chỉnh sửa. Thưởng thức!
jcolebrand

Bạn cũng có thể muốn chỉnh sửa câu trả lời của mình và cho biết lý do tại sao bạn nghĩ con trỏ cung cấp hiệu suất tốt hơn.
jcolebrand

Con trỏ là một quá trình tuần tự. Nó gần giống như đọc một tập tin phẳng một bản ghi tại một thời điểm. Trong một trong các tình huống, tôi đã thay thế bảng MEM TEMP bằng một con trỏ. Điều này làm giảm thời gian xử lý từ 26 giờ xuống còn 6 giờ. Tôi đã phải sử dụng WHILE không cần thiết để lặp qua tập kết quả.
Ravi Ramaswamy

Bạn đã bao giờ thử kiểm tra trường hợp giả định của bạn chưa? Bạn có thể ngạc nhiên. Ngoại trừ trường hợp góc SQL đơn giản là nhanh nhất.
Erwin Brandstetter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.