选择最长的连续序列

Dav*_*veB 12 postgresql window-functions gaps-and-islands postgresql-9.0

我正在尝试在 PostgreSQL 9.0 中构建一个查询,该查询获取特定列的最长连续行序列。

考虑下表:

lap_id (serial), lap_no (int), car_type (enum), race_id (int FK)
Run Code Online (Sandbox Code Playgroud)

lap_no每个(race_id, car_type). where都是独一无二的。

我希望查询为给定的race_idand生成最长的序列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
Run Code Online (Sandbox Code Playgroud)

对于car_type = red and race_id = 1查询将5作为lap_no字段的最长序列返回。

我在这里发现了一个类似的问题但是我的情况更简单一些。

(我也想知道car_type所有种族给定的最长序列,但我计划自己解决这个问题。)

Erw*_*ter 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)
);
Run Code Online (Sandbox Code Playgroud)

此类问题的通用解决方案

要获得最长的序列(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;
Run Code Online (Sandbox Code Playgroud)

count(*) FILTER (WHERE step)only counts TRUE(= step to next group),这会为每个新组产生一个新数字。

关于 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;
Run Code Online (Sandbox Code Playgroud)

连续的圈数以相同的方式结束grp。每丢失一圈都会导致grp每个分区降低。

这取决于(race_id, car_type, lap_no)存在UNIQUE NOT NULL。NULL 值或重复项可能会破坏逻辑。

杰克更简单的替代方案的讨论

@杰克的版本有效计算所有的圈(行)在先前lap_no在此race_id具有相同的car_type。这是简单,快速和正确的-只要每次car_type只能有一个每个序列race_id

但是对于这么简单的任务,查询可能会更简单。从逻辑上讲,所有lap_noper(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;
Run Code Online (Sandbox Code Playgroud)

另一方面,如果每个race_idcar_type可以有多个单独的序列(并且问题没有另外指定),Jack 的版本将失败。

对于给定的比赛/汽车类型更快

回复问题中的评论/澄清:将查询限制为给定 的查询(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;
Run Code Online (Sandbox Code Playgroud)

db<>fiddle here
SQL 小提琴

指数

最佳性能的关键是拟合指数(除了上述使用单个顺序扫描的程序解决方案)。像这样的多列索引效果最好:

CREATE INDEX tbl_mult_idx ON tbl (race_id, car_type, lap_no);
Run Code Online (Sandbox Code Playgroud)

如果您的表有UNIQUE约束我认为在顶部,也就是只有这一点(唯一)的索引内部实现,你也不会需要创建另一个索引。


Jac*_*las 7

create table tbl (lap_no int, car_type text, race_id int);
Run Code Online (Sandbox Code Playgroud)
create table tbl (lap_no int, car_type text, race_id int);
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
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;
Run Code Online (Sandbox Code Playgroud)