sql >> Databáze >  >> RDS >> Oracle

Normalizujte data transakcí ze sloupců času a stavu na minuty podle hodnoty stavu

Jedno řešení tohoto druhu dotazu zahrnuje dvě části:generování kategorií následované agregací do vygenerovaných kategorií.

Pro data, která jste poskytli, je prvním krokem v tomto druhu řešení rozdělení dat podle hodin (protože data, která jste poskytli, neobsahují žádné události v 02:00 nebo 04:00, aby se zobrazily tyto hodiny v konečném výsledku je lze vygenerovat namísto).

Druhým prvkem je agregace do segmentů za hodinu pomocí pivot , jak zmínil Jorge Campos v komentářích.

Níže je uveden příklad.

Nejprve vytvořte testovací tabulku:

CREATE TABLE INSERT_TIME_STATUS(
  INSERT_TIME TIMESTAMP,
  STATUS VARCHAR2(128)
);

A přidejte testovací data:

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');

Poté vytvořte dotaz. To použije faktoring poddotazů k nastínění dvoufázové povahy tohoto procesu.

CALENDAR subfaktor zde vygeneruje každou hodinu dne bez ohledu na to, zda se během této hodiny vyskytly nějaké záznamy.

HOUR_CALENDAR subfaktor přiřadí každý poskytnutý stavový záznam konkrétní hodině a rozdělí stavy, které přejdou do další hodiny, na kousky, takže všechny záznamy se vejdou do rozsahu jedné hodiny.

DURATION_IN_STATUS subfaktor bude počítat, kolik minut byl každý stav aktivní během každé hodiny.

Poslední dotaz bude PIVOT k agregaci (SUM ) dobu každého STATUS byla aktivní během každé hodiny.

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;

Výsledek:

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. 

Tento příklad dotazu generuje záznamy za celý den. Takže poslední stav NOT AVAILABLE přenáší. Pokud chcete zastavit v době posledního přiřazeného stavu, lze toto chování upravit podle potřeby.

UPRAVTE v reakci na vaši aktualizaci a vyhodnoťte tyto časy podle channel_id a user_id , zde je další příklad:

Nejprve vytvořte testovací tabulku:

CREATE TABLE INSERT_TIME_STATUS(
  USER_ID NUMBER,
  CHANNEL_ID NUMBER,
  INSERT_TIME TIMESTAMP,
  STATUS VARCHAR2(128)
);

A načtěte jej (zde user_id=1 je na kanálech 3 a 4 a user_id=2 je pouze na kanálu 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');

Poté aktualizujte dotaz tak, aby generoval data pro-user_id za-channel_id . V tomto příkladu jsou zahrnuta data pro všechny časy, pro všechny kanály, kterých se každý uživatel účastní. uživatel 1 bude mít počty pro každou hodinu dne pro kanály 3 a 4 zatímco uživatel-2 bude mít počty pro každou hodinu dne pouze pro kanál 3 (pokud měl záznamy na jiném kanálu, bude tento kanál zahrnut také).

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;

Pak to otestujte:

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 



  1. Najděte v SQLite hodnoty, které neobsahují čísla

  2. Date_trunc PostgreSQL v mySQL

  3. Jak přesunout model mezi dvěma aplikacemi Django (Django 1.7)

  4. Jak se vyhnout chybám při mutování tabulky