2017-05-15 30 views
2

如何计算具有两个日期列的表中每个月的唯一天数,其中期间可能存在间隙和重叠?MySQL:计算重叠和间隔期间的独特天数(优化后)

我宁愿不使用日历表来获取独特的日子,因为它会生成一个包含数千条记录的临时表,并且资源有限。

示例表:

+---------+------------+------------+ 
| mygroup | alpha  | omega  | 
+---------+------------+------------+ 
|  1 | 2017-02-04 | 2017-04-14 | 
|  1 | 2017-03-25 | 2017-03-28 | 
|  1 | 2017-01-23 | 2017-01-25 | 
|  2 | 2017-02-05 | 2017-02-20 | 
|  1 | 2017-04-28 | 2017-05-12 | 
| etc. | etc. | etc. | 
+---------+------------+------------+ 
+0

请提供表例如 – smaiakov

+0

添加例如表 – Code4R7

+0

答案是每个组独特的日子吗?或为每一行? – smaiakov

回答

0

还有另一种方法可以比日历表快大约10倍。

最大的资源掠夺者是日历表本身,它用于过滤独特的日子。 但是,不使用整个表记录,它可以在UINT中使用31位完成。

Recepe:

  1. 伴月创建日历表个月仅
  2. 切段,以及与日历表加入他们
  3. 转换周期来的uint
  4. OR占的uint的位月份唯一性
  5. 每月计算它们的位数为独特的天数

输出:

+--------------+---------+---------+-------+ 
| Period  | Group 1 | Group 2 | Total | 
+--------------+---------+---------+-------+ 
| 2017 month 5 |  11 |  0 | 11 | 
| 2017 month 4 |  15 |  0 | 15 | 
| 2017 month 3 |  30 |  0 | 30 | 
| 2017 month 2 |  24 |  15 | 39 | 
| 2017 month 1 |  2 |  0 |  2 | 
+--------------+---------+---------+-------+ 

MySQL查询:

SELECT 
    `tabulate`.`period` AS `Period`, 
    SUM(IF(`tabulate`.`mygroup` = 1,    
    `tabulate`.`days`, 0)) AS `Group 1`, 
    SUM(IF(`tabulate`.`mygroup` = 2,    
    `tabulate`.`days`, 0)) AS `Group 2`, 
    SUM(`tabulate`.`days`) AS `Total` 
FROM 
    (SELECT 
     `unique`.`period`, 
     BIT_COUNT(BIT_OR(CONV(CONCAT(
     REPEAT("1", DAYOFMONTH(`unique`.`omega`) - DAYOFMONTH(`unique`.`alpha`)), 
     REPEAT("0", DAYOFMONTH(`unique`.`alpha`) - 1) 
    ), 2, 10))) AS `days`, 
     `unique`.`mygroup` 
    FROM 
     (SELECT 
      DATE_FORMAT(`permonth`.`period_alpha`, "%Y month %c") AS `period`, 
      GREATEST(`permonth`.`period_alpha`, `permonth`.`example_alpha`) AS `alpha`, 
      LEAST(`permonth`.`period_omega`, `permonth`.`example_omega`) AS `omega`, 
      `permonth`.`mygroup` 
     FROM 
      (SELECT 
       `period`.`alpha` AS `period_alpha`, 
       DATE_SUB(`period`.`omega`, INTERVAL 1 DAY) AS `period_omega`, 
       `example`.`mygroup`, 
       IFNULL(`example`.`alpha`, `period`.`alpha`) AS `example_alpha`, 
       IFNULL(`example`.`omega`, CURDATE()) AS `example_omega` 
      FROM 
       (SELECT 
        DATE_ADD(
        MAKEDATE(YEAR(CURDATE()), 1), 
        INTERVAL `season`.`n` + (`month`.`n` << 2) MONTH 
       ) AS `alpha`, 
        DATE_ADD(
        MAKEDATE(YEAR(CURDATE()), 1), 
        INTERVAL 1 + `season`.`n` + (`month`.`n` << 2) MONTH 
       ) AS `omega` 
       FROM 
        (   SELECT 0 AS `n` 
        UNION ALL SELECT 1 
        UNION ALL SELECT 2 
       ) AS `month` 
        CROSS JOIN (SELECT 0 AS `n` 
        UNION ALL SELECT 1 
        UNION ALL SELECT 2 
        UNION ALL SELECT 3 
       ) AS `season` 
      ) AS `period` 
       INNER JOIN 
       (SELECT 1 AS `mygroup`, "2017-02-04" AS `alpha`, "2017-04-14" AS `omega` 
        UNION ALL SELECT 1, "2017-03-25", "2017-03-28" 
        UNION ALL SELECT 1, "2017-01-23", "2017-01-25" 
        UNION ALL SELECT 2, "2017-02-05", "2017-02-20" 
        UNION ALL SELECT 1, "2017-04-28", "2017-05-12" 
       ) AS `example` ON (
        (`example`.`alpha` < `period`.`omega` OR `example`.`alpha` IS NULL) 
        AND IFNULL(`example`.`omega`, CURDATE()) >= `period`.`alpha` 
       ) 
     ) AS `permonth` 
    ) AS `unique` 
    GROUP BY 
     `unique`.`period`, 
     `unique`.`mygroup` 
) AS `tabulate` 
GROUP BY `tabulate`.`period` 
ORDER BY `tabulate`.`period` DESC 
2

难道你需要什么?

select count(distinct selected_date),te.mygroup, MONTHNAME(selected_date)from 
(select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) selected_date from 
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0, 
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1, 
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2, 
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3, 
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) v 
cross join test te 
where selected_date between te.alpha and te.omega 
group by mygroup, MONTHNAME(selected_date) 

Оutput您例如:

'17','1','April' 
'25','1','February' 
'3','1','January' 
'31','1','March' 
'12','1','May' 
'16','2','February' 

计数可能比天数更大的月份,因为这种重叠的几行存在 - 这不是а错误。

+0

不错的做法,它似乎工作,除了第一个输出行有一个错字,天数应该是12.但它也很慢,因为使用临时日历表。 – Code4R7

+0

编号天数应该是17因为我们有4月在两行=)关于性能:将很好创建物化视图,而不是使用临时表。但据我所知 - mysql不支持它。 – smaiakov