SQL:在查询中重用函数结果而不使用子查询

时间:2016-06-25 07:56:10

标签: mysql performance

在存储销售订单的MySQL数据库表格中,我有一个LastReviewed列,其中包含修改销售订单的最后日期和时间(类型timestamp,默认值CURRENT_TIMESTAMP )。我想绘制特定用户每天(最近90天)修改的销售数量。

我正在尝试制作一个SELECT,它返回自LastReviewed日期以来的天数,以及有多少记录属于该范围。以下是我的查询,其工作正常:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales
WHERE UserID=123 AND DATEDIFF(CURDATE(),LastReviewed)<=90
GROUP BY days
ORDER BY days ASC

请注意,我为每条记录多次计算DATEDIFF()以及CURDATE()。这似乎真的无效,所以我想知道如何重用以前计算的结果。我尝试的第一件事是:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales
WHERE UserID=123 AND days<=90
GROUP BY days
ORDER BY days ASC

错误:Unknown column 'days' in 'where clause'。所以我开始环顾网络。根据另一个讨论(Can I reuse a calculated field in a SELECT query?),我接下来尝试了以下内容:

SELECT DATEDIFF(CURDATE(), LastReviewed) AS days, COUNT(*) AS number FROM sales
WHERE UserID=123 AND (SELECT days)<=90
GROUP BY days
ORDER BY days ASC

错误:Unknown column 'days' in 'field list'。我也尝试了以下内容:

SELECT @days := DATEDIFF(CURDATE(), LastReviewed) AS days, 
       COUNT(*) AS number FROM sales
WHERE UserID=123 AND @days <=90
GROUP BY days
ORDER BY days ASC

查询返回零结果,因此@days<=90似乎返回false,即使我将它放在SELECT子句中并删除WHERE子句,我也能看到@days值低于90的某些结果。

我通过使用子查询得到了一些工作:

SELECT * FROM (
  SELECT DATEDIFF(CURDATE(),LastReviewed) AS sales , 
         COUNT(*) AS number FROM sales
  WHERE UserID=123
  GROUP BY days
) AS t
WHERE days<=90
ORDER BY days ASC

但我不知道这是否是最有效的方式。更不用说即使这个解决方案每个记录计算一次CURDATE(),即使它的值从查询的开始到结尾都是相同的。这不是浪费吗?我是否想过这个?欢迎提供帮助。

注意:Mods,它应该在CodeReview上吗?我在这里发布是因为我试图使用的代码实际上不起作用

2 个答案:

答案 0 :(得分:1)

您的问题实际上有两个问题。

首先,您忽略了WHERESELECT之前的事实。当服务器评估WHERE <expression>时,它已经知道为评估<expression>而完成的计算的值,并且可以将其用于SELECT

但是,更糟糕的是,您几乎不应该编写使用列作为函数参数的查询,因为这通常需要服务器评估每行的表达式。

相反,你应该使用它:

WHERE LastReviewed < DATE_SUB(CURDATE(), INTERVAL 90 DAY)

优化器会看到这一点并让所有人兴奋,因为DATE_SUB(CURDATE(), INTERVAL 90 DAY)可以解析为常量,可以在<比较的一侧使用,这意味着如果存在一个索引LastReviewed作为最左边的相关列,然后服务器可以使用索引立即消除具有常量值的LastReviewed >=的所有行。

然后DATEDIFF(CURDATE(), LastReviewed) AS daysSELECT仍然需要)只会根据我们已经知道的行进行评估。

在(UserID,LastReviewed)上添加一个索引,服务器将能够非常快速地精确定位相关行。

答案 1 :(得分:1)

内置函数比获取行要便宜得多。

使用以下“复合”索引可以提高性能:

INDEX(UserID, LastReviewed)

更改为

WHERE UserID=123
  AND LastReviewed >= CURRENT_DATE() - INTERVAL 90 DAY

您的公式在函数调用中“隐藏”LastRevieded,使其在索引中无法使用。

如果您仍然对此改进不满意,请考虑每晚查询计算昨天的统计数据并将其放入“汇总表”中。从那里开始,你提到的SELECT可以更快地运行。