在单个查询中选择总行数,总字段1 = 1,总字段1 = 0?

Posted

技术标签:

【中文标题】在单个查询中选择总行数,总字段1 = 1,总字段1 = 0?【英文标题】:Select total of rows, total of field1 = 1, total of field1 = 0 in a single query? 【发布时间】:2015-05-20 14:34:54 【问题描述】:

我有下表:

id  | command_id    | started_at            | ended_at              | rows_involved | completed
-----------------------------------------------------------------------------------------------
1   | 1             | 2015-05-20 12:02:25   | 2015-05-20 12:02:28   | 1             | 1
2   | 1             | 2015-05-20 12:02:47   | NULL                  | NULL          | 0
3   | 1             | 2015-05-20 12:11:10   | NULL                  | NULL          | 0
4   | 1             | 2015-05-20 12:11:46   | NULL                  | NULL          | 0
5   | 1             | 2015-05-20 12:12:25   | NULL                  | NULL          | 0

我想获取started_at is '2015-05-20' AND commande_id = 1 的行数,我想获得2 个小计,1 是completed = 1 的这些行的总数,1 是completed = 0 的这些行的总数。

预期的数据集如下:

array(4) 
    ["totalRows"]=> 5
    ["name"]=> "evo:send_post_registration_mail_1"
    ["totalCompleted"] => 1
    ["totalUncompleted"] => 4

“name”列并不重要,它是在 command_id 字段上与另一个表的连接。

我当前的查询如下,但它没有获取 2 个小计:

SELECT COUNT(s0_.id) AS totalRows, s1_.name AS name 
FROM sf_command_executions s0_ 
INNER JOIN sf_commands s1_ ON (s1_.id = s0_.command_id) 
WHERE DATE_FORMAT(s0_.started_at,'%Y-%m-%d') = '2015-05-20' 
GROUP BY s0_.command_id 

我可以在单个查询中获取这 2 个小计吗?

【问题讨论】:

谓词(WHERE 子句)中 s0_.started_at 周围的函数会阻止 mysql 有效使用索引。假设started 列是DATETIMETIMESTAMP,这可以被重写以引用bare 列,并允许索引范围扫描操作。 WHERE s0_.started_at >= '2015-05-20' AND s0_.started_at < '2015-05-20' + INTERVAL 1 DAY. 【参考方案1】:

您可以使用条件聚合。在 SELECT 列表中使用这样的表达式...

SELECT ...
     , SUM(IF(s0_.completed=1,1,0)) AS tot_completed_1
     , SUM(IF(s0_.completed=0,1,0)) AS tot_completed_0

您可以使用(更符合 ANSI 标准)CASE 表达式来实现相同的目的:

     , SUM(CASE WHEN s0_.completed = 1 THEN 1 ELSE 0 END) AS tot_completed_1

或者您可以使用更短的 MySQL 速记,因为布尔表达式返回值 1、0 或 NULL:

     , SUM(s0_.completed=1) AS tot_completed_1

编辑

以下内容未解决您提出的问题(有关您提出的问题的答案,请参见上文)。但我想指出started_at 列上的谓词(即WHERE 子句)。

 WHERE DATE_FORMAT(s0_.started_at,'%Y-%m-%d') = '2015-05-20'
       ^^^^^^^^^^^^              ^^^^^^^^^^^^

包裹在列引用周围的DATE_FORMAT 函数可防止 MySQL 使用索引范围扫描操作来满足该谓词。

也就是说,MySQL 必须对表中的每一行计算该函数,然后将表达式的结果与文字值进行比较。

如果started_at 被定义为DATETIMETIMESTAMP,我们可以将其重写为等效条件,但在 started_at 列上。这将允许 MySQL 使用索引范围扫描操作。例如,我们可以像这样得到相同的行:

 WHERE s0_.started_at >= '2015-05-20'
   AND s0_.started_at <  '2015-05-20' + INTERVAL 1 DAY

如果started_at 被定义为DATE,我们可以通过相等比较来引用裸列。不需要DATE_FORMAT 函数。

如果我们必须使用函数来进行某种类型的转换以便比较值,我们更喜欢使用函数来包裹文字而不是列引用。围绕文字,该函数只需要评估一次。

在这种情况下,这实际上不是必需的,只是作为将文字包装在函数中的示例:

 WHERE s0_.started_at >= STR_TO_DATE('2015-05-20','%Y-%m-%d')
   AND s0_.started_at <  STR_TO_DATE('2015-05-20','%Y-%m-%d') + INTERVAL 1 DAY

请注意(再次)使用STR_TO_DATE 函数实际上并不是必需的;这只是展示一种模式。如果我们确实需要进行转换,我们希望在字面而不是列上进行转换,以允许 MySQL 使用 started_at 上的可用索引。

【讨论】:

接受这两个答案,符合 ANSI 标准是唯一适用于我的情况,因为我使用 Doctrine DQL 来构建查询。你提出的速记返回了一个教义错误。非常感谢! 我刚刚阅读了您关于 DATE_FORMAT 的编辑说明。我完全理解如何摆脱它,但我不明白为什么最好避免使用它。它会使sql语句更快吗? @VaN:如果我们有保证可以使 SQL 语句更高效,那就太好了,但我们没有得到这样的保证。但是,在许多情况下,当有合适的索引可用时,它可以并且确实使语句更有效。将列包装在函数中禁用 MySQL 无法有效地使用索引范围扫描操作。 function 必须针对表中的 每一 行(未被另一个谓词过滤掉)进行评估。在 bare上使用谓词> 列,MySQL 可以对合适的索引进行范围扫描操作,这样可以更快。 好的,我相应地编辑了我的查询。我不能使用 INTERVAL 因为教义不支持它。所以我直接在值中做了操作:-&gt;where('ce.startedAt &gt;= :start') -&gt;setParameter('start', $datetime-&gt;format('Y-m-d')) -&gt;andWhere('ce.startedAt &lt; :end') -&gt;setParameter('end', $datetime-&gt;modify('+1 day')-&gt;format('Y-m-d')) @Van:是的。这看起来像我们更喜欢看到的模式。 (我对 Doctrine 了解不多,但它确实会生成针对数据库运行的 SQL 语句,而且我确实了解 SQL。)最合适的索引可能是 ... ON sf_command_executions (command_id, started_at, id)。 https://dev.mysql.com/doc/refman/5.5/en/using-explain.html【参考方案2】:

您可以使用条件和作为

SELECT 
COUNT(s0_.id) AS totalRows, 
s1_.name AS name ,
sum(s0_.completed=1) as totalCompleted,
sum(s0_.completed=0) as totalUncompleted
FROM sf_command_executions s0_ 
INNER JOIN sf_commands s1_ ON (s1_.id = s0_.command_id) 
WHERE DATE_FORMAT(s0_.started_at,'%Y-%m-%d') = '2015-05-20' 
GROUP BY s0_.command_id 

【讨论】:

接受这两个答案,符合 ANSI 标准是唯一适用于我的情况,因为我使用 Doctrine DQL 来构建查询。你提出的速记返回了一个 Doctrine 错误,但想法就在这里。【参考方案3】:

试试这个:

SELECT COUNT(s0_.id) AS totalRows, s1_.name AS name, 
(select count(S2_.id) from sf_command_executions S2_ where s0_.command_id=S2_.command_id and s2_.completed = 1) AS totalCompleted,
(select count(S2_.id) from sf_command_executions S2_ where s0_.command_id=S2_.command_id and s2_.completed = 0) AS totalUncompleted
FROM sf_command_executions s0_ 
INNER JOIN sf_commands s1_ ON (s1_.id = s0_.command_id) 
WHERE DATE_FORMAT(s0_.started_at,'%Y-%m-%d') = '2015-05-20' 
GROUP BY s0_.command_id 

【讨论】:

上面的 2 个答案没有像你一样使用子查询,它们可能更加优化。

以上是关于在单个查询中选择总行数,总字段1 = 1,总字段1 = 0?的主要内容,如果未能解决你的问题,请参考以下文章

李老师 mongoDB中 字符类型的字段 有大小长度限制吗

在 groupBy 选择后计算总记录数

SQL语句基础

怎么设置bootstrap table 中的总行数或者总页数

按 4 个不同级别和总数字字段分组

按4个不同级别和总数字字段分组