Chọn chuỗi liên tục dài nhất


12

Tôi đang cố gắng xây dựng một truy vấn trong PostgreQuery 9.0 để có chuỗi các hàng liên tục dài nhất cho một cột cụ thể.

Hãy xem xét bảng sau:

lap_id (serial), lap_no (int), car_type (enum), race_id (int FK)

Đâu lap_nolà duy nhất cho mỗi (race_id, car_type).

Tôi muốn truy vấn tạo ra chuỗi dài nhất cho một chuỗi nhất định race_idcar_typedo đó, nó sẽ trả về một int(hoặc dài) cao nhất.

Với dữ liệu sau:

1, 1, red, 1
2, 2, red, 1
3, 3, red, 1
4, 4, red, 1
5, 1, blue, 1
6, 5, red, 1
7, 2, blue, 1
8, 1, green, 1

Đối với car_type = red and race_id = 1truy vấn sẽ trả về 5là chuỗi dài nhất của lap_notrường.

Tôi tìm thấy một câu hỏi tương tự ở đây tuy nhiên tình huống của tôi đơn giản hơn một chút.

(Tôi cũng muốn biết chuỗi dài nhất được đưa ra car_typecho tất cả các chủng tộc, nhưng đã lên kế hoạch để tự mình giải quyết nó.)

Câu trả lời:


20

Mô tả của bạn dẫn đến một định nghĩa bảng như thế này:

CREATE TABLE tbl (
   lap_id   serial PRIMARY KEY
 , lap_no   int NOT NULL
 , car_type enum NOT NULL
 , race_id  int NOT NULL  -- REFERENCES ...
 , UNIQUE(race_id, car_type, lap_no)
);

Giải pháp chung cho lớp vấn đề này

Để có được chuỗi dài nhất (1 kết quả, dài nhất trong tất cả, chọn tùy ý nếu có quan hệ):

SELECT race_id, car_type, count(*) AS seq_len
FROM  (
   SELECT *, count(*) FILTER (WHERE step)
                      OVER (ORDER BY race_id, car_type, lap_no) AS grp
   FROM  (
      SELECT *, (lag(lap_no) OVER (PARTITION BY race_id, car_type ORDER BY lap_no) + 1)
                 IS DISTINCT FROM lap_no AS step
      FROM   tbl
      ) x
   ) y
GROUP  BY race_id, car_type, grp
ORDER  BY seq_len DESC
LIMIT  1;

count(*) FILTER (WHERE step)chỉ tính TRUE(= bước tới nhóm tiếp theo), dẫn đến một số mới cho mỗi nhóm mới.

Câu hỏi liên quan về SO, một câu trả lời có giải pháp thủ tục với plpgsql :

Nếu yêu cầu hàng đầu là hiệu năng, thì hàm plpgsql thường nhanh hơn trong trường hợp cụ thể này vì nó có thể tính kết quả trong một lần quét.

Nhanh hơn cho các số liên tiếp

Chúng ta có thể tận dụng thực tế là liên tiếp lap_no xác định một chuỗi, cho phiên bản đơn giản hơn và nhanh hơn nhiều :

SELECT race_id, car_type, count(*) AS seq_len
FROM  (
   SELECT race_id, car_type
        , row_number() OVER (PARTITION BY race_id, car_type ORDER BY lap_no) - lap_no AS grp
   FROM   tbl
   ) x
GROUP  BY race_id, car_type, grp
ORDER  BY seq_len DESC
LIMIT  1;

Vòng đua liên tiếp kết thúc trong cùng grp. Mỗi vòng thiếu kết quả trong một grpphân vùng thấp hơn .

Điều này dựa trên (race_id, car_type, lap_no)việc UNIQUE NOT NULL. Giá trị NULL hoặc trùng lặp có thể phá vỡ logic.

Thảo luận về sự thay thế đơn giản hơn của Jack

@ Phiên bản của Jack hiệu quả đếm tất cả các vòng (hàng), nơi trước đó lap_notrong này race_idcó giống nhau car_type. Điều đó đơn giản hơn và nhanh hơn và chính xác - miễn là mỗi car_typechỉ có thể có một chuỗi trên mỗi race_id.

Nhưng đối với một nhiệm vụ đơn giản, truy vấn có thể đơn giản hơn. Nó sẽ tuân theo logic rằng tất cả lap_nomỗi (car_type, race_id)phải theo trình tự và chúng tôi chỉ có thể đếm các vòng:

SELECT race_id, car_type, count(*) AS seq_len
FROM   tbl
GROUP  BY race_id, car_type
ORDER  BY seq_len DESC
LIMIT  1;

Mặt khác, nếu một người car_typecó thể có nhiều chuỗi riêng biệt cho mỗi racing_id (và câu hỏi không chỉ định khác), phiên bản của Jack sẽ thất bại.

Nhanh hơn cho một loại xe đua / xe nhất định

Trả lời nhận xét / làm rõ trong câu hỏi: hạn chế truy vấn cho một truy vấn nhất định(race_id, car_type) sẽ làm cho nó nhanh hơn nhiều , tất nhiên:

SELECT count(*) AS seq_len
FROM  (
   SELECT row_number() OVER (ORDER BY lap_no) - lap_no AS grp
   FROM   tbl
   WHERE  race_id = 1
   AND    car_type = 'red'
   ) x
GROUP  BY grp
ORDER  BY seq_len DESC
LIMIT  1;

db <> fiddle ở đây Fiddle SQL

Mục lục

Chìa khóa để đạt hiệu suất cao nhất là một chỉ số phù hợp (ngoại trừ giải pháp thủ tục được đề cập làm việc với một lần quét tuần tự duy nhất). Một chỉ mục nhiều màu như thế này phục vụ tốt nhất:

CREATE INDEX tbl_mult_idx ON tbl (race_id, car_type, lap_no);

Nếu bảng của bạn có UNIQUEràng buộc mà tôi giả định ở trên cùng, điều đó được triển khai chỉ với chỉ mục (duy nhất) này trong nội bộ và bạn không cần tạo chỉ mục khác.


Xin chào Erwin, cảm ơn vì công việc đó, tuy nhiên phải mất ~ 17 giây trên cơ sở dữ liệu của tôi! Đừng cho rằng bạn có thể cung cấp một sửa đổi để nó lấy racing_id và car_type làm tham số thay vì so sánh toàn bộ bảng? (Tôi đã thử viết lại và tiếp tục gặp lỗi)
DaveB ngày

7

create table tbl (lap_no int, car_type text, race_id int);
insert into tbl values (1,'red',1),(2,'red',1),(3,'red',1),(4,'red',1),
                       (1,'blue',1),(5,'red',1),(2,'blue',1),(1,'green',1);
select car_type, race_id, sum(case when lap_no=(prev+1) then 1 else 0 end)+1 seq_len
from ( select *, lag(lap_no) over (partition by car_type, race_id order by lap_no) prev 
       from tbl ) z
group by car_type, race_id
order by seq_len desc limit 1;
/*
|car_type|race_id|seq_len|
|:-------|------:|------:|
|red     |      1|      5|
*/

hoặc có lẽ sum((lap_no=(prev+1))::integer)+1nhưng tôi không chắc là dễ đọc hơn
Jack nói hãy thử topanswers.xyz
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.