在 UPSERT 的 UPDATE 部分,为多列计算一次表达式
In UPDATE part of UPSERT, compute expression once for multiple columns
我在 PostgreSQL 12 数据库中有一个 INSERT ... ON CONFLICT (primary_key) DO UPDATE
查询。
它要求我对更新子句中的多个列进行相同的计算。计算并不太复杂,但我不想在两个地方维护它。计算包含对冲突行和 EXCLUDED
虚拟 table.
的引用
有什么方法可以执行一次此计算并将其结果分配给单个查询表达式中的临时变量?
完整的查询是:
INSERT INTO table_name(id, refreshed_at, tokens) VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE SET
tokens = GREATEST(
-1,
LEAST(
GREATEST(0, table_name.tokens)
+ EXTRACT(epoch FROM EXCLUDED.refreshed_at)
- EXTRACT(epoch FROM table_name.refreshed_at),
100
) - 1
),
refreshed_at = CASE
WHEN (
GREATEST(0, table_name.tokens)
+ EXTRACT(epoch FROM EXCLUDED.refreshed_at)
- EXTRACT(epoch FROM table_name.refreshed_at)
) > 0 THEN EXCLUDED.refreshed_at
ELSE table_name.refreshed_at
END
RETURNING tokens >= 0;
tokens
列是根据列的先验值计算的,即查询时间与上次更新列之间的秒数差。 refreshed_at
列只有在更新 tokens
列值时才应更改,因此必须在两个 SET
子句中执行相同的计算。
The refreshed_at column should only be changed if the tokens column value was updated
您最初的想法应该适用于子查询:
INSERT INTO table_name(id, refreshed_at, tokens)
VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE
<b>SET (tokens, refreshed_at)
= (SELECT GREATEST(-1, LEAST(GREATEST(0, table_name.tokens) + sub.diff, 100) - 1)
, CASE WHEN sub.diff > 0 THEN EXCLUDED.refreshed_at ELSE table_name.refreshed_at END
FROM (SELECT EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)::int) sub(diff))</b>
RETURNING tokens >= 0;
那是 allowed variant of the UPDATE
syntax。这样,以秒为单位的时间戳之间的差异只计算一次。
计算也更便宜,从差异中提取纪元(结果 interval
):
EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)
而不是像原来那样提取两次并减去:
EXTRACT(epoch FROM EXCLUDED.refreshed_at) - EXTRACT(epoch FROM table_name.refreshed_at)
但是添加子查询的开销可能比做两次便宜的计算更昂贵!
备选方案
考虑将 WHERE
子句添加到 UPDATE
部分。喜欢:
INSERT INTO table_name(id, refreshed_at, tokens)
VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE
SET tokens = GREATEST(-1, LEAST(GREATEST(0, table_name.tokens) + EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at), 100) - 1)
, refreshed_at = EXCLUDED.refreshed_at
<b>WHERE EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)::int > 0</b>
RETURNING tokens >= 0;
这根本不会触及时差低于 1 秒的行。这通常是优越的,因为空更新只会增加成本和膨胀。 (转换 rounds,您可能想要 truncate?)
当然,这也不会 return INSERT
和 UPDATE
部分都没有通过的行。如果这是一个问题,请考虑:
我在 PostgreSQL 12 数据库中有一个 INSERT ... ON CONFLICT (primary_key) DO UPDATE
查询。
它要求我对更新子句中的多个列进行相同的计算。计算并不太复杂,但我不想在两个地方维护它。计算包含对冲突行和 EXCLUDED
虚拟 table.
有什么方法可以执行一次此计算并将其结果分配给单个查询表达式中的临时变量?
完整的查询是:
INSERT INTO table_name(id, refreshed_at, tokens) VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE SET
tokens = GREATEST(
-1,
LEAST(
GREATEST(0, table_name.tokens)
+ EXTRACT(epoch FROM EXCLUDED.refreshed_at)
- EXTRACT(epoch FROM table_name.refreshed_at),
100
) - 1
),
refreshed_at = CASE
WHEN (
GREATEST(0, table_name.tokens)
+ EXTRACT(epoch FROM EXCLUDED.refreshed_at)
- EXTRACT(epoch FROM table_name.refreshed_at)
) > 0 THEN EXCLUDED.refreshed_at
ELSE table_name.refreshed_at
END
RETURNING tokens >= 0;
tokens
列是根据列的先验值计算的,即查询时间与上次更新列之间的秒数差。 refreshed_at
列只有在更新 tokens
列值时才应更改,因此必须在两个 SET
子句中执行相同的计算。
The refreshed_at column should only be changed if the tokens column value was updated
您最初的想法应该适用于子查询:
INSERT INTO table_name(id, refreshed_at, tokens)
VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE
<b>SET (tokens, refreshed_at)
= (SELECT GREATEST(-1, LEAST(GREATEST(0, table_name.tokens) + sub.diff, 100) - 1)
, CASE WHEN sub.diff > 0 THEN EXCLUDED.refreshed_at ELSE table_name.refreshed_at END
FROM (SELECT EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)::int) sub(diff))</b>
RETURNING tokens >= 0;
那是 allowed variant of the UPDATE
syntax。这样,以秒为单位的时间戳之间的差异只计算一次。
计算也更便宜,从差异中提取纪元(结果 interval
):
EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)
而不是像原来那样提取两次并减去:
EXTRACT(epoch FROM EXCLUDED.refreshed_at) - EXTRACT(epoch FROM table_name.refreshed_at)
但是添加子查询的开销可能比做两次便宜的计算更昂贵!
备选方案
考虑将 WHERE
子句添加到 UPDATE
部分。喜欢:
INSERT INTO table_name(id, refreshed_at, tokens)
VALUES ('id', CURRENT_TIMESTAMP, 60)
ON CONFLICT (id) DO UPDATE
SET tokens = GREATEST(-1, LEAST(GREATEST(0, table_name.tokens) + EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at), 100) - 1)
, refreshed_at = EXCLUDED.refreshed_at
<b>WHERE EXTRACT(epoch FROM EXCLUDED.refreshed_at - table_name.refreshed_at)::int > 0</b>
RETURNING tokens >= 0;
这根本不会触及时差低于 1 秒的行。这通常是优越的,因为空更新只会增加成本和膨胀。 (转换 rounds,您可能想要 truncate?)
当然,这也不会 return INSERT
和 UPDATE
部分都没有通过的行。如果这是一个问题,请考虑: