PostgreSQL 受 CTE 中的变量值限制

PostgreSQL limit by variable value in a CTE

我正在尝试使用两个 SQL select 查询从数据库中获取一些结果,我正在使用 CTE,但由于我有数百万行,我需要限制结果,我想要将结果限制为 20,我尝试的是:

with fetch_new_events as 
(
select * from events e where id > @las_max_id_processed 
order by e.id
    limit 20), 
fetch_old_events as (
 select * from events e where id < @las_min_id_processed 
    order by e.id desc
    limit 20-count(fetch_new_events.id))
select * from fetch_new_events
union select * from fetch_old_events

聚合函数似乎不能在极限上工作,此外,即使他们这样做,如果 20-count() return 是负数,我也会遇到问题,数据库引擎将抛出异常所以我必须使用 max(0,20-count()) 或类似的东西。 有人可以帮忙吗?

你可能会争辩说我可以使用单个 select 查询,我想我不能,因为一个查询获取了自从我开始处理它们以来添加的新事件,以避免使客户端(消费者)过于复杂), 消费者只跟踪处理的最大和最小 id 的两个整数,这意味着我每次得到的结果应该有连续的 id,所以没有间隙和丢失的行,就此而言,我需要进行第一个查询以订购按id升序排列,第二个查询因为我对最早的事件感兴趣,所以需要按id降序排列。

更新

这是示例数据:

create table events
(
    id serial not null
        constraint events_pk
            primary key,
    name varchar
);
insert into public.events (id, name) values (7, 'event7');
insert into public.events (id, name) values (6, 'event6');
insert into public.events (id, name) values (5, 'event5');
insert into public.events (id, name) values (4, 'event4');
insert into public.events (id, name) values (3, 'event3');
insert into public.events (id, name) values (2, 'event2');
insert into public.events (id, name) values (1, 'event1');


我假定以下起点,我在只有五行时开始,客户端第一次获取行时使用:

select * from events e
    order by e.id desc
    limit 3

他得到了 ID 为 5,3 的事件。在处理生产者推送的更多事件时,现在客户想要获得 3 个 ID > 5 或 < 4 的事件,所以他这样做:

with fetch_new_events as
(
select * from events e where id > 5
order by e.id
    limit 3),
fetch_old_events as (
 select * from events e where id < 3
    order by e.id desc
    limit 3-count(fetch_new_events.id))
select * from fetch_new_events
union select * from fetch_old_events;

失败了。

抛开问题

出于好奇,要理解为什么如果我们使用单个查询会出现差距,请添加更多条目:

insert into public.events (id, name) values (8, 'event8');
insert into public.events (id, name) values (9, 'event9');
insert into public.events (id, name) values (10, 'event10');

然后用一个select语句查询:

select * from events e
where e.id > 5 or e.id < 3
order by e.id desc
limit 3

我们现在将获取 ID 为 10,9,8 的事件。

发生了什么?那么,客户端现在会处理以下范围:

所以如果我们有一个非常简单的客户端(正如我希望的那样),因为它只跟踪两个整数,处理过的最大 id 和最小 id,它会认为他处理了从 10 到3、事实并非如此!我们会错过 ID 为 7 和 6 的事件。为了克服这个问题,我必须让复杂的客户端记住范围而不是两个简单的整数,或者使用我所说的。

更新2

在 postgresql slack 频道中被称为 jer_s 的人给了我解决方案,我只需要添加另一个只包含行数的 CTE 查询,然后使用该值作为限制:

with new_events as (
    select * from events e
    where e.id > 5
    order by e.id
    limit 3
    ), new_row_count as (
        select count('*') as row_count from new_events
    ), old_events as (
        select * from events e
        where e.id < 3
        order by e.id desc
        limit 3-(select row_count from new_row_count)
    )
select * from new_events
union
select *
from old_events;

这将 return 个 ID 为 7、6、2 的事件,因此正在处理的事件将是连续的,从 7 到 2!

我真的不明白你在做什么,为什么更简单的版本不起作用。

但是,我可以看出您在这段代码中遇到的表面问题:

limit 20-count(fetch_new_events.id)

简单的解决方案是使用 window 函数:

with fetch_new_events as (
      select *
      from events e
      where id > @las_max_id_processed 
      order by e.id
      limit 20
     ), 
     fetch_old_events as (
      select e.*
      from (select e.*, row_number() over (order by e.id desc) as seqnum
            from events e
            where id < @las_min_id_processed 
           ) e
      where seqnum <= 20 - (select count(*) from fetch_new_events)
     )
select . . .   -- list the columns you want here
from fetch_new_events
union
select . . .   -- list the columns you want here
from fetch_old_events;

请注意,您还需要明确列出所有列以处理 seqnum 列。