为什么不能对使用 COALESCE 创建的索引使用 Index Only Scan?

Why can't Index Only Scan be used on index created with COALESCE?

PostgreSQL 9.4 table创建如下:

CREATE TABLE foo (
    id integer,
    date date,
    value numeric(14,3)
);

我正在使用 ROW_NUMBER() window 函数和 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;

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

CREATE INDEX ON foo (id, date DESC NULLS LAST, value);

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

CREATE INDEX ON foo (id, date DESC NULLS LAST, COALESCE(value, 0));

计划者会做 Index Scan

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

我认为您错误地认为 SELECT 中的 COALESCE(value, 0) 在索引使用方面很重要。说实话,它只是在 返回行值后 完成了视图转换。

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

现在,您必须知道 INDEX ONLY SCAN 只有在索引本身存储查询请求的整个未触及的行值时才能使用。 PostgreSQL manual之后:

If the index stores the original indexed data values (and not some lossy representation of them), it is useful to support index-only scans, in which the index returns the actual data...

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

编辑:

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