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)