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

时间:2015-10-16 08:59:33

标签: sql postgresql indexing internals

PostgreSQL 9.4 该表创建如下:

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

我正在使用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;

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

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

1 个答案:

答案 0 :(得分:1)

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

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

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

  

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

在您的情况下,您的第二个索引存储行的某些有损表示,因为最后一列的值由函数转换,并且查询要求idvalue和{ {1}}。 PostgreSQL并不是那么聪明,因为它只是date NULLs的替代品。对他而言,这不是原始价值。所以我们需要访问表来获取原始行值(最后使用普通0)。之后,为输出格式化值并INDEX SCAN发生。

修改

对于你关于内部问题的问题,我认为这个解释已经足够了。要谈谈COALESCE(values, 0)评估费用,我同意a_horse_with_no_name您可能不应该担心这一点。