Выборка начальных значений повторяющихся последовательностей
Столкнулся с проблемой при выборке данных. Имеется таблица:
| ID | DDATE | STATUS |
|----|-----------------------|--------|
| 1 | 2000-01-01 01:00:00.0 | aaaaaa |
| 1 | 2000-01-01 02:00:00.0 | aaaaaa |
| 1 | 2000-01-01 03:00:00.0 | aaaaaa |
| 1 | 2000-01-01 04:00:00.0 | aaaaaa |
| 1 | 2000-02-01 05:00:00.0 | bbbbbb |
| 1 | 2000-02-01 06:00:00.0 | cccccc |
| 1 | 2000-02-01 07:00:00.0 | cccccc |
| 1 | 2000-02-01 08:00:00.0 | bbbbbb |
| 1 | 2000-02-01 09:00:00.0 | cccccc |
| 1 | 2000-02-01 10:00:00.0 | cccccc |
| 1 | 2000-02-01 11:00:00.0 | cccccc |
| 1 | 2000-02-01 12:00:00.0 | dddddd |
| 2 | 2000-01-01 13:00:00.0 | aaaaaa |
| 2 | 2000-02-01 14:00:00.0 | aaaaaa |
| 2 | 2000-02-01 15:00:00.0 | aaaaaa |
| 2 | 2000-02-01 16:00:00.0 | aaaaaa |
| 2 | 2000-03-01 17:00:00.0 | bbbbbb |
| 2 | 2000-03-01 18:00:00.0 | cccccc |
| 2 | 2000-03-01 19:00:00.0 | cccccc |
| 2 | 2000-04-01 20:00:00.0 | bbbbbb |
| 2 | 2000-04-01 21:00:00.0 | cccccc |
| 2 | 2000-04-01 22:00:00.0 | cccccc |
| 2 | 2000-04-01 23:00:00.0 | cccccc |
| 2 | 2000-04-01 23:30:00.0 | dddddd |
Нужно получить первые (по времени) записи по каждой последовательности статусов для каждого id:
| ID | DDATE | STATUS |
|----|-----------------------|--------|
| 1 | 2000-01-01 01:00:00.0 | aaaaaa |
| 1 | 2000-02-01 05:00:00.0 | bbbbbb |
| 1 | 2000-02-01 06:00:00.0 | cccccc |
| 1 | 2000-02-01 08:00:00.0 | bbbbbb |
| 1 | 2000-02-01 09:00:00.0 | cccccc |
| 1 | 2000-02-01 12:00:00.0 | dddddd |
| 2 | 2000-01-01 13:00:00.0 | aaaaaa |
| 2 | 2000-03-01 17:00:00.0 | bbbbbb |
| 2 | 2000-03-01 18:00:00.0 | cccccc |
| 2 | 2000-04-01 20:00:00.0 | bbbbbb |
| 2 | 2000-04-01 21:00:00.0 | cccccc |
| 2 | 2000-04-01 23:30:00.0 | dddddd |
Мне удалось получить только первые статусы по каждому id, с потерей повторов:
SELECT ID, MIN(DDATE) dd, STATUS FROM Table1
GROUP BY ID, STATUS
ORDER BY ID, dd
Подскажите, пожалуйста, хотя бы, направление.
UPD: Воспроизводимый пример http://sqlfiddle.com/#!4/3de9e/1/0.
Ответы (2 шт):
Чтобы избежать повторения, здесь недостаточно обойтись просто запросом, понадобится цикл. Я пытался реализовать это на PL\SQL но как то много ошибок по неопытности у меня появилось. Я написал на TSQL концепцию, если получится перевести на Oracle её то всё будет как надо
-- Это исходная таблица
CREATE TABLE #Table1 (ID INT, DDATE DATETIME, SSTATUS VARCHAR(255))
INSERT #Table1 (ID, DDATE, SSTATUS) VALUES
( 1 , '2000-01-01 01:00:00.0' , 'aaaaaa'),
( 1 , '2000-01-01 02:00:00.0' , 'aaaaaa'),
( 1 , '2000-01-01 03:00:00.0' , 'aaaaaa'),
( 1 , '2000-01-01 04:00:00.0' , 'aaaaaa'),
( 1 , '2000-02-01 05:00:00.0' , 'bbbbbb'),
( 1 , '2000-02-01 06:00:00.0' , 'cccccc'),
( 1 , '2000-02-01 07:00:00.0' , 'cccccc'),
( 1 , '2000-02-01 08:00:00.0' , 'bbbbbb'),
( 1 , '2000-02-01 09:00:00.0' , 'cccccc'),
( 1 , '2000-02-01 10:00:00.0' , 'cccccc'),
( 1 , '2000-02-01 11:00:00.0' , 'cccccc'),
( 1 , '2000-02-01 12:00:00.0' , 'dddddd'),
( 2 , '2000-01-01 13:00:00.0' , 'aaaaaa'),
( 2 , '2000-02-01 14:00:00.0' , 'aaaaaa'),
( 2 , '2000-02-01 15:00:00.0' , 'aaaaaa'),
( 2 , '2000-02-01 16:00:00.0' , 'aaaaaa'),
( 2 , '2000-03-01 17:00:00.0' , 'bbbbbb'),
( 2 , '2000-03-01 18:00:00.0' , 'cccccc'),
( 2 , '2000-03-01 19:00:00.0' , 'cccccc'),
( 2 , '2000-04-01 20:00:00.0' , 'bbbbbb'),
( 2 , '2000-04-01 21:00:00.0' , 'cccccc'),
( 2 , '2000-04-01 22:00:00.0' , 'cccccc'),
( 2 , '2000-04-01 23:00:00.0' , 'cccccc'),
( 2 , '2000-04-01 23:30:00.0' , 'dddddd')
-- Это промежуточная таблица с полем IDENTITY (AUTO_INCREMENT) с отсортированными данными по ID и DDATE
CREATE TABLE #Table2 (RowNum INT IDENTITY, ID INT, DDATE DATETIME, SSTATUS VARCHAR(255))
INSERT INTO #Table2 (ID, DDATE, SSTATUS)
SELECT t.ID, t.DDATE, t.SSTATUS FROM #Table1 t ORDER BY t.ID, t.DDATE
-- Результирующая таблица
CREATE TABLE #Table3 (ID INT, DDATE DATETIME, SSTATUS VARCHAR(255))
DECLARE @CurrentRowNum INT
DECLARE @CurrentId INT = NULL
DECLARE @CurrenDate DATETIME = NULL
DECLARE @CurrentStatus VARCHAR(255) = NULL
DECLARE @OldId INT = NULL
DECLARE @OldStatus VARCHAR(255) = NULL
-- Начнём с минимальной записи, ей будет соответствовать
-- пара минимального ID и DDATE
SELECT @CurrentRowNum = MIN(RowNum) FROM #Table2 t
-- Циклом бежим по всем записям, пока не кончатся
WHILE @CurrentRowNum IS NOT NULL
BEGIN
SELECT
@CurrentId = t.ID,
@CurrenDate = t.DDATE,
@CurrentStatus = t.SSTATUS
FROM #Table2 t
WHERE t.RowNum = @CurrentRowNum
-- Ищем различие с предыдущей записью по ID или по STATUS
IF @CurrentId <> ISNULL(@OldId, 0) OR @CurrentStatus <> ISNULL(@OldStatus, '')
BEGIN
INSERT #Table3 (ID, DDATE, SSTATUS) SELECT @CurrentId, @CurrenDate, @CurrentStatus
SELECT
@OldId = @CurrentId,
@OldStatus = @CurrentStatus
END
SELECT @CurrentRowNum = MIN(RowNum) FROM #Table2 t WHERE t.RowNum > @CurrentRowNum
END
-- Выводим необходимый результат
SELECT * FROM #Table3 t ORDER BY t.ID, t.DDATE
DROP TABLE #Table1
DROP TABLE #Table2
DROP TABLE #Table3
А вот решение одним запросом, проверил на Oracle g12 всё работает:
SELECT
SQ3.ID,
SQ3.DDATE,
SQ3.SSTATUS
FROM
(
SELECT
(CASE WHEN SQ.RowNumber = 1 THEN SQ.ID ELSE SQ2.ID END) AS ID,
(CASE WHEN SQ.RowNumber = 1 THEN SQ.DDATE ELSE SQ2.DDATE END) AS DDATE,
(CASE WHEN SQ.RowNumber = 1 THEN SQ.SSTATUS ELSE SQ2.SSTATUS END) AS SSTATUS,
(CASE WHEN (SQ.SSTATUS <> SQ2.SSTATUS) OR SQ.RowNumber = 1 THEN 1 ELSE 0 END) AS IsChangeStatus
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY t1.ID, t1.DDATE ) AS RowNumber,
t1.*
FROM Table1 t1
) SQ
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER ( ORDER BY t1.ID, t1.DDATE ) AS RowNumber,
t1.*
FROM Table1 t1
) SQ2 ON SQ.RowNumber + 1 = SQ2.RowNumber
) SQ3
WHERE SQ3.IsChangeStatus = 1
ORDER BY SQ3.ID, SQ3.DDATE
Решил следующим образом:
Cоздал промежуточную таблицу, пронумеровав все строки Table1 (при нумерации строки отсортировал по ид и дате):
CREATE TABLE TABLE2 (
"RowNum" NUMBER GENERATED ALWAYS AS IDENTITY (
MAXVALUE 1000),
ID NUMBER,
DDATE DATE,
SSTATUS NVARCHAR2(50)
);
INSERT INTO TABLE2 (ID, DDATE, SSTATUS)
SELECT ID, DDATE, SSTATUS FROM Table1 ORDER BY ID, DDATE;
Дальше создал пустую TABLE3:
CREATE TABLE TABLE3 (
ID NUMBER,
DDATE DATE,
SSTATUS NVARCHAR2(50)
);
И в цикле вставил туда нужные строки:
DECLARE
laststatus VARCHAR2(50) := 'st';
currentst VARCHAR2(50);
currentid NUMBER;
currentdate DATE;
BEGIN
FOR i IN 1..74 -- тут костыль на моё количество строк в TABLE2
LOOP
BEGIN
SELECT ID, DDATE, SSTATUS INTO currentid, currentdate, currentst FROM TABLE2 WHERE "RowNum" = i;
IF currentst != laststatus THEN
INSERT INTO TABLE3 (ID, DDATE, SSTATUS) VALUES (currentid, currentdate, currentst);
END IF;
END;
laststatus := currentst;
END LOOP;
END;