Postgres 使用带有 `split_part` 的索引
Postgres use index with `split_part`
上下文:
我有一个 test
table:
=> \d+ test
Table "public.test"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+------------------------+-----------+----------+---------+----------+--------
------+-------------
id | character varying(255) | | | | extended |
|
configuration | jsonb | | | | extended |
|
configuration
列包含 "well-defined" 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 小时 运行s。这项工作应该找到所有这些 expired/about 过期的记录(然后对其进行处理)。
解法:
我有这个查询来完成我的工作:
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(包括那些已经过期的)
问题:
- 虽然这解决了我的问题,但我真的不喜欢这个解决方案。这有很多我觉得不干净的字符串操作。有更简洁的方法吗?
- 如果我们"have"采用上述解决方案,我们甚至可以为这种查询使用索引吗?我知道函数
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
列的价值。为什么不将 URL 作为 text
列呢?
您可以走得更远,将 URL 分成单独的部分,并存储在列中。
这一切是否是个好主意取决于您如何使用数据库中的值:通常将您在 WHERE
条件等中使用的数据部分拆分出来是个好主意剩下的 "in a lump"。这在某种程度上是一个品味问题。
您可以使用 URI 解析模块,如果这是您认为不干净的部分。您可以使用 plperl 或 plpythonu,以及您喜欢的任何 URI 解析器库。但是,如果您的 json 真的是 "well defined",我认为没有多大意义。除非您已经在使用 plperl 或 plpythonu,否则添加这些依赖项可能会增加 "dirt" 而不是删除的依赖项。
您可以建立索引:
create index on test (to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint));
set enable_seqscan TO off;
explain select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using test_to_timestamp_idx1 on test (cost=0.13..8.15 rows=1 width=36)
Index Cond: (to_timestamp(((split_part(split_part((configuration ->> 'source_url'::text), 'Expires-At='::text, 2), '&'::text, 1))::bigint)::double precision) <= (now() + '24:00:00'::interval))
I would introduce a new key inside configuration json called expires_at, making sure, this gets filled with the correct value, every time a row is inserted.
那不就是重新整理一下污垢吗?它使查询看起来更好,但代价是使插入更难看。或许您可以将其放入 INSERT OR UPDATE 触发器中。
上下文:
我有一个 test
table:
=> \d+ test
Table "public.test"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+------------------------+-----------+----------+---------+----------+--------
------+-------------
id | character varying(255) | | | | extended |
|
configuration | jsonb | | | | extended |
|
configuration
列包含 "well-defined" 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 小时 运行s。这项工作应该找到所有这些 expired/about 过期的记录(然后对其进行处理)。
解法:
我有这个查询来完成我的工作:
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(包括那些已经过期的)
问题:
- 虽然这解决了我的问题,但我真的不喜欢这个解决方案。这有很多我觉得不干净的字符串操作。有更简洁的方法吗?
- 如果我们"have"采用上述解决方案,我们甚至可以为这种查询使用索引吗?我知道函数
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
列的价值。为什么不将 URL 作为 text
列呢?
您可以走得更远,将 URL 分成单独的部分,并存储在列中。
这一切是否是个好主意取决于您如何使用数据库中的值:通常将您在 WHERE
条件等中使用的数据部分拆分出来是个好主意剩下的 "in a lump"。这在某种程度上是一个品味问题。
您可以使用 URI 解析模块,如果这是您认为不干净的部分。您可以使用 plperl 或 plpythonu,以及您喜欢的任何 URI 解析器库。但是,如果您的 json 真的是 "well defined",我认为没有多大意义。除非您已经在使用 plperl 或 plpythonu,否则添加这些依赖项可能会增加 "dirt" 而不是删除的依赖项。
您可以建立索引:
create index on test (to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint));
set enable_seqscan TO off;
explain select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using test_to_timestamp_idx1 on test (cost=0.13..8.15 rows=1 width=36)
Index Cond: (to_timestamp(((split_part(split_part((configuration ->> 'source_url'::text), 'Expires-At='::text, 2), '&'::text, 1))::bigint)::double precision) <= (now() + '24:00:00'::interval))
I would introduce a new key inside configuration json called expires_at, making sure, this gets filled with the correct value, every time a row is inserted.
那不就是重新整理一下污垢吗?它使查询看起来更好,但代价是使插入更难看。或许您可以将其放入 INSERT OR UPDATE 触发器中。