从表中的不同条件中选择COUNT

时间:2011-06-14 21:05:37

标签: sql query-optimization subquery

我有一张名为'jobs'的表。对于特定用户,作业可以是活动的,存档的,过期的,挂起的或关闭的。现在每个页面请求都生成5个COUNT个查询,并且在尝试优化时我试图将其减少为单个查询。这是我到目前为止所拥有的,但它几乎比5个单独的查询快。请注意,我已经简化了每个子查询的条件以使其更容易理解,但完整查询的行为却相同。

有没有办法在不使用低效子查询的情况下在同一查询中获取这5个计数?

SELECT
  (SELECT count(*)
    FROM "jobs"
    WHERE
      jobs.creator_id = 5 AND
      jobs.status_id NOT IN (8,3,11) /* 8,3,11 being 'inactive' related statuses */
  ) AS active_count, 
  (SELECT count(*)
    FROM "jobs"
    WHERE
      jobs.creator_id = 5 AND
      jobs.due_date < '2011-06-14' AND
      jobs.status_id NOT IN(8,11,5,3) /* Grabs the overdue active jobs
                                      ('5' means completed successfully) */
  ) AS overdue_count,
  (SELECT count(*)
    FROM "jobs"
    WHERE
      jobs.creator_id = 5 AND
      jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000'
  ) AS due_today_count

这继续增加2个子查询但我认为你明白了。

是否有更简单的方法来收集这些数据,因为它基本上是5个不同的COUNT来自作业表的相同数据子集?

数据子集是'creator_id = 5',之后每个计数基本上只有1-2个附加条件。请注意,目前我们正在使用Postgres,但可能会在不久的将来转向MySQL。因此,如果您能提供与ANSI兼容的解决方案,我将非常感激:)

3 个答案:

答案 0 :(得分:24)

这是典型的解决方案。使用案例陈述来突破不同的条件。如果记录满足,则得到1,否则为0.然后对值<{p>>执行SUM

  SELECT
    SUM(active_count) active_count,
    SUM(overdue_count) overdue_count
    SUM(due_today_count) due_today_count
  FROM 
  (

  SELECT 
    CASE WHEN jobs.status_id NOT IN (8,3,11) THEN 1 ELSE 0 END active_count,
    CASE WHEN jobs.due_date < '2011-06-14' AND jobs.status_id NOT IN(8,11,5,3)  THEN 1 ELSE 0 END  overdue_count,
    CASE WHEN jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000' THEN 1 ELSE 0 END  due_today_count

    FROM "jobs"
    WHERE
      jobs.creator_id = 5 ) t

UPDATE 如前所述,当0记录作为t返回时,这导致所有值中的Null的单个结果。你有三个选择

1)添加A Having子句,以便您没有返回记录而不是所有NULLS的结果

   HAVING SUM(active_count) is not null

2)如果你想要返回所有零,那么你可以为你的所有总和添加合并

例如

 SELECT
      COALESCE(SUM(active_count)) active_count,
       COALESCE(SUM(overdue_count)) overdue_count
      COALESCE(SUM(due_today_count)) due_today_count

3)利用COUNT(NULL) = 0作为sbarro演示的事实。您应该注意,not-null值可以是它不必是1

的任何值

例如

 SELECT
      COUNT(CASE WHEN 
            jobs.status_id NOT IN (8,3,11) THEN 'Manticores Rock' ELSE NULL
       END) as [active_count]

答案 1 :(得分:12)

我会使用这种方法,将COUNT与CASE WHEN结合使用。

SELECT 
    COUNT(CASE WHEN 
        jobs.status_id NOT IN (8,3,11) THEN 1 
    END) as [Count1],
    COUNT(CASE WHEN 
        jobs.due_date < '2011-06-14' 
        AND jobs.status_id NOT IN(8,11,5,3) THEN 1
    END) as [COUNT2],
    COUNT(CASE WHEN
            jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000'
    END) as [COUNT3]
FROM 
    "jobs"
WHERE 
     jobs.creator_id = 5 

答案 2 :(得分:0)

SQL Server 2012 introduced the IIF logical function。使用SQL Server 2012或更高版本,您现在可以使用此新函数而不是CASE表达式。 IIF函数也适用于 Azure SQL数据库(但目前它不能与 Azure SQL数据仓库并行数据仓库一起使用)。它是CASE表达式的简写。

当只有一个案例时,我发现自己使用IIF函数而不是CASE表达式。这减轻了必须写CASE WHEN condition THEN x ELSE y END并将其写为IIF(condition, x, y)的痛苦。如果可以满足多个条件(多个WHEN s),则应考虑使用常规CASE表达式而不是嵌套的IIF函数。

  

返回两个值中的一个,具体取决于布尔表达式   在SQL Server中计算结果为true或false。

     

语法

IIF ( boolean_expression, true_value, false_value )
     

参数

     

boolean_expression
一个有效的布尔表达式。

     

如果此参数不是布尔表达式,则语法错误为   提高。

     

true_value
如果boolean_expression评估为,则返回的值   真。

     

false_value
如果boolean_expression评估,则返回值   为假。

     

说明

     

IIF是编写CASE表达式的简便方法。它评估   布尔表达式作为第一个参数传递,然后返回   基于结果的其他两个参数中的任何一个   评价。也就是说,如果布尔值,则返回true_value   expression为true,如果是Boolean,则返回false_value   表达是错误的或未知的。 true_valuefalse_value可以   任何类型。适用于CASE表达式的相同规则   布尔表达式,空值处理和返回类型也适用于   IIF。有关详细信息,请参阅CASE (Transact-SQL)

     

IIF被翻译为CASE这一事实也会产生影响   此功能行为的其他方面。自CASE以来   表达式只能嵌套到10 IIF语句的级别   也可以只嵌套到最高级别10.此外,IIF是   作为语义上等效的CASE远程服务于其他服务器   表达式,具有远程CASE表达式的所有行为。

代码

SQL中IIF函数的实现类似于以下内容(使用@rsbarrohis answer提供的相同逻辑):

SELECT 
    COUNT(
        IIF(jobs.status_id NOT IN (8,3,11), 1, 0)
    ) as active_count,
    COUNT(
        IIF(jobs.due_date < '2011-06-14' AND jobs.status_id NOT IN(8,11,5,3), 1, 0)
    ) as overdue_count,
    COUNT(
        IIF(jobs.due_date BETWEEN '2011-06-14' AND '2011-06-15 06:00:00.000000', 1, 0)
    ) as due_today_count
FROM 
    "jobs"
WHERE 
     jobs.creator_id = 5