我在RoR堆栈中,我必须编写一些实际的SQL来完成对所有“打开”记录的查询,这意味着当前时间在指定的操作时间内。在hours_of_operations
表中,两个integer
列opens_on
和closes_on
存储一个工作日,两个time
字段opens_at
和closes_at
存储一天中的相应时间。
我做了一个查询,比较当前的日期和时间存储的值,但我想知道是否有一种方法转换为某种日期类型,并让PostgreSQL完成其余的工作?
查询的核心是:
WHERE (
(
/* Opens in Future */
(opens_on > 5 OR (opens_on = 5 AND opens_at::time > '2014-03-01 00:27:25.851655'))
AND (
(closes_on < opens_on AND closes_on > 5)
OR ((closes_on = opens_on)
AND (closes_at::time < opens_at::time AND closes_at::time > '2014-03-01 00:27:25.851655'))
OR ((closes_on = 5)
AND (closes_at::time > '2014-03-01 00:27:25.851655' AND closes_at::time < opens_at::time)))
OR
/* Opens in Past */
(opens_on < 5 OR (opens_on = 5 AND opens_at::time < '2014-03-01 00:27:25.851655'))
AND
(closes_on > 5)
OR
((closes_on = 5)
AND (closes_at::time > '2014-03-01 00:27:25.851655'))
OR (closes_on < opens_on)
OR ((closes_on = opens_on)
AND (closes_at::time < opens_at::time))
)
)
如此密集的复杂性的原因是一个小时的操作可能会在一周结束时结束,例如,从周日中午开始直到周一早上6点。由于我以UTC存储值,因此在许多情况下,用户的本地时间可能会以一种非常奇怪的方式 Package 。上面的查询确保您可以在一周中输入任意两次,我们补偿 Package 。
1条答案
按热度按时间thigvfpy1#
表格布局
重新设计该表,将营业时间(营业时间)存储为一组
tsrange
(range oftimestamp without time zone
)值。需要Postgres9.2或更高版本。随便挑一个星期来安排你的开放时间。我喜欢这个星期:
这是最近的闰年,1月1日恰好是星期一。但在这个案子里可以是任意的一周。保持一致。
首先安装附加模块
btree_gist
:参见:
然后像这样创建表格:
hours
替换所有列:例如,从 * 星期三18:30 * 到 * 星期四05:00 * UTC的工作时间输入为:
排除约束**
hoo_no_overlap
可防止每个车间的条目重叠。它是用GiST索引实现的,它也恰好支持我们的查询。请考虑下面讨论索引策略的章节“索引和性能”。check约束
hoo_bounds_inclusive
**强制执行范围的包含边界,有两个值得注意的结果:f_hoo_hours()
处理了这个问题。check约束**
hoo_standard_week
使用“range is contained by”运算符<@
强制执行分期周的外部边界。使用包含边界,您必须观察一个角情况**,其中时间在周日午夜结束:
您必须同时搜索两个时间戳。下面是一个相关的例子,它的***互斥***上限不会出现这个缺点:
函数
f_hoo_time(timestamptz)
要“归一化”任何给定的
timestamp with time zone
:PARALLEL SAFE
仅适用于Postgres 9.6或更高版本。该函数接受
timestamptz
并返回timestamp
。它将相应周($1 - date_trunc('week', $1)
(UTC时间)的经过时间间隔添加到我们的分期周的起始点。(date
+interval
产生timestamp
。)函数
f_hoo_hours(timestamptz, timestamptz)
标准化范围并分割周一00:00的交叉点。此函数采用任意间隔(两个
timestamptz
),并生成一个或两个标准化的tsrange
值。它涵盖***任何***合法输入,不允许其余的:要
INSERT
* 单个 * 输入行:对于 * 任意 * 数量的输入行:
如果一个范围需要在周一00:00 UTC拆分,则每种方法都可以插入两行。
查询
通过调整后的设计,* 您的整个大型、复杂、昂贵的查询 * 可以替换为……这一点:
SELECT *
**FROM hoo
**WHERE hours @> f_hoo_time(now());
**为了一点悬念,我在解决方案上放了一个扰流板。将鼠标移到上面。
该查询由所述GiST索引支持,并且速度快,即使对于大表也是如此。
fiddle *(更多示例)
老麻雀
如果你想计算总营业时间(每家商店),这里有一个食谱:
指标和性能
范围类型的包含操作符可以通过GiST或SP-GiST索引来支持。两者都可以用来实现排除约束,但只有GiST支持multicolumn indexes:
目前,只有B-tree、GiST、GIN和BRIN索引类型支持多列索引。
order of index columns matters:
多列GiST索引可以与涉及索引列的任何子集的查询条件一起使用。附加列上的条件限制索引返回的条目,但是第一列上的条件对于确定需要扫描索引的多少是最重要的。如果GiST索引的第一列只有几个不同的值,即使其他列中有许多不同的值,GiST索引也将相对无效。
所以我们有利益冲突。对于大表,
shop_id
的不同值要比hours
多得多。shop_id
开头的GiST索引写入和强制执行排除约束的速度更快。hours
。先有这个专栏会更好。shop_id
,普通的btree索引会更快。hours
上的SP-GiST索引对于查询来说是最快的。基准测试
在旧笔记本电脑上使用Postgres 12进行新测试。我的生成虚拟数据的脚本:
结果约141k随机生成的行,约30k不同的
shop_id
,约12k不同的hours
。表格大小8 MB。我删除并重新创建了排除约束:
shop_id
第一个比这个分布快4倍。此外,我还测试了另外两个读取性能:
在
VACUUM FULL ANALYZE hoo;
之后,我运行了两个查询:结果
对每一个都进行了 * 仅索引扫描 *(当然,“无索引”除外):
如果读的比写的多得多(典型的用例),请按照开始时的建议保留排除约束,并创建一个额外的SP-GiST索引来优化读性能。