เลือกลำดับต่อเนื่องที่ยาวที่สุด


12

ฉันพยายามสร้างแบบสอบถามใน PostgreSQL 9.0 ที่ได้รับลำดับของแถวต่อเนื่องที่ยาวที่สุดสำหรับคอลัมน์ที่ระบุ

พิจารณาตารางต่อไปนี้:

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

ในกรณีที่มีความเป็นเอกลักษณ์ของแต่ละlap_no(race_id, car_type)

ฉันต้องการให้คิวรีสร้างลำดับที่ยาวที่สุดสำหรับหนึ่งrace_idและcar_typeดังนั้นจึงส่งคืนint(หรือยาว) ที่สูงที่สุด

ด้วยข้อมูลต่อไปนี้:

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

สำหรับcar_type = red and race_id = 1แบบสอบถามจะกลับมา5เป็นลำดับที่ยาวที่สุดของlap_noสนาม

ฉันพบคำถามที่คล้ายกันที่นี่แต่สถานการณ์ของฉันค่อนข้างตรงไปตรงมา

(ฉันต้องการทราบลำดับที่ยาวที่สุดสำหรับการcar_typeแข่งขันทั้งหมด แต่วางแผนที่จะทำงานด้วยตัวเอง)

คำตอบ:


20

คำอธิบายของคุณส่งผลให้คำจำกัดความของตารางเช่นนี้:

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

วิธีแก้ปัญหาทั่วไปสำหรับปัญหาระดับนี้

ในการรับลำดับที่ยาวที่สุด (1 ผลลัพธ์ยาวที่สุดของทั้งหมดเลือกโดยพลหากมีความสัมพันธ์):

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)นับเฉพาะTRUE(= ขั้นตอนสู่กลุ่มถัดไป) ซึ่งจะส่งผลให้มีหมายเลขใหม่สำหรับทุกกลุ่มใหม่

คำถามที่เกี่ยวข้องกับ SO หนึ่งคำตอบที่มีวิธีการแก้ปัญหาขั้นตอนด้วย plpgsql :

หากความต้องการสูงสุดคือประสิทธิภาพการทำงานฟังก์ชั่น plpgsql จะเร็วขึ้นในกรณีนี้เพราะมันสามารถคำนวณผลลัพธ์ในการสแกนครั้งเดียว

เร็วขึ้นสำหรับตัวเลขที่ต่อเนื่องกัน

เราสามารถใช้ประโยชน์จากความจริงที่ว่าลำดับต่อเนื่อง lap_noกำหนดสำหรับรุ่นที่ง่ายกว่าและเร็วกว่า :

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;

grpรอบติดต่อกันจบลงในแบบเดียวกัน ทุกรอบที่หายไปจะส่งผลให้grpพาร์ติชั่นต่ำกว่า

สิ่งนี้ขึ้นอยู่กับ(race_id, car_type, lap_no)ความเป็นUNIQUE NOT NULLอยู่ ค่า NULL หรือรายการซ้ำอาจทำให้ลอจิกแตก

การอภิปรายของทางเลือกที่ง่ายกว่าของแจ็ค

@ รุ่นแจ็คได้อย่างมีประสิทธิภาพนับทุกรอบ (แถว) ที่ก่อนหน้าlap_noนี้ได้เหมือนกันrace_id car_typeนั่นคือเรียบง่ายและเร็วขึ้นและถูกต้อง - ตราบใดที่แต่ละอันcar_typeสามารถมีลำดับเดียวrace_idได้

แต่สำหรับงานที่ง่ายแบบสอบถามอาจจะง่ายกว่านี้ มันจะเป็นไปตามเหตุผลว่าทั้งหมดlap_noต่อ(car_type, race_id)จะต้องอยู่ในลำดับและเราก็สามารถนับรอบ:

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

หากในอีกด้านหนึ่งcar_typeสามารถมีหลายลำดับแยกกันต่อrace_id (และคำถามไม่ได้ระบุเป็นอย่างอื่น) รุ่นของแจ็คจะล้มเหลว

เร็วขึ้นสำหรับประเภทการแข่งขัน / รถยนต์ที่ระบุ

ในการตอบความเห็น / ชี้แจงในคำถาม: การ จำกัด แบบสอบถามไปยังหนึ่งที่ได้รับ (race_id, car_type)จะทำให้มันเร็วขึ้นมากของหลักสูตร:

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 ที่นี่
Old SQL Fiddle

ดัชนี

ประสิทธิภาพการทำงานของ Key to top เป็นดัชนีที่เหมาะสม (ยกเว้นโซลูชันขั้นตอนที่กล่าวถึงซึ่งทำงานด้วยการสแกนตามลำดับครั้งเดียว) ดัชนีหลายคอลัมน์เช่นนี้ให้บริการอาหารที่ดีที่สุด:

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

หากตารางของคุณมีUNIQUEข้อ จำกัด ที่ฉันสันนิษฐานว่าอยู่ด้านบนนั่นจะถูกนำมาใช้กับดัชนี (เฉพาะ) นี้ภายในและคุณไม่จำเป็นต้องสร้างดัชนีอื่น


สวัสดีเออร์วินขอบคุณที่ทำงานได้ แต่ใช้เวลา ~ 17 วินาทีบนฐานข้อมูลของฉัน! อย่าสมมติว่าคุณสามารถให้การปรับเปลี่ยนได้ดังนั้นจึงควรใช้ race_id และ car_type เป็นพารามิเตอร์แทนที่จะเปรียบเทียบทั้งตาราง (ฉันได้พยายามอีกครั้งเขียนมันและยังคงทำงานเป็นข้อผิดพลาด)
DaveB

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|
*/

หรือบางทีsum((lap_no=(prev+1))::integer)+1แต่ฉันไม่แน่ใจว่าอ่านง่ายกว่า
แจ็คบอกว่าลอง topanswers.xyz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.