MySQL Select 数据系列之间的日期范围以 NULL 分隔

MySQL Select date ranges between data series separated with NULL

这是解决的一部分。

有一个 table 数据:

+------------+------+----------+-----------+
| date       | data | data_max | data_diff |
+------------+------+----------+-----------+
| 2017-01-02 |    2 |        2 |      NULL |
| 2017-01-03 |    4 |        4 |      NULL |
| 2017-01-04 |    1 |        4 |        -3 |
| 2017-01-05 |    3 |        4 |        -1 |
| 2017-01-06 |    1 |        4 |        -3 |
| 2017-01-07 |    4 |        4 |      NULL |
| 2017-01-08 |    5 |        5 |      NULL |
| 2017-01-09 |   -2 |        5 |        -7 |
| 2017-01-10 |    0 |        5 |        -5 |
| 2017-01-11 |   -5 |        5 |       -10 |
| 2017-01-12 |    6 |        6 |      NULL |
| 2017-01-13 |    4 |        6 |        -2 |
+------------+------+----------+-----------+

我想计算 data_diff 的最小值和最大值,但要针对每个数据子集分别计算。每个数据子集都以 NULL 开头(但最后一个数据子集可能不会以 NULL 结尾,而是以数据结尾)我还需要每个数据子集的开始和结束日期,我以后可以使用它们来计算最小值、最大值。我想获取日期范围:

+----------------+--------------+
| diff_date_from | diff_date_to |
+----------------+--------------+
| 2017-01-04     | 2017-01-06   |
| 2017-01-09     | 2017-01-11   |
| 2017-01-13     | 2017-01-13   |
+----------------+--------------+

如果您想获取示例数据,请查询:

CREATE TABLE IF NOT EXISTS `test`
(
   `date_time` DATETIME UNIQUE NOT NULL,
   `data` INT NOT NULL
)
ENGINE InnoDB;

INSERT INTO `test` VALUES
('2017-01-02', 2),
('2017-01-03', 4),
('2017-01-04', 1),
('2017-01-05', 3),
('2017-01-06', 1),
('2017-01-07', 4),
('2017-01-08', 5),
('2017-01-09', -2),
('2017-01-10', 0),
('2017-01-11', -5),
('2017-01-12', 6),
('2017-01-13', 4)

;

SELECT 
    DATE(`date_time`) AS `date`,
    `data`,
    `data_max`,
    IF(`data` < `data_max`,  - (`data_max` - `data`), NULL) 
    AS `data_diff`

FROM
    (
    SELECT 
        `date_time`,
        `data`,
        MAX(`data`) OVER (ORDER BY `date_time` ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `data_max`
    FROM
        `test`
    ) t
;

是否可以编写提供上述日期范围的单个查询?还是必须应用程序或某种技巧?

也许带有 OVER 的 window 函数会有所帮助,但我不知道如何指定其 window 非 NULL 的当前行与从 NULL 前面的行开始的前面行之间的边界.这完全可行吗?

设置window边界有RANGE运算符Documentation 看起来很有希望:

PRECEDING: For ROWS, the bound is expr rows before the current row. For RANGE, the bound is the rows with values equal to the current row value minus expr; if the current row value is NULL, the bound is the peers of the row.

还有一部分:

ORDER BY X ASC RANGE BETWEEN 10 PRECEDING AND 10 FOLLOWING

The frame starts at NULL and stops at NULL, thus includes only rows with value NULL.

但我不明白 inlcuding only rows with null 的意思。 也许对于日期范围 2017-01-022017-01-03 但对于 2017-01-032017-01-07 怎么会?

我一直在查看您的 "more complex problem"(仍在寻找答案),但这里有解决此问题的方法。鉴于您使用的是 window 函数,我假设您使用的是 MySQL 8,因此也可以使用 CTE:

WITH cte AS (SELECT DATE(`date_time`) AS `date`,
                    `data`,
                    MAX(`data`) OVER (ORDER BY `date_time` ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS `data_max`
             FROM `test`),
cte2 AS (SELECT `date`,
                `data`,
                `data_max`,
                CASE WHEN `data` < `data_max` THEN `data` - `data_max` END AS `data_diff`
         FROM cte)
SELECT `data_max`, 
       MIN(CASE WHEN `data_diff` IS NOT NULL THEN date END) AS diff_date_from,
       MAX(CASE WHEN `data_diff` IS NOT NULL THEN date END) AS diff_date_to
FROM cte2
GROUP BY `data_max`
HAVING diff_date_from IS NOT NULL

输出:

data_max    diff_date_from  diff_date_to
4           2017-01-04      2017-01-06
5           2017-01-09      2017-01-11
6           2017-01-13      2017-01-13

Demo on dbfiddle

这是我在摇摇欲坠的旧 MySQL 版本中的做法...

SELECT MIN(date_time) diff_date_from
     , MAX(date_time) diff_date_to
  FROM
     ( SELECT x.*
            , CASE WHEN @prev > data THEN @prev:=@prev ELSE @prev:= data END data_max
            , (data-@prev < 0) * (CASE WHEN data-@prev < 0  THEN @i:=@i ELSE @i:=@i+1 END) delta 
         FROM test x
            , ( SELECT @prev:=null,@i:=0) vars 
        ORDER 
           BY date_time
     ) a
 WHERE delta <> 0
 GROUP
    BY delta;