我在postgres中有一个sql查询,它有一个意外的行为:
SELECT
DISTINCT s.id,
(
SELECT string_agg(CAST(t_code AS TEXT), ',')
FROM (
SELECT DISTINCT ps.status
FROM products_status ps
WHERE
ps.status_transition_message_id IS NOT NULL AND
ps.enabled IS FALSE AND
ps.status_transition_message_id = stm_rejected.id
) AS t_code
) AS t_code
FROM (
SELECT
*,
row_number() OVER (PARTITION BY stm.shipment_id ORDER BY date) AS rn
FROM status_transition_message stm
WHERE
stm.final_status = 6 OR
stm.final_status = 7
) AS stm_rejected
JOIN shipment s ON s.id = stm_rejected.shipment_id
WHERE
stm_rejected.rn = 1 AND
stm_rejected.date BETWEEN :startDate AND :endDate;
这个 status_transition_message
表表示给定装运的x和y之间的状态转换。通过这个查询,我将遍历所有发货,并获取在给定的开始日期和结束日期之间第一次状态转换为6或7(它们可以多次经历相同的转换)的发货。对于符合这一条件的货物,我正在做一项调查 SELECT
对于 t_code
领域 products_status
在状态转换中指定。
这个查询的问题是 t_code
子查询,即使 ps.status_transition_message_id IS NOT NULL
计算结果为false,postgres仍在计算 ps.status_transition_message_id = stm_rejected.id
,即使它位于and布尔运算符之后。我知道这是因为当我移除 ps.status_transition_message_id = stm_rejected.id
部分来自于查询,它的执行速度要快得多。我也100%确信 products_status
在我测试的数据库中 status_transition_message_id
为空。
编辑:以下是 status_transition_message
行看起来像:
[id] [date] [initial_status] [final_status] [shipment_id]
434 3/20/13 14:18 0 4 943
和一个 products_status
行:
[id] [status] [status_transition_message_id] [enabled] [shipment_id]
211 5 434 true 943
这个 status_transition_message_id
在 products_status
表是 status_transition_message
表,并且此字段可以为null,这就是 WHERE
我正在检查的子查询语句是否为null(因此不必对其进行测试) stm_rejected
行(不需要)
我不知道我的问题是否清楚,但是查询确实返回了预期的结果。问题是,当第一个and子句的计算结果已经为false时,它会不必要地计算and子句,这会损害查询的性能。
1条答案
按热度按时间mrphzbgm1#
你的直觉是正确的:布尔表达式不是从左到右计算的。postgres docs中的表达式求值规则(4.2.14)声明:
子表达式的求值顺序未定义。特别地,运算符或函数的输入不一定是从左到右或以任何其他固定顺序计算的。
要强制执行求值顺序,可以使用公共表表达式(cte),这也会使查询更具可读性。它告诉优化器不要重写表达式,而是具体化结果,有点像临时表。如果不对实际数据运行并解释查询,很难判断它是否会导致性能提高或降低。我会尝试子查询和cte。
但是,在您的特定情况下,可能不需要相关的子查询。我重写了查询以使用内部连接,这样可以更有效地处理该逻辑。我在这里也使用了一个cte,但目的不同,因为我猜到状态6和状态7的转换是所有转换的一个很小的子集,因此性能可能会受益于行的早期减少。
我在这里也做了一个尝试,改变了逻辑,用显式分组来替换distinct。