Sql 列值最接近的行
我有下表:Sql 列值最接近的行,sql,postgresql,aggregate,Sql,Postgresql,Aggregate,我有下表: CREATE TABLE items ( id serial timestamp bigint CONSTRAINT id_pkey PRIMARY KEY (id), ); 此表以仅附加的方式使用,因此时间戳值随id的增加而增加。我需要找到与特定$value最接近的时间戳的行 查询1:这需要两次完整的表扫描 SELECT id FROM ( ( SELECT id, timestamp FROM records
CREATE TABLE items (
id serial
timestamp bigint
CONSTRAINT id_pkey PRIMARY KEY (id),
);
此表以仅附加的方式使用,因此时间戳
值随id
的增加而增加。我需要找到与特定$value
最接近的时间戳的行
查询1:这需要两次完整的表扫描
SELECT id FROM
(
(
SELECT id, timestamp
FROM records
WHERE timestamp < $value
ORDER BY timestamp DESC
LIMIT 1
)
UNION ALL
(
SELECT id, timestamp
FROM items
WHERE timestamp >= $value
ORDER BY timestamp ASC
LIMIT 1
)
) AS tmp
ORDER BY abs($value - timestamp)
LIMIT 1
查询3:我正在试验一个自定义聚合,它需要一个完整的表扫描,但不需要对任何内容进行排序或加载任何索引
create function closest_id_sfunc(
agg_state bigint[2],
id bigint,
timestamp bigint,
target_timestamp bigint
)
returns bigint[2]
immutable
language plpgsql
as $$
declare
difference bigint;
begin
difference := abs(timestamp - target_timestamp);
if agg_state is null or difference < agg_state[0] then
agg_state[0] = difference;
agg_state[1] = id;
end if;
return agg_state;
end;
$$;
create function closest_id_finalfunc(agg_state bigint[2])
returns bigint
immutable
strict
language plpgsql
as $$
begin
return agg_state[1];
end;
$$;
create aggregate closest_id (bigint, bigint, bigint)
(
stype = bigint[2],
sfunc = closest_id_sfunc,
finalfunc = closest_id_finalfunc
);
SELECT closest_id(id, timestamp, $value) as id FROM items
create function\u id\u sfunc(
agg_state bigint[2],
id bigint,
时间戳bigint,
目标时间戳bigint
)
返回bigint[2]
不变的
语言plpgsql
作为$$
声明
差分比基特;
开始
差异:=abs(时间戳-目标时间戳);
如果agg_状态为空或差异
为什么查询2比查询1慢?您的第二个查询将无法工作,因为在提供的时间戳之前可能会有一行,该时间戳更接近提供的值。准确性并不是这里唯一的问题:可能没有一行大于提供的时间戳(同时,存在一个较低的值)
您的第一个查询看起来很有效(当您在子查询中也使用limit 1
时)。但是是的,当您没有索引时,它需要两次表扫描,但是您无法解决这个问题。您需要索引才能获得巨大的性能提升。然而,有一些技巧是可以使用的
我最初的想法是,您可以通过使用条件来避免外部查询排序的开销:
(注意:我将使用ts
作为列名,因为timestamp
是一个关键字&不应该用作列名,除非它被转义。)
在我的测试中,这花费了原始查询(或CTE变体)的约70%
您还可以在cube(ts)
上使用cube
类型及其(欧几里德)距离运算符
(9.6+功能)(因此立方体将始终是一维点):
这样,您的原始查询(以及CTE变体)只使用索引扫描,其成本约为同一查询(无索引)的1.5%
点
变量可以使用以下要点索引:
(注意:btree\u-gist
模块是索引的一部分,id
需要的。您可以使用create extension btree\u-gist;
初始化模块)
这样,点
变量的成本约为原始查询的1%(无索引)
cube
变量可以使用以下GiST索引:
(注意:这也需要btree\u gist
模块。)
同样,这仍然与点
变量相当
结论(见下文编辑)
使用点
或立方体
(后者需要9.6+)可以获得最佳性能。此外,索引可以帮助您很多
进一步说明:
点
变量有时实际上比立方体
变量更快
- PostgreSQL花了很长时间构建
cube
索引&我不知道为什么
- 理论上,
cube
索引应该更小,因为它不包含不必要的零。但是,因为它们更一般(N维),所以我的观点可能不对。我建议尝试这两种方法(索引大小和性能)
(这里的查询也是针对cube
,但不会运行,因为rextester现在使用9.5)
此外,我还测试了一个自定义聚合解决方案(基本上是您的版本,但我使用了语言sql
函数来提高一点速度,但仍然如此),它比原始查询慢约10倍。嗯,这根本不值得
Edit:刚刚注意到,btree\u-gist
模块为基本类型(如bigint
)添加了对距离运算符的支持
因此,此查询的性能甚至优于点
和多维数据集
变量(有一点):
时间戳是用户指定的还是db指定的?换句话说,我们可以取而代之地获取id前后的行,并使用它们而不必使用timestamp字段吗?此外,在时间戳字段上创建索引是一种选择吗?速度慢是由于在未索引的字段上进行比较时进行了完整的表扫描。时间戳是用户指定的,不,我不能在其上放置索引(不要问:S…),第一个查询更好。我们不得不问。要么你在时间戳上有一个索引,要么你就得不到好的性能。告诉那些说你没有索引的人。他们必须付出代价(这可能是值得付出的代价)。@LaurenzAlbe为什么第一个更好?
create function closest_id_sfunc(
agg_state bigint[2],
id bigint,
timestamp bigint,
target_timestamp bigint
)
returns bigint[2]
immutable
language plpgsql
as $$
declare
difference bigint;
begin
difference := abs(timestamp - target_timestamp);
if agg_state is null or difference < agg_state[0] then
agg_state[0] = difference;
agg_state[1] = id;
end if;
return agg_state;
end;
$$;
create function closest_id_finalfunc(agg_state bigint[2])
returns bigint
immutable
strict
language plpgsql
as $$
begin
return agg_state[1];
end;
$$;
create aggregate closest_id (bigint, bigint, bigint)
(
stype = bigint[2],
sfunc = closest_id_sfunc,
finalfunc = closest_id_finalfunc
);
SELECT closest_id(id, timestamp, $value) as id FROM items
with l as (
select id, ts
from items
where ts < $value
order by ts desc
limit 1
),
g as (
select id, ts
from items
where ts >= $value
order by ts asc
limit 1
)
select case
when abs($value - l.ts) < abs($value - g.ts)
then l.id
else coalesce(g.id, l.id)
end id
from l
full join g on true
select id
from items
order by point(ts, 0) <-> point($value, 0)
limit 1
select id
from items
order by cube(ts) <-> cube($value)
limit 1
create index idx_items_ts_id on items (ts, id)
create index idx_items_point_gist on items using gist (point(ts, 0), id)
create index idx_items_cube_gist on items using gist (cube(ts), id)
select id
from items
order by ts <-> $value
limit 1
create index idx_items_ts_gist on items using gist (ts, id)