我有一个查询,在选择中包含很多case语句,看起来像这样...
SELECT
('2017-41') AS yearweek_week_1,
('09/10/2017') AS date_week_1,
COUNT(CASE WHEN `created_at` > DATE_SUB('2017-10-02 00:00:00', INTERVAL 1 DAY) AND `created_at` < DATE_SUB('2017-10-09 00:00:00', INTERVAL 1 DAY) THEN my_user_id ELSE NULL END) AS total_week_1,
COUNT(DISTINCT CASE WHEN `created_at` > DATE_SUB('2017-10-02 00:00:00', INTERVAL 1 DAY) AND `created_at` < DATE_SUB('2017-10-09 00:00:00', INTERVAL 1 DAY) THEN my_user_id ELSE NULL END) AS distinct_week_1,
...
// all weeks between 1 and 52 between here...
('2018-40') AS yearweek_week_52,
('01/10/2018') AS date_week_52,
COUNT(CASE WHEN `created_at` > DATE_SUB('2018-09-24 00:00:00', INTERVAL 1 DAY) AND `created_at` < DATE_SUB('2018-10-01 00:00:00', INTERVAL 1 DAY) THEN my_user_id ELSE NULL END) AS total_week_52,
COUNT(DISTINCT CASE WHEN `created_at` > DATE_SUB('2018-09-24 00:00:00', INTERVAL 1 DAY) AND `created_at` < DATE_SUB('2018-10-01 00:00:00', INTERVAL 1 DAY) THEN my_user_id ELSE NULL END) AS distinct_week_52
FROM my_table
WHERE my_group_id = 123123
AND created_at > '2017-10-02 00:00:00'
如果有帮助,请在表创建语法:
CREATE TABLE `my_table` (
`my_table_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`my_user_id` int(11) DEFAULT NULL,
`my_group_id` int(11) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`my_login_id`),
KEY `my_user_id` (`my_user_id`),
KEY `my_group_id` (`my_group_id`),
KEY `created_at` (`created_at`),
KEY `my_group_id_2` (`my_group_id`,`created_at`),
KEY `my_user_id_2` (`my_user_id`,`created_at`),
CONSTRAINT `fk_groups` FOREIGN KEY (`my_group_id`) REFERENCES `groups` (`group_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_users` FOREIGN KEY (`my_user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
所以select中有104个case语句,这使它非常慢。
是否有更好的方式编写此查询或优化表?
使用EXPLAIN会得到以下结果
id select_type table type possible_keys key
key_len ref rows Extra
1 SIMPLE my_table ref my_group_id,created_at,my_group_id_2 my_group_id_2 5 const 1789636 Using index condition
答案 0 :(得分:0)
这可能有点麻烦,但请耐心等待。当在查询中内联使用@变量时,MySQL具有一些强大的功能。首先是查询,然后我将描述其组成部分。
select
@WeekFrom := date_add( @WeekFrom, interval 7 day ) WeekBeginning,
date_add( @WeekFrom, interval 7 day ) WeekEnding,
concat( @FiscalYr, '-', @WeekNum ) AS ShowDate,
@FiscalYr := @FiscalYr + case when @WeekNum = 52 then 1 else 0 end as NextFYCheck,
@WeekNum := case when @WeekNum = 52 then 1 else @WeekNum +1 end as PrepNextWeekCycle
from
my_table MT,
( select
@WeekFrom := date_sub( '2017-10-01', interval 7 day ),
@WeekNum := 41,
@FiscalYr := 2017 ) sqlvars
limit 52
您可以将其视为查询中的嵌入式程序。您可以使用:=赋值来设置值,并将结果存储为列名,以便再次用作要处理的下一条记录的新值。
接下来,执行此操作后,我不会对您的所有52周年份进行硬编码,因为下周周期需要更改的总PITA值,并担心代表所讨论的一周期间的开始/结束日期。为了解决这个问题,我编写了一个预查询,该查询使用这两个组件来构建会计年度。
打破这个。我从查询“ my_Table”开始,将一个会计年度的52条记录限制为52周。
接下来,我声明一个子选择来声明要使用的@MySQL变量。我从要代表的实际一周开始(通过date_sub)之前1周。因此,如果您的会计年度从2017年10月1日开始,那是在报价内,我减去7天。接下来,我硬性设定您的会计周数字(第41周),然后将会计年度设置为2017。
现在是字段。 @WeekFrom将代表一周的开始,因此取任何最后的值,并添加7天,以标识给定财政周的一周的开始。现在,我可以获取该日期结果,并添加7天以标识比该日期少的一周结束(稍后说明)。然后建立日期以显示为FiscalYear-WeekNumber格式。完成所有操作后,现在我可以在处理的下一个记录上查看我是否要进入下一会计年度(2018)。仅当我刚完成第52周的处理时,才能执行此操作。如果是这样,请在会计年度表示中添加1。最后,我可以在“周”编号上加1,但是如果我刚完成第52周,则将计数器设置回第1周。例如:2017-52会计年度,下一个周期为2018-1。
现在日期。由于您具有日期/时间字段,因此让我们看看日历2017年10月1日是星期日。因此,如果没有明确的时间,则假定为12:00 am。您希望该周内发生的所有交易。因此,我要添加7天,这使我们进入10月8日。但是最后一个WHERE子句(在下一个查询中)将测试10月8日之前的日期。因此,这使事情在10月7日至11:59:59 pm包括在内。因此,现在您不必担心时间部分的组成部分。
因此,现在,上面的查询(如果凭自己的优势运行)将建立并显示会计年度日历,其中包含开始/结束日期以及显示日期和实际日期顺序的显示顺序。
现在下一部分。您想要活动,请根据日期和所讨论的用户ID所在的位置执行简单的JOIN操作。以上方的整个查询为例,并在括号内的下方明确插入其中(并保留括号)
SELECT
FYDates.ShowDate,
FYDates.WeekBeginning,
count(*) as Entries,
count( distinct MT.my_user_id ) as DistinctEntries,
from
my_table MT
JOIN ( TheEntireQueryAsIsFromAbove ) as FYDates
ON MT.Created_At >= FYDates.WeekBeginning
AND MT.Created_At < FYDates.WeekEnding
where
MT.my_group_id = 123123
group by
FYDates.ShowDate,
FYDates.WeekBeginning
order by
FYDates.WeekBeginning
将JOIN通知子查询。它基于计算出的开始周的CreatedAt GREATER或EQUAL条目,并且小于ENDING DATE(因此直到11:59:59 pm)。
现在,您需要再运行一周吗?您实际上可以为
的内部默认声明进行参数化 ( select
@WeekFrom := date_sub( '2017-10-01', interval 7 day ),
@WeekNum := 41,
@FiscalYr := 2017 ) sqlvars
这里唯一的区别是结果将以ROWS而不是列的形式出现,但更适合于性能,可读性,未来财务周的更新等。我宁愿在单个页面上看到52行的列表,而不是宽208列,但那可能只是我:)
答案 1 :(得分:0)
我强烈建议将该查询转换为常规GROUP BY
http://sqlfiddle.com/#!9/b600c5/1
SELECT
DATE_FORMAT(`created_at`, '%Y-%u') AS yearweek_week,
COUNT(my_user_id) AS total_week,
COUNT(DISTINCT my_user_id) AS distinct_week
FROM my_table
WHERE my_group_id = 123123
AND created_at BETWEEN '2017-10-02 00:00:00' AND '2018-10-03 00:00:00'
GROUP BY yearweek_week
所有其余逻辑,例如查找一周中的第一天,以及如果我们错过一年中某些星期的某些数据,则可以获取所有星期,无论使用哪种语言,您都可以在后端应用程序上进行操作。
或者,即使没有数据,您也可以改进查询以将其全部获取,但这将比您的方法更具性能。
但是,即使我的方法对大数据集也不是一件好事。因此,为了改善这一点,我建议添加另一个索引列“ yearweek”,并在需要时更新值-这样我们就可以摆脱动态格式设置。只需SELECT yearweek AS yearweek_week,