sql相关子查询没有预期的行为

nle07wnf  于 2021-07-29  发布在  Java
关注(0)|答案(1)|浏览(311)

我在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_idproducts_status 表是 status_transition_message 表,并且此字段可以为null,这就是 WHERE 我正在检查的子查询语句是否为null(因此不必对其进行测试) stm_rejected 行(不需要)
我不知道我的问题是否清楚,但是查询确实返回了预期的结果。问题是,当第一个and子句的计算结果已经为false时,它会不必要地计算and子句,这会损害查询的性能。

mrphzbgm

mrphzbgm1#

你的直觉是正确的:布尔表达式不是从左到右计算的。postgres docs中的表达式求值规则(4.2.14)声明:
子表达式的求值顺序未定义。特别地,运算符或函数的输入不一定是从左到右或以任何其他固定顺序计算的。
要强制执行求值顺序,可以使用公共表表达式(cte),这也会使查询更具可读性。它告诉优化器不要重写表达式,而是具体化结果,有点像临时表。如果不对实际数据运行并解释查询,很难判断它是否会导致性能提高或降低。我会尝试子查询和cte。
但是,在您的特定情况下,可能不需要相关的子查询。我重写了查询以使用内部连接,这样可以更有效地处理该逻辑。我在这里也使用了一个cte,但目的不同,因为我猜到状态6和状态7的转换是所有转换的一个很小的子集,因此性能可能会受益于行的早期减少。
我在这里也做了一个尝试,改变了逻辑,用显式分组来替换distinct。

with

  stm_rejected as (
    select
      id,
      "date" as transition_date,
      row_number() over (partition by shipment_id order by "date") as transition_rank
    from
      status_transition_message
    where
      final_status in (6, 7)
  )

select
  shipment.id as shipment_id,
  string_agg(products_status.t_code, ',') as t_codes
from
  shipment
  inner join stm_rejected
    on shipment.id = stm_rejected.shipment_id
  inner join products_status
    on stm_rejected.id = products_status.status_transition_message_id
where
  stm_rejected.transition_rank = 1
  and stm_rejected.transition_date between :startDate and :endDate
  and products_status.enabled = false
group by
  shipment.id
;

相关问题