Oracle
 sql >> Teknologi Basis Data >  >> RDS >> Oracle

Normalisasikan data transaksi dari kolom waktu dan status menjadi menit per nilai status

Salah satu solusi untuk jenis kueri ini melibatkan dua bagian:pembuatan kategori diikuti dengan agregasi ke dalam kategori yang dihasilkan.

Untuk data yang Anda berikan, langkah pertama dalam solusi semacam ini adalah mengelompokkan data berdasarkan jam (karena data yang Anda berikan tidak memiliki peristiwa apa pun pada jam 02:00 atau jam 04:00, untuk menampilkan jam tersebut di hasil akhir, mereka dapat dihasilkan sebagai gantinya).

Bagian kedua adalah untuk menggabungkan ke dalam ember per jam melalui pivot , seperti yang disebutkan oleh Jorge Campos di komentar.

Di bawah ini adalah contohnya.

Pertama buat tabel pengujian:

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

Dan tambahkan data pengujian:

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

Kemudian buat kueri. Ini akan menggunakan pemfaktoran subquery untuk menguraikan sifat dua langkah dari proses ini.

CALENDAR subfaktor di sini akan menghasilkan setiap jam dalam sehari, terlepas dari apakah ada catatan yang terjadi selama jam itu.

HOUR_CALENDAR subfaktor akan menetapkan setiap catatan status yang diberikan ke jam tertentu, dan akan memotong status yang melintasi jam lain menjadi beberapa bagian, sehingga semua catatan muat dalam rentang satu jam.

DURATION_IN_STATUS subfaktor akan menghitung berapa menit setiap status aktif selama setiap jam.

Kueri terakhir akan PIVOT untuk menggabungkan (SUM ) jumlah waktu setiap STATUS aktif selama setiap jam.

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;

Hasil:

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. 

Contoh kueri ini menghasilkan rekaman sepanjang hari. Jadi status terakhir NOT AVAILABLE membawa melalui. Jika Anda ingin berhenti pada saat status terakhir ditetapkan, perilaku ini dapat disesuaikan sesuai kebutuhan.

EDIT, sebagai tanggapan atas pembaruan Anda untuk mengevaluasi waktu ini per channel_id dan user_id , ini contoh lainnya:

Pertama buat tabel pengujian:

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

Dan muat (di sini user_id=1 ada di saluran 3 dan 4 dan user_id=2 hanya di saluran 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');

Kemudian perbarui kueri untuk menghasilkan data per-user_id per-channel_id . Dalam contoh ini, data disertakan sepanjang waktu, untuk semua saluran yang melibatkan setiap pengguna. pengguna 1 akan memiliki hitungan untuk setiap jam dalam sehari untuk saluran 3 dan 4 sementara pengguna-2 akan memiliki hitungan untuk setiap jam dalam sehari hanya untuk saluran 3 (jika memiliki catatan di saluran lain, saluran itu juga disertakan).

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;

Kemudian uji:

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. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Batas waktu koneksi untuk DriverManager getConnection

  2. Menghapus baris duplikat dari tabel di Oracle

  3. Urutan Oracle tetapi kemudian di MS SQL Server

  4. Jumlah baris yang terpengaruh oleh UPDATE di PL/SQL

  5. Terhubung dengan di Oracle SQL