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 为 5 到 3 的事件。
- id 为 10 到 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
列。
我正在尝试使用两个 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 为 5 到 3 的事件。
- id 为 10 到 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
列。