我最近在SO上看到了将来自同一history
表的三个查询合并为一个以提高性能的请求。
三个查询是
SELECT COUNT(*) as number, SUM(order_total) as sum FROM history;
SELECT COUNT(*) as number, SUM(order_total) as sum FROM history
WHERE date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY));
SELECT COUNT(*) as number, SUM(order_total) as sum FROM history
WHERE date <= UNIX_TIMESTAMP(CURDATE());
所以我认为我会格式化一个更一般的问题,以上为例:如何组合更多的查询,以及如何最好地进行?
答案 0 :(得分:1)
所有查询都访问相同的变量,并且仅用于运行总和和总计的条件不同。
要在一个查询中全部运行,我们必须将每个结果分配到不同的列,因此我们不会number
和sum
而是number1
},number2
,... sum3
,以便访问结果。
通常,COUNT()
,SUM()
等是aggregate functions,因此我们将使用包含该条件的新表达式替换每个实例。
例如:COUNT(*) WHERE some_condition
与
add 1 for each record among the records where <some_condition>
可以改写(尽管有点慢)
add 1 if <some_condition>, else 0, for each record among ALL the records
是
SUM(IF(<some_condition>, 1, 0))
同样适用于SUM(value) WHERE <some_condition>
:它变为SUM(IF(<some_condition>, value, 0))
。
在考虑MIN()
,MAX()
和AVG()
时,我们发现默认值0可能会有问题。使用NULL而不是0来解决此问题。
我们的第一次迭代允许简单的替换:
Single query Combined query
COUNT(*) SUM(<conditionalOne>)
SUM(value) SUM(<conditionalValue>)
AVG(value) AVG(<conditionalValue>)
MIN(value) MIN(<conditionalValue>)
...
<conditionalValue>
,如果<condition>
存在,
IF(<condition>, value, NULL)
,只是value
。 <conditionalOne>
是<conditionalValue>
,其中值等于1.否则,value
可以是字段名称或表达式。
所以我们的示例查询变为:
SELECT
SUM(1) AS number1, SUM(order_total) AS sum1,
SUM(IF(date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)), 1, NULL)) AS number2,
SUM(IF(date <= UNIX_TIMESTAMP(DATE_ADD(CURDATE(),INTERVAL -30 DAY)), order_total, NULL)) AS sum2,
SUM(IF(date <= UNIX_TIMESTAMP(CURDATE()), 1, NULL)) AS number3,
SUM(IF(date <= UNIX_TIMESTAMP(CURDATE()), order_total, NULL)) AS sum3
FROM history;
在这种情况下,至少有一个条件对整个表有效,即一个查询没有WHERE
;所以我们需要扫描整个表格。那么我们也可以完全没有WHERE
。
否则我们会合并这三个条件并使用它们中最大或最宽松的(所以如果我们选择去年,上个月和上周,我们实际上只会添加去年的选择)。
我们可以自动执行此操作,并希望MySQL优化器能够解决问题:
WHERE (<condition1>) OR (<condition2>) OR (<condition3>);
由于索引,很可能单个查询实际上会比几个脱节查询运行更慢。如果条件和值实际上针对几个不同的列,通常会发生这种情况,从而使索引效率降低。
如果根本没有索引,那么合并查询应该总是比单独运行它们更方便。
理论上,我们希望covering index包含WHERE
子句中出现的所有列,从具有最小基数的列到具有最大基数的列,然后是表达式中出现的所有列。这样,MySQL选择器将快速置零所需的行,并且还将在内存中找到所需的值。
在此示例中,条件基于date
,查询要求order_total
,因此我们只使用两列创建索引。
CREATE INDEX history_stat_ndx ON history(`date`, order_total);
然而,在实践中,覆盖指数可能太大而无法被接受,或者如果它是有益的。在这种情况下,我们仍然会合并多个查询,但这次是多个查询:
需要全表扫描和/或大量列的查询,特别是如果其他查询不需要相同的查询,它将自行完成,并将与具有相同特征的所有其他查询合并,并且没有被编入索引(我们从索引中获得的收益很少。不适用于WHERE,因为有一个完整的表扫描,而不是来自覆盖,因为那里的列太多了。)
< / LI>表达式中需要类似条件或类似列的所有查询可以组合在一起,如果条件确实相似,则可能编入索引。每个组可能具有不同的索引,针对该组及其表达式进行了优化。