Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Normalize dados de transações de colunas de tempo e status para minutos por valor de status


Uma solução para esse tipo de consulta envolve duas partes:geração de categoria seguida de agregação nas categorias geradas.

Para os dados que você forneceu, o primeiro passo neste tipo de solução é agrupar os dados por hora (já que os dados que você forneceu não têm nenhum evento nas 02:00 horas ou nas 04:00 horas, para mostrar essas horas no resultado final, eles podem ser gerados em vez de).

A segunda parte é agregar nos buckets por hora por meio de um pivot , conforme mencionado por Jorge Campos nos comentários.

Abaixo está um exemplo.

Primeiro crie uma tabela de teste:
CREATE TABLE INSERT_TIME_STATUS(
  INSERT_TIME TIMESTAMP,
  STATUS VARCHAR2(128)
);

E adicione os dados de teste:
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 00:00:00', 'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 00:15:00', 'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 00:30:00', 'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 01:30:00', 'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 03:10:00', 'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (TIMESTAMP '2017-01-01 05:00:00', 'NOT AVAILABLE');

Em seguida, crie a consulta. Isso usará fatoração de subconsulta para delinear a natureza de duas etapas desse processo.

O CALENDAR subfator aqui irá gerar cada hora do dia, independentemente de quaisquer registros terem ocorrido durante aquela hora.

O HOUR_CALENDAR O subfactor atribuirá cada registro de status fornecido a uma hora específica e cortará os status que se cruzam em outra hora em pedaços, para que todos os registros caibam em um intervalo de uma hora.

O DURATION_IN_STATUS subfator contará quantos minutos cada status esteve ativo durante cada hora.

A consulta final será PIVOT para agregar (SUM ) a quantidade de tempo que cada STATUS estava ativo durante cada hora.
WITH HOUR_OF_DAY AS (SELECT LEVEL - 1 AS THE_HOUR
                     FROM DUAL
                     CONNECT BY LEVEL < 25),
    CALENDAR AS (SELECT DAY_START
                 FROM (
                   SELECT (TIMESTAMP '2017-01-01 00:00:00' + NUMTODSINTERVAL(DATE_INCREMENT.OFFSET, 'DAY')) AS DAY_START
                   FROM (SELECT LEVEL - 1 AS OFFSET
                         FROM DUAL
                         CONNECT BY LEVEL < 9999) DATE_INCREMENT)
                 WHERE DAY_START BETWEEN (SELECT MIN(TRUNC(INSERT_TIME_STATUS.INSERT_TIME))
                                          FROM INSERT_TIME_STATUS)
                 AND (SELECT MAX(TRUNC(INSERT_TIME_STATUS.INSERT_TIME))
                      FROM INSERT_TIME_STATUS)),
    HOUR_CALENDAR AS (
     SELECT
       TO_CHAR(CALENDAR.DAY_START, 'MM/DD/YYYY')                                               AS THE_DAY,
       HOUR_OF_DAY.THE_HOUR,
       CALENDAR.DAY_START + NUMTODSINTERVAL(HOUR_OF_DAY.THE_HOUR, 'HOUR')                      AS HOUR_START,
       (SELECT MAX(INSERT_TIME_STATUS.STATUS)
       KEEP (DENSE_RANK LAST
         ORDER BY INSERT_TIME_STATUS.INSERT_TIME ASC)
        FROM INSERT_TIME_STATUS
        WHERE INSERT_TIME_STATUS.INSERT_TIME <= DAY_START + NUMTODSINTERVAL(THE_HOUR, 'HOUR')) AS HOUR_START_STATUS
     FROM CALENDAR
       CROSS JOIN HOUR_OF_DAY),
    ALL_HOUR_STATUS AS (
    SELECT
      HOUR_CALENDAR.THE_DAY,
      HOUR_CALENDAR.THE_HOUR,
      HOUR_CALENDAR.HOUR_START        AS THE_TIME,
      HOUR_CALENDAR.HOUR_START_STATUS AS THE_STATUS
    FROM HOUR_CALENDAR
    UNION ALL
    SELECT
      HOUR_CALENDAR.THE_DAY,
      HOUR_CALENDAR.THE_HOUR,
      INSERT_TIME_STATUS.INSERT_TIME AS THE_TIME,
      INSERT_TIME_STATUS.STATUS      AS THE_STATUS
    FROM HOUR_CALENDAR
      INNER JOIN INSERT_TIME_STATUS
        ON HOUR_CALENDAR.HOUR_START < INSERT_TIME_STATUS.INSERT_TIME
           AND HOUR_CALENDAR.THE_HOUR = EXTRACT(HOUR FROM INSERT_TIME_STATUS.INSERT_TIME)),
    DURATION_IN_STATUS AS (
     SELECT
       ALL_HOUR_STATUS.THE_DAY,
       ALL_HOUR_STATUS.THE_HOUR,
       ALL_HOUR_STATUS.THE_STATUS,
       (EXTRACT(HOUR FROM
                (COALESCE(LEAD(THE_TIME)
                          OVER (
                            PARTITION BY NULL
                            ORDER BY THE_TIME ASC ), TO_TIMESTAMP(THE_DAY, 'MM/DD/YYYY') + NUMTODSINTERVAL(THE_HOUR + 1, 'HOUR')) - THE_TIME)) * 60)
       +
       EXTRACT(MINUTE FROM
               (COALESCE(LEAD(THE_TIME)
                         OVER (
                           PARTITION BY NULL
                           ORDER BY THE_TIME ASC ), TO_TIMESTAMP(THE_DAY, 'MM/DD/YYYY') + NUMTODSINTERVAL(THE_HOUR + 1, 'HOUR')) - THE_TIME))
         AS DURATION_IN_STATUS
     FROM ALL_HOUR_STATUS)
SELECT
  THE_DAY,
  THE_HOUR,
  COALESCE(AVAILABLE, 0)     AS AVAILABLE,
  COALESCE(NOT_AVAILABLE, 0) AS NOT_AVAILABLE,
  COALESCE(BUSY, 0)          AS BUSY
FROM DURATION_IN_STATUS
PIVOT (SUM(DURATION_IN_STATUS)
  FOR THE_STATUS
  IN ('AVAILABLE' AS AVAILABLE, 'NOT AVAILABLE' AS NOT_AVAILABLE, 'BUSY' AS BUSY)
)
ORDER BY THE_DAY ASC, THE_HOUR ASC;

Resultado:
THE_DAY     THE_HOUR  AVAILABLE  NOT_AVAILABLE  BUSY  
01/01/2017  0         15         30             15    
01/01/2017  1         30         30             0     
01/01/2017  2         60         0              0     
01/01/2017  3         10         0              50    
01/01/2017  4         0          0              60    
01/01/2017  5         0          60             0     
01/01/2017  6         0          60             0     
01/01/2017  7         0          60             0     
01/01/2017  8         0          60             0     
01/01/2017  9         0          60             0     
01/01/2017  10        0          60             0     
01/01/2017  11        0          60             0     
01/01/2017  12        0          60             0     
01/01/2017  13        0          60             0     
01/01/2017  14        0          60             0     
01/01/2017  15        0          60             0     
01/01/2017  16        0          60             0     
01/01/2017  17        0          60             0     
01/01/2017  18        0          60             0     
01/01/2017  19        0          60             0     
01/01/2017  20        0          60             0     
01/01/2017  21        0          60             0     
01/01/2017  22        0          60             0     
01/01/2017  23        0          60             0     


24 rows selected. 

Esta consulta de exemplo gera registros para o dia inteiro. Portanto, o último status de NOT AVAILABLE realiza. Se você deseja parar no momento do último status atribuído, esse comportamento pode ser ajustado conforme necessário.

EDITAR, em resposta à sua atualização para avaliar esses tempos por channel_id e user_id , aqui está outro exemplo:

Primeiro crie a tabela de teste:
CREATE TABLE INSERT_TIME_STATUS(
  USER_ID NUMBER,
  CHANNEL_ID NUMBER,
  INSERT_TIME TIMESTAMP,
  STATUS VARCHAR2(128)
);

E carregue-o (aqui user_id=1 está nos canais 3 e 4 e user_id=2 está apenas no canal 3):
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 0:00','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 0:15','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 0:30','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 1:30','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 3:10','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,3,TO_TIMESTAMP('1/1/2017 5:00','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 0:00','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 0:15','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 0:30','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 1:30','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 3:10','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (1111,4,TO_TIMESTAMP('1/1/2017 5:00','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 0:00','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 0:15','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 0:30','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 1:30','MM/DD/YYYY HH24:MI'),'AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 3:10','MM/DD/YYYY HH24:MI'),'BUSY');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 5:00','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');
INSERT INTO INSERT_TIME_STATUS VALUES (2222,3,TO_TIMESTAMP('1/1/2017 5:00','MM/DD/YYYY HH24:MI'),'NOT AVAILABLE');

Em seguida, atualize a consulta para gerar dados por user_id por-channel_id . Neste exemplo, os dados são incluídos para todos os tempos, para todos os canais com os quais cada usuário está envolvido. o usuário 1 terá contagens para cada hora do dia para os canais 3 e 4 enquanto o usuário-2 terá contagens para cada hora do dia apenas para o canal 3 (se tiver registros em outro canal, esse canal também será incluído).
WITH HOUR_OF_DAY AS (SELECT LEVEL - 1 AS THE_HOUR
                     FROM DUAL
                     CONNECT BY LEVEL < 25),
    CALENDAR AS (SELECT DAY_START
                 FROM (
                   SELECT ((SELECT MIN(TRUNC(INSERT_TIME_STATUS.INSERT_TIME))
                            FROM INSERT_TIME_STATUS) + NUMTODSINTERVAL(DATE_INCREMENT.OFFSET, 'DAY')) AS DAY_START
                   FROM (SELECT LEVEL - 1 AS OFFSET
                         FROM DUAL
                         CONNECT BY LEVEL < 9999) DATE_INCREMENT)
                 WHERE DAY_START BETWEEN (SELECT MIN(TRUNC(INSERT_TIME_STATUS.INSERT_TIME))
                                          FROM INSERT_TIME_STATUS)
                 AND (SELECT MAX(TRUNC(INSERT_TIME_STATUS.INSERT_TIME))
                      FROM INSERT_TIME_STATUS)),
    USER_CHANNEL_HOUR_CALENDAR AS (
     SELECT
       USER_ID,
       CHANNEL_ID,
       CALENDAR.DAY_START,
       TO_CHAR(CALENDAR.DAY_START, 'MM/DD/YYYY')                                               AS THE_DAY,
       HOUR_OF_DAY.THE_HOUR,
       CALENDAR.DAY_START + NUMTODSINTERVAL(HOUR_OF_DAY.THE_HOUR, 'HOUR')                      AS HOUR_START
     FROM CALENDAR
       CROSS JOIN HOUR_OF_DAY
       --
       CROSS JOIN (SELECT UNIQUE USER_ID, CHANNEL_ID FROM INSERT_TIME_STATUS)
  ),
    HOUR_CALENDAR AS (
     SELECT USER_ID,
       CHANNEL_ID,
       THE_DAY,
       THE_HOUR,
       DAY_START,
       HOUR_START,
       (SELECT MAX(INSERT_TIME_STATUS.STATUS)
       KEEP (DENSE_RANK LAST
         ORDER BY INSERT_TIME_STATUS.INSERT_TIME ASC)
        FROM INSERT_TIME_STATUS
        WHERE INSERT_TIME_STATUS.INSERT_TIME <= DAY_START + NUMTODSINTERVAL(THE_HOUR, 'HOUR')
              AND INSERT_TIME_STATUS.USER_ID = USER_ID
              AND INSERT_TIME_STATUS.CHANNEL_ID = CHANNEL_ID) AS HOUR_START_STATUS
     FROM USER_CHANNEL_HOUR_CALENDAR),
    ALL_HOUR_STATUS AS (
    SELECT
      HOUR_CALENDAR.USER_ID,
      HOUR_CALENDAR.CHANNEL_ID,
      HOUR_CALENDAR.THE_DAY,
      HOUR_CALENDAR.THE_HOUR,
      HOUR_CALENDAR.HOUR_START        AS THE_TIME,
      HOUR_CALENDAR.HOUR_START_STATUS AS THE_STATUS
    FROM HOUR_CALENDAR
    UNION ALL
    SELECT
      INSERT_TIME_STATUS.USER_ID,
      INSERT_TIME_STATUS.CHANNEL_ID,
      HOUR_CALENDAR.THE_DAY,
      HOUR_CALENDAR.THE_HOUR,
      INSERT_TIME_STATUS.INSERT_TIME AS THE_TIME,
      INSERT_TIME_STATUS.STATUS      AS THE_STATUS
    FROM HOUR_CALENDAR
      INNER JOIN INSERT_TIME_STATUS
        ON HOUR_CALENDAR.HOUR_START < INSERT_TIME_STATUS.INSERT_TIME
           AND HOUR_CALENDAR.THE_HOUR = EXTRACT(HOUR FROM INSERT_TIME_STATUS.INSERT_TIME)
           AND HOUR_CALENDAR.USER_ID = INSERT_TIME_STATUS.USER_ID
           AND HOUR_CALENDAR.CHANNEL_ID = INSERT_TIME_STATUS.CHANNEL_ID),
    DURATION_IN_STATUS AS (
     SELECT
       ALL_HOUR_STATUS.USER_ID,
       ALL_HOUR_STATUS.CHANNEL_ID,
       ALL_HOUR_STATUS.THE_DAY,
       ALL_HOUR_STATUS.THE_HOUR,
       ALL_HOUR_STATUS.THE_STATUS,
       (EXTRACT(HOUR FROM
                (COALESCE(LEAD(THE_TIME)
                          OVER (
                            PARTITION BY USER_ID, CHANNEL_ID
                            ORDER BY THE_TIME ASC ), TO_TIMESTAMP(THE_DAY, 'MM/DD/YYYY') + NUMTODSINTERVAL(THE_HOUR + 1, 'HOUR')) - THE_TIME)) * 60)
       +
       EXTRACT(MINUTE FROM
               (COALESCE(LEAD(THE_TIME)
                         OVER (
                           PARTITION BY USER_ID, CHANNEL_ID
                           ORDER BY THE_TIME ASC ), TO_TIMESTAMP(THE_DAY, 'MM/DD/YYYY') + NUMTODSINTERVAL(THE_HOUR + 1, 'HOUR')) - THE_TIME))
         AS DURATION_IN_STATUS
     FROM ALL_HOUR_STATUS)
SELECT
  USER_ID,
  CHANNEL_ID,
  THE_DAY,
  THE_HOUR,
  COALESCE(AVAILABLE, 0)     AS AVAILABLE,
  COALESCE(NOT_AVAILABLE, 0) AS NOT_AVAILABLE,
  COALESCE(BUSY, 0)          AS BUSY
FROM DURATION_IN_STATUS
PIVOT (SUM(DURATION_IN_STATUS)
  FOR THE_STATUS
  IN ('AVAILABLE' AS AVAILABLE, 'NOT AVAILABLE' AS NOT_AVAILABLE, 'BUSY' AS BUSY)
)
  -- You can additionally filter the result
  -- WHERE CHANNEL_ID IN (3,4)
  -- WHERE USER_ID = 12345
  -- WHERE THE_DAY > TO_CHAR(DATE '2017-01-01')
  -- etc.
ORDER BY USER_ID ASC, CHANNEL_ID ASC, THE_DAY ASC, THE_HOUR ASC;

Então teste:
USER_ID  CHANNEL_ID  THE_DAY     THE_HOUR  AVAILABLE  NOT_AVAILABLE  BUSY  
1111     3           01/01/2017  0         15         30             15    
1111     3           01/01/2017  1         30         30             0     
1111     3           01/01/2017  2         60         0              0     
1111     3           01/01/2017  3         10         0              50    
1111     3           01/01/2017  4         0          0              60    
1111     3           01/01/2017  5         0          60             0     
1111     3           01/01/2017  6         0          60             0  
...
1111     3           01/01/2017  23        0          60             0     
1111     4           01/01/2017  0         15         30             15    
1111     4           01/01/2017  1         30         30             0     
1111     4           01/01/2017  2         60         0              0     
1111     4           01/01/2017  3         10         0              50    
1111     4           01/01/2017  4         0          0              60    
1111     4           01/01/2017  5         0          60             0     
1111     4           01/01/2017  6         0          60             0
...
1111     4           01/01/2017  23        0          60             0     
2222     3           01/01/2017  0         15         30             15    
2222     3           01/01/2017  1         30         30             0     
2222     3           01/01/2017  2         60         0              0     
2222     3           01/01/2017  3         10         0              50    
2222     3           01/01/2017  4         0          0              60    
2222     3           01/01/2017  5         0          60             0     
2222     3           01/01/2017  6         0          60             0