Выборка начальных значений повторяющихся последовательностей

Столкнулся с проблемой при выборке данных. Имеется таблица:

| 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 шт):

Автор решения: Andrew Nikolaev

Чтобы избежать повторения, здесь недостаточно обойтись просто запросом, понадобится цикл. Я пытался реализовать это на 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
→ Ссылка
Автор решения: another_login

Решил следующим образом:

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;
→ Ссылка