【问题标题】:if a non-correlated subquery is repeated at several places in the query, can it be cached and the result reused?如果一个不相关的子查询在查询中的多个位置重复,是否可以缓存它并重用结果?
【发布时间】:2012-10-22 12:16:03
【问题描述】:

如果我有类似的查询

SELECT date_trunc('day', assigndate)e,
       count(CASE WHEN a.assigneeid = 65548
             AND a.assigneeid IN
               (SELECT userid
                FROM groupmembers
                WHERE groupid = 65553) THEN 1 ELSE NULL END) assigned,
       count(CASE WHEN a.assigneeid = 65548
             AND a.completedtime IS NOT NULL
             AND a.assigneeid IN
               (SELECT userid
                FROM groupmembers
                WHERE groupid = 65553) THEN 1 ELSE NULL END) completed
FROM ASSIGNMENT a
WHERE assigndate > CURRENT_TIMESTAMP - interval '20 days'
GROUP BY date_trunc('day',assigndate);

有问题的子查询是

SELECT userid
                FROM groupmembers
                WHERE groupid = 65553

那么由于子查询对于父查询是not co-related,所以它只会执行一次并使用缓存的结果。但由于子查询存在于查询中的 2 个位置,因此根据SQL plan,它被评估两次。有什么方法可以cache 该子查询的结果并在两个位置都使用它?

子查询不能转换为连接,因为没有单个字段可以连接(也不能是无条件连接,因为计数会出错)

【问题讨论】:

  • 您似乎有多余的表达。有一个子句说“a.assigneeid = 65548”和“a.assigneeid in (subquery)”是没有意义的——它们要么都是真的,要么都是假的。
  • 这只是出于示范的原因(虽然逻辑很糟糕!)

标签: sql postgresql subquery postgresql-9.1


【解决方案1】:

您可以使用公用表快递(WITH

with cte as 
(
     SELECT userid FROM groupmembers WHERE groupid = 65553
)
SELECT 
    date_trunc('day', assigndate)e,  
    count(CASE WHEN a.assigneeid = 65548 AND a.assigneeid IN  
           (SELECT userid from cte) then 1 else null end) assigned,
...

【讨论】:

  • 另一种变体是使用一个临时表并加入它,这样你就只执行给定的语句一次。
  • 在 SQL Server 中,这保证子查询只会运行一次。 Postgres 能保证这一点吗?
  • @JustBob - 没有办法将该临时表与分配表连接起来,并且联合将是无条件的,导致计数错误
  • 它是documented: WITH 查询的一个有用特性是每次执行父查询时它们只被评估一次,即使它们被父查询多次引用或同级 WITH 查询。
  • 哦,是的。在我提供的实际链接中。 D'oh :)
【解决方案2】:

您应该重写查询以消除子查询:

SELECT date_trunc('day', assigndate)e,
       sum(CASE WHEN a.assigneeid = 65548 and gm.userid is not null then 1 else 0
           end) as assigned,
       sum(CASE WHEN a.assigneeid = 65548 and a.completedtime IS NOT NULL and gm.userid is not null
                then 1 else 0
           end) as completed
FROM ASSIGNMENT a left outer join
     (select distinct userid
      from groupmembers
      where groupid = 65553
     ) gm
     on a.assigneeid = gm.userid
WHERE assigndate > CURRENT_TIMESTAMP - interval '20 days'
GROUP BY date_trunc('day',assigndate)
order by 1

一般来说,我认为在FROM(或WITH)子句中保留表引用是一种很好的做法。可能很难遵循SELECT 子句中的子查询逻辑。在这种情况下,子查询是如此相似,以至于它们实际上是在乞求组合成一个语句。

【讨论】:

  • 感谢指出相似之处,但在实际场景中,也有一个带有approvalrid的count子句。因此,我无法加入 assigneeid
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-13
  • 1970-01-01
  • 1970-01-01
  • 2021-01-20
  • 1970-01-01
相关资源
最近更新 更多