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
?
答案 0 :(得分:1)
我认为您错误地认为COALESCE(value, 0)
SELECT
在索引使用方面很重要。说实话,只有视图转换在之后返回行值。
就索引使用而言,重要的是您的WINDOW FUNCTION
。首先按id
进行分区,然后按date DESC NULLS LAST
顺序对每个分区中的值进行排序。这两件事确定像CREATE INDEX ON foo (id, date DESC NULLS LAST, ...)
这样的索引对于你放在下一个位置的任何东西都是有用的。 请注意,如果在创建索引时更改id
和date
的顺序,PostgreSQL根本不会使用索引。
现在,您必须知道INDEX ONLY SCAN
只有在索引本身存储查询请求的整个未触动行值时才可以使用。在PostgreSQL manual之后:
如果索引存储原始索引数据值(而不是它们的某些有损表示),则支持仅索引扫描很有用,其中索引返回实际数据...
在您的情况下,您的第二个索引存储行的某些有损表示,因为最后一列的值由函数转换,并且查询要求id
,value
和{ {1}}。 PostgreSQL并不是那么聪明,因为它只是date
NULLs
的替代品。对他而言,这不是原始价值。所以我们需要访问表来获取原始行值(最后使用普通0
)。之后,为输出格式化值并INDEX SCAN
发生。
修改强>
对于你关于内部问题的问题,我认为这个解释已经足够了。要谈谈COALESCE(values, 0)
评估费用,我同意a_horse_with_no_name您可能不应该担心这一点。