【问题标题】:MySQL time ranges exluding overlapping onesMySQL 时间范围,不包括重叠的时间范围
【发布时间】:2021-08-25 19:49:20
【问题描述】:
id start_date end_date
7 2021-08-07 15:25:10.000000 2021-08-10 15:25:10.000000
8 2021-08-09 15:25:10.000000 2021-08-12 15:25:10.000000
9 2021-08-19 15:25:10.000000 2021-08-22 15:25:10.000000
10 2021-08-21 15:25:10.000000 2021-08-25 15:25:10.000000

我有这样的时间数据重叠。我要做的是取开始和结束而不计算中间的那些,然后计算它们之间的差距。所以我期望的结果是

start_date end_date
2021-08-07 15:25:10.000000 2021-08-12 15:25:10.000000
2021-08-19 15:25:10.000000 2021-08-25 15:25:10.000000

我这样写了一个简单的查询,但是结果和差错了,我该怎么办? (我的表只包含 id、start_date 和 end_date)

SELECT *,TIME_TO_SEC(TIMEDIFF(end_date, start_date)) / 3600 AS Total
FROM (
  SELECT   DISTINCT *
        ,(SELECT MIN(start_date) FROM deneme
          WHERE end_date BETWEEN t.start_date AND t.end_date) AS TIME_ENTER
        ,(SELECT MAX(end_date) FROM deneme
          WHERE start_date BETWEEN t.start_date AND t.end_date) AS TIME_EXIT
  FROM deneme t
  ) AS o
GROUP BY id;

【问题讨论】:

  • select version(); 显示什么?
  • @ysth 10.4.20-MariaDB
  • 您能否更准确地说明您的表格数据?向我们展示运行 SELECT * FROM deneme 时结果表数据的样子。
  • @FaNo_FN 感谢您的建议,我编辑了它。

标签: mysql datetime overlap overlapping


【解决方案1】:

一个想法:

列出没有记录的最小和最大日期,然后用它来区分之前和之后(无记录日期范围)作为一个单独的组。

*完整的分解,请参考这个小提琴链接:https://dbfiddle.uk/?rdbms=mariadb_10.4&fiddle=d366ff58efc1f520132ed7cd81d729de

细分:

  1. 根据表中的现有数据生成日期范围。幸运的是,MariaDB 10.4 支持WITH RECURSIVE 的窗口功能,因此一旦您了解了WITH RECURSIVE 的工作原理,就很容易生成日期范围:
WITH RECURSIVE cte AS (
   SELECT MIN(start_date) AS dt, MAX(end_date) AS mxdt FROM deneme UNION ALL
    SELECT dt+INTERVAL 1 DAY, mxdt FROM cte WHERE dt+INTERVAL 1 DAY <= mxdt)

SELECT * FROM cte;
  1. 将生成的日期范围和LEFT JOIN与表deneme一起使用,只获取没有记录的日期,获取MIN()MAX()的日期:
WITH RECURSIVE cte AS (
   SELECT MIN(start_date) AS dt, MAX(end_date) AS mxdt FROM deneme UNION ALL
    SELECT dt+INTERVAL 1 DAY, mxdt FROM cte WHERE dt+INTERVAL 1 DAY <= mxdt)
    
SELECT MIN(dt) min_no_record, MAX(dt) max_no_record
FROM cte 
LEFT JOIN deneme d1 ON dt BETWEEN d1.start_date AND d1.end_date
WHERE d1.start_date IS NULL;
  1. 将上面的查询设为子查询并再次将CROSS JOIN 与表deneme 一起使用,但这次使用CASE 表达式来分配在不存在的记录期之前和之后存在的每个日期:
WITH RECURSIVE cte AS (
   SELECT MIN(start_date) AS dt, MAX(end_date) AS mxdt FROM deneme UNION ALL
    SELECT dt+INTERVAL 1 DAY, mxdt FROM cte WHERE dt+INTERVAL 1 DAY <= mxdt)
    
SELECT *,
       CASE WHEN d.end_date < min_no_record THEN min_no_record
            WHEN d.start_date > max_no_record THEN max_no_record
            ELSE 0 END
FROM deneme d 
CROSS JOIN   
(SELECT MIN(dt) min_no_record, MAX(dt) max_no_record
FROM cte 
LEFT JOIN deneme d1 ON dt BETWEEN d1.start_date AND d1.end_date
WHERE d1.start_date IS NULL) v;

*CASE 表达式中... THEN min_no_record... THEN max_no_record 的部分可以是任何东西。您可以将其定义为... THEN 1 ... THEN 2,但这取决于您。

  1. 最后,获取组的MIN()MAX() 日期。上面的CASE 表达式在GROUP BY 中使用;从SELECT 中省略:
WITH RECURSIVE cte AS (
    SELECT MIN(start_date) AS dt, MAX(end_date) AS mxdt FROM deneme UNION ALL
    SELECT dt+INTERVAL 1 DAY, mxdt FROM cte WHERE dt+INTERVAL 1 DAY <= mxdt)
 SELECT MIN(d.start_date),
        MAX(d.end_date)
   FROM deneme d 
    CROSS JOIN
  (SELECT MIN(dt) min_no_record, MAX(dt) max_no_record
     FROM cte 
     LEFT JOIN deneme d1 ON dt BETWEEN d1.start_date AND d1.end_date
   WHERE d1.start_date IS NULL) v
GROUP BY CASE WHEN d.end_date < min_no_record THEN min_no_record
            WHEN d.start_date > max_no_record THEN max_no_record
            ELSE 0 END;

【讨论】:

    猜你喜欢
    • 2014-07-24
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多