为什么不能将索引扫描用于使用COALESCE创建的索引?

St.*_*rio 5 sql postgresql indexing internals

PostgreSQL 9.4该表创建如下:

CREATE TABLE foo (
    id integer,
    date date,
    value numeric(14,3)
);
Run Code Online (Sandbox Code Playgroud)

我正在使用ROW_NUMBER()窗口函数优化查询COALESCE.为了最有效,我倾向于Index Only Scan在以下查询中使用:

SELECT id, c_val
FROM (
    SELECT id, COALESCE(value, 0) c_val, ROW_NUMBER() OVER(PARTITION BY id ORDER BY date DESC NULLS LAST) rn
    FROM foo) sbt
WHERE sbt.rn = 1;
Run Code Online (Sandbox Code Playgroud)

所以,如果我创建索引如下:

CREATE INDEX ON foo (id, date DESC NULLS LAST, value);
Run Code Online (Sandbox Code Playgroud)

规划师选择使用Index Only Scan,但如果我这样做:

CREATE INDEX ON foo (id, date DESC NULLS LAST, COALESCE(value, 0));
Run Code Online (Sandbox Code Playgroud)

规划师会做的Index Scan.

为什么?我试图避免COALESCE在执行查询时评估函数的成本.为什么不适用Index Only Scan

Gab*_*ger 2

我认为您COALESCE(value, 0)SELECT索引使用方面错误地假设了这一点。说实话,这只是在返回行值之后完成的视图转换。

就索引使用而言,重要的是您的WINDOW FUNCTION. 首先按 进行分区id,然后按 来对每个分区中的值进行排序date DESC NULLS LAST。这两件事决定了CREATE INDEX ON foo (id, date DESC NULLS LAST, ...)无论您将其放在下一个位置,索引都是有用的。请注意,如果您在创建索引时更改和 的顺序,PostgreSQL 将根本不会使用该索引。iddate

现在,您必须知道,INDEX ONLY SCAN仅当索引本身存储查询请求的整个未更改行值时才可以使用。PostgreSQL 手册之后:

如果索引存储原始索引数据值(而不是它们的一些有损表示),则支持仅索引扫描很有用,其中索引返回实际数据......

在您的情况下,您的第二个索引存储行的一些有损表示,因为最后一列的值是由函数和查询要求的id,value和转换的date。PostgreSQL 并没有聪明到它只是NULLsby的替代0。对他来说这不是原来的价值。所以我们需要访问表来获取原始行值(最后使用 plain INDEX SCAN)。之后,值被格式化以供输出并COALESCE(values, 0)发生。

编辑:

我认为就您有关内部的问题而言,这个解释对您来说已经足够了。要谈论COALECE()评估成本,我同意a_horse_with_no_name 的观点,您可能不应该担心这一点。