MySQL Выберите диапазон дат между рядами данных, разделенными NULL - PullRequest
2 голосов
/ 20 марта 2019

Это часть решения более сложной задачи .

Существует таблица с данными:

+------------+------+----------+-----------+
| 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, а данными). Мне также нужна дата начала и окончания каждого подмножества данных, которую я позже смогу использовать для вычисления значений Min, Max.Я хотел бы получить диапазоны дат:

+----------------+--------------+
| 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 могла бы помочь, но я не знаю, как указать границу окна между текущей строкой, которая не является NULL, и началом предыдущих строк.из строки, которой предшествует NULL.Это вообще возможно?

Существует оператор RANGE для установки границы окна Документация , которая выглядит многообещающе:

PRECEDING: Для ROWS граница - это строки expr перед текущей строкой, Для RANGE граница - это строки со значениями, равными текущему значению строки минус expr;если текущим значением строки является NULL, то пределом является пиров строки.

и другой части:

ORDER BY X ASC RANGEМЕЖДУ 10 ПРЕКРАСНЫМИ И 10 СЛЕДУЮЩИМИ

Кадр начинается с NULL и заканчивается на NULL, поэтому включает в себя только строки со значением NULL.

Но я не понимаю inlcuding only rows with null.Возможно, для диапазона дат от 2017-01-02 до 2017-01-03, но для 2017-01-03 до 2017-01-07 как получится?

Ответы [ 2 ]

1 голос
/ 20 марта 2019

Вот как я это сделаю в своей старой разрушенной версии 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;
1 голос
/ 20 марта 2019

Я смотрю на вашу "более сложную проблему" (все еще работаю над ответом), но вот решение этой проблемы.Учитывая, что вы используете оконные функции, я предполагаю, что вы используете 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

Демонстрация включенаdbfiddle

...