Une solution à ce type de requête implique deux éléments :la génération de catégories suivie d'une agrégation dans les catégories générées.
Pour les données que vous avez fournies, la première étape de ce type de solution consiste à classer les données par heure (puisque les données que vous avez fournies n'ont aucun événement à 02h00 ou à 04h00, pour afficher ces heures dans le résultat final, ils peuvent être générés Au lieu).
Le deuxième élément consiste à agréger dans les compartiments par heure via un pivot
, comme mentionné par Jorge Campos dans les commentaires.
Ci-dessous un exemple.
Créez d'abord une table de test :
CREATE TABLE INSERT_TIME_STATUS(
INSERT_TIME TIMESTAMP,
STATUS VARCHAR2(128)
);
Et ajoutez les données de test :
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');
Créez ensuite la requête. Cela utilisera la factorisation des sous-requêtes pour souligner la nature en deux étapes de ce processus.
Le CALENDAR
le sous-facteur ici générera chaque heure de la journée, que des enregistrements aient eu lieu ou non pendant cette heure.
Le HOUR_CALENDAR
le sous-facteur attribuera chaque enregistrement de statut fourni à une heure spécifique et découpera en morceaux les statuts qui traversent une autre heure, de sorte que tous les enregistrements tiennent dans une période d'une heure.
Le DURATION_IN_STATUS
le sous-facteur comptera combien de minutes chaque statut a été actif pendant chaque heure.
La requête finale sera PIVOT
à agréger (SUM
) la durée de chaque STATUS
était actif pendant chaque heure.
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;
Résultat :
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.
Cet exemple de requête génère des enregistrements pour toute la journée. Donc le dernier statut de NOT AVAILABLE
porte à travers. Si vous souhaitez vous arrêter au moment du dernier état attribué, ce comportement peut être ajusté selon les besoins.
EDIT, en réponse à votre mise à jour pour évaluer ces heures par channel_id
et user_id
, voici un autre exemple :
Créez d'abord la table de test :
CREATE TABLE INSERT_TIME_STATUS(
USER_ID NUMBER,
CHANNEL_ID NUMBER,
INSERT_TIME TIMESTAMP,
STATUS VARCHAR2(128)
);
Et chargez-le (ici user_id=1 est sur les canaux 3 et 4 et user_id=2 est sur le canal 3 uniquement) :
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');
Ensuite, mettez à jour la requête pour générer des données par user_id
par channel_id
. Dans cet exemple, les données sont incluses pour tous les temps, pour tous les canaux avec lesquels chaque utilisateur est impliqué. l'utilisateur 1 aura des décomptes pour chaque heure de la journée pour les canaux 3
et 4
tandis que l'utilisateur-2 aura des décomptes pour chaque heure de la journée pour le canal 3 uniquement (s'il avait des enregistrements sur un autre canal, ce canal sera également inclus).
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;
Alors testez-le :
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