在 TIMESTAMP 列上創建索引以與範圍運算符一起使用
TLDR:我可以創建一個由以下
WHERE
子句使用的索引:WHERE foo_date <@ tsrange('2018-01-01', '2018-02-01')
假設我有一張這樣的桌子:
創建表 foo ( foo_id INTEGER 預設為 IDENTITY 生成, 沒有時區的 foo_date 時間戳 NOT NULL, 約束 foo_pkey 主鍵 (foo_id) );
此表包含 100,000 條記錄,日期從
2009-01-01
到2018-12-29
。我希望能夠查詢給定日期範圍內的行(例如 2018 年 1 月的行)。選項1
一種方法是使用
BETWEEN
運算符:SELECT * FROM foo WHERE foo_date BETWEEN '2018-01-01' AND '2018-01-31';
這種方法的問題是,如果
foo_date
發生在2018-01-31
午夜之後,它們將不會包含在此查詢中。所以我可以將查詢更改為BETWEEN '2018-01-01' AND '2018-02-01'
. 然而,問題是發生在 上的記錄2018-02-01 00:00:00
。這些將包括在內,這是我不想要的。選項 2
Aaron Bertrand提出的另一種選擇是使用以下構造:
foo_date >= '2018-01-01' AND foo_date < '2018-02-01'
(是的,此部落格適用於 SQL Server,但似乎適用於此處)。
雖然這種形式明確地給出了我想要的結果,但它很麻煩:我必須重複列名兩次。
選項 3
由於 Postgres 為我們提供了範圍數據類型,我認為更清晰的形式可能是:
foo_date <@ tsrange('2018-01-01', '2018-02-01')
那麼我的下一個問題是,如果我使用這種形式,我可以使用索引來加速操作嗎?
使用上面的選項 1 和 2,可以使用普通的 b-tree 索引:
CREATE INDEX idx_foo ON foo(foo_date);
使用選項 1 或 2 的查詢將使用索引:
EXPLAIN SELECT * FROM foo WHERE foo_date >= '2018-01-01' AND foo_date < '2018-02-01';
給我這個查詢計劃:
Bitmap Heap Scan on foo (cost=21.95..592.70 rows=942 width=12) Recheck Cond: ((foo_date >= '2018-01-01 00:00:00'::timestamp without time zone) AND (foo_date < '2018-02-01 00:00:00'::timestamp without time zone)) -> Bitmap Index Scan on idx_foo (cost=0.00..21.71 rows=942 width=0) Index Cond: ((foo_date >= '2018-01-01 00:00:00'::timestamp without time zone) AND (foo_date < '2018-02-01 00:00:00'::timestamp without time zone))
但是,如果我使用選項 3,則不使用索引:
EXPLAIN SELECT * FROM foo WHERE foo_date <@ tsrange('2018-01-01', '2018-02-01');
給我:
Seq Scan on foo (cost=0.00..1791.00 rows=500 width=12) Filter: (foo_date <@ '["2018-01-01 00:00:00","2018-02-01 00:00:00")'::tsrange)
如果我嘗試創建一個 gist 索引,我最初會收到一條錯誤消息。
ERROR: data type timestamp without time zone has no default operator class for access method "gist" HINT: You must specify an operator class for the index or define a default operator class for the data type. SQL state: 42704
添加
btree_gist
擴展後,我可以創建索引:CREATE INDEX idx_foo ON foo USING gist (foo_date)
但是,使用
@>
or<@
仍然不會使用索引。有什麼我想念的嗎?或者創建一個可以被這個構造使用的索引是不可行的?
- 名稱*“foo_date”表示一個列
date
,並且是一個不好的選擇timestamp
。選項 1適用於實際日期*。- 結合選項 2的普通 btree 索引 無疑是最佳解決方案。不要再看了。除了具有物理排序數據的大型表的 BRIN 索引的特殊情況。看:
概念證明
也就是說,要使 GiST 或 SP-GiST 索引起作用,您可以在假範圍上創建表達式索引。你不需要這個模組
btree_gist
。使用 SP-GiST 進行展示,因為這里通常會更快一些。看:CREATE INDEX foo_date_spgist_idx ON foo USING spgist(tsrange(foo_date, foo_date, '[]')); SELECT * FROM foo WHERE tsrange(foo_date, foo_date, '[]') <@ tsrange('2018-01-01', '2018-02-01')
或使用範圍文字:
... WHERE tsrange(foo_date, foo_date, '[]') <@ '[2018-01-01,2018-02-01)'
**但是:**更大,維護成本更高,比 btree 索引慢。寫起來更麻煩。對你的情況毫無意義。
除了:從技術上講,您可以:
... WHERE foo_date BETWEEN '2018-01-01' AND '2018-01-31 23:59.999999';
Postgres 時間戳類型(目前)以微秒解析度實現,即最大。6 位小數。因此,表達式完全符合您的要求。但我強烈建議不要在此實現細節上進行建構。選項2是要走的路。有關的: