上下文:
我有一个测试
表:
=> \d+ test
Table "public.test"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+------------------------+-----------+----------+---------+----------+--------
------+-------------
id | character varying(255) | | | | extended |
|
configuration | jsonb | | | | extended |
|
configuration
列包含“定义良好”的 json,它有一个名为 source_url
的键(跳过其他不相关的键)。 configuration
列的示例值是:
{
"source_url": "https://<resource-address>?Signature=R1UzTGphWEhrTTFFZnc0Q4qkGRxkA5%2BHFZSfx3vNEvRsrlDcHdntArfHwkWiT7Qxi%2BWVJ4DbHJeFp3GpbS%2Bcb1H3r1PXPkfKB7Fjr6tFRCetDWAOtwrDrVOkR9G1m7iOePdi1RW%2Fn1LKE7MzQUImpkcZXkpHTUgzXpE3TPgoeVtVOXXt3qQBARpdSixzDU8dW%2FcftEkMDVuj4B%2Bwiecf6st21MjBPjzD4GNVA%2F6bgvKA6ExrdYmM5S6TYm1lz2e6juk81%2Fk4eDecUtjfOj9ekZiGJVMyrD5Tyw%2FTWOrfUB2VM1uw1PFT2Gqet87jNRDAtiIrJiw1lfB7Od1AwNxIk0Rqkrju8jWxmQhvb1BJLV%2BoRH56OHdm5nHXFmQdldVpyagQ8bQXoKmYmZPuxQb6t9FAyovGMav3aMsxWqIuKTxLzjB89XmgwBTxZSv5E9bkWUbom2%2BWq4O3%2BCrVxYwsqg%3D%3D&Expires-At=1569340020&Issued-At=1568293200"
.
.
}
URL 包含一个查询参数 Expires-At
问题:
有一个每 24 小时运行一次的计划作业。这项工作应该找到所有这些已过期/即将过期的记录(然后对其进行处理)。
解决方案:
我有这个查询来完成我的工作:
select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';
解释:
- 查询首先在
Expires-At=
处拆分source_url
并选择它右侧的部分,然后在& 处拆分结果字符串
并选择它的左侧部分,从而获得text
所需的确切纪元时间
- 当
Expires-At
是source_url
中的最后一个查询参数时,相同的查询也适用于极端情况 - 一旦将纪元时间提取为
text
,它首先将其转换为bigint
,然后将其转换为 Postgres 时间戳,然后比较此时间戳是否要进行小于或等于距now()
24 小时的时间
- 所有满足上述条件的行都被选中
因此,在每次运行的最后,调度程序会刷新所有将在接下来的 24 小时内过期的 url(包括那些已经过期的 url)
问题:
- 虽然这解决了我的问题,但我真的不喜欢这个解决方案。这有很多我觉得不干净的字符串操作。有没有更简洁的方法来做到这一点?
- 如果我们“必须”采用上述解决方案,我们甚至可以为此类查询使用索引吗?我知道函数
lower()
,upper()
extra 可以被索引,但我真的想不出任何方法可以索引这个查询。
备选方案:
除非有一个真正干净的解决方案,否则我将采用以下方法:
- 我会在
configuration
json 中引入一个名为expires_at
的新键,确保每次插入一行时它都会填充正确的值。 - 然后直接查询这个新添加的字段(索引在
configuration
列)。
我承认我通过这种方式重复了信息 Expires-At
,但在我能想到的所有可能解决方案中,这是我发现最干净的解决方案。
你们能想到比这更好的方法吗?
编辑:
更新查询以使用 substring()
和正则表达式而不是内部 split_part()
:
select * from test where to_timestamp(split_part(substring(configuration->>'source_url' from 'Expires-At=\d+'), '=', 2)::bigint) <= now() + interval '24 hours';
最佳答案
鉴于您当前的数据模型,我认为您的 WHERE
条件没有那么糟糕。
你可以用它来索引
CREATE INDEX ON test (
to_timestamp(
split_part(
split_part(
configuration->>'source_url',
'Expires-At=',
2
),
'&',
1
)::bigint
)
);
本质上,您必须为 =
左侧的整个表达式建立索引。只有当涉及的所有函数和运算符都是 IMMUTABLE
时,您才能这样做,我认为它们就是您的情况。
不过我会更改数据模型。首先,我看不到包含单个值的 jsonb
列的值(value)。为什么不用 URL 作为 text
列呢?
您可以走得更远,将 URL 拆分为单独的部分,这些部分存储在列中。
这一切是否是一个好主意取决于您如何使用数据库中的值:通常将您在 WHERE
条件和喜欢并让其余的“一团糟”。这在某种程度上是一个品味问题。
关于sql - Postgres 使用 `split_part` 索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58082958/