Related
I have a select which shows me how much lasts a process, it looks like:
SELECT
info1,
info2,
ifno3...
(DATE,'DD.MM.YYYY')DAY,
to_char(DATE,'HH24:MI') hour_from,
to_char(DATE_UNTIL,''HH24:MI'')hour_until,
REGEXP_SUBSTR((date_until-date)DAY TO SECOND,'d{2}:\d{2}')lasts_time
.....
and if a process lasts for example from 09.06.2021 23:00 to 10.06.2021 07:00, then the output of query from above will be:
info1 info2 info3 DAY hour_from hour_until lasts_time
info1| info2 |info3 |09.06.2021 |23:00 | 07:00 | 08:00
and i want it to change in
if this process lasts over night, then the output should be something like this:
info1 info2 info3 DAY hour_from hour_until lasts_time
info1| info2 |info3 |09.06.2021 |23:00 | 00:00 | 01:00
info1| info2 |info3 |10.06.2021 |00:00 | 07:00 | 07:00
so it will count only until the day ends, and if the process is still on going in the new day the output will come in a new row,
I think I have here to deal with union all, but I'm not sure, if anyone has any idea would be great
Here's one option. Read comments within code.
SQL> with
2 test (id, info1, date_from, date_to) as
3 -- sample data
4 (select 1, 'info1', to_date('09.06.2021 23:00', 'dd.mm.yyyy hh24:mi'),
5 to_date('10.06.2021 07:00', 'dd.mm.yyyy hh24:mi')
6 from dual
7 ),
8 temp as
9 -- row generator, to generate all hours between DATE_FROM and DATE_TO
10 (select id, info1,
11 date_from + (level - 1) / 24 datum,
12 lead(date_from + (level - 1) / 24) over (order by date_from) next_datum
13 from test
14 connect by level <= (date_to - date_from) * 24 + 1
15 ),
16 temp2 as
17 -- find date boundaries (MINDAT and MAXDAT), as well as duration in between (LASTS_TIME)
18 (select id, info1, datum, next_datum,
19 min(datum) mindat,
20 max(case when to_char(next_datum, 'mi') = '00' then next_datum - 1/(24*60*60)
21 else datum
22 end
23 ) maxdat,
24 --
25 round((max(case when to_char(next_datum, 'mi') = '00' then next_datum - 1/(24*60*60)
26 else datum
27 end
28 ) -
29 min(datum)
30 ) * 24) lasts_time
31 from temp
32 group by id, info1, datum, next_datum
33 )
34 -- the final result
35 select id,
36 info1,
37 to_char(datum, 'dd.mm.yyyy') day,
38 min(to_char(datum, 'hh24:mi')) hour_from,
39 max(to_char(next_datum, 'hh24:mi')) hour_until,
40 sum(lasts_time) lasts_time
41 from temp2
42 group by id, info1,
43 to_char(datum, 'dd.mm.yyyy')
44 order by day;
The result is
ID INFO1 DAY HOUR_FROM HOUR_UNTIL LASTS_TIME
---------- ----- ---------- ---------- ---------- ----------
1 info1 09.06.2021 23:00 00:00 1
1 info1 10.06.2021 00:00 07:00 7
SQL>
For #astentx's comment: there's no endless loop in my case for values you suggested:
SQL> set timing on
SQL> with
2 test (id, info1, date_from, date_to) as
3 -- sample data
4 (select 1, 'info1', to_date('10.06.2021 07:00', 'dd.mm.yyyy hh24:mi'),
5 to_date('11.06.2021 07:00', 'dd.mm.yyyy hh24:mi')
6 from dual
7 ),
<snip>
ID INFO1 DAY HOUR_ HOUR_ LASTS_TIME
---------- ----- ---------- ----- ----- ----------
1 info1 10.06.2021 07:00 23:00 17
1 info1 11.06.2021 00:00 07:00 7
Elapsed: 00:00:00.04
SQL>
You can use recursive CTE to generate required amount of rows (which are equal to days distance between dates).
with a as (
select
'info1' as id
, to_date('2021-05-01 10:20:30') as dt_from
, to_date('2021-05-03 07:08:09') as dt_to
from dual
union all
select
'info2'
, to_date('2021-05-02 05:06:07')
, to_date('2021-05-02 10:20:30')
from dual
)
, b(id, dt_from, dt_to, dt) as (
select
id
, dt_from
, dt_to
, trunc(dt_from)
from a
union all
select
id
, dt_from
, dt_to
, dt + 1
from b
where dt < trunc(dt_to)
)
select
id
, dt
, numtodsinterval(
case
when trunc(dt_to) = dt
then dt_to
else dt + 1
end
-
greatest(dt_from, dt)
, 'DAY') as dur_interval
/*Or any rounding function you need*/
, trunc((case
when trunc(dt_to) = dt
then dt_to
else dt + 1
end
-
greatest(dt_from, dt))*24) as dur_hours
, case
when trunc(dt_from) = dt
then to_char(dt_from, 'hh24:mi')
else '00:00'
end as hour_from
, case
when trunc(dt_to) = dt
then to_char(dt_to, 'hh24:mi')
else '23:59'
end as hour_to
from b
order by
id
, dt
ID | DT | DUR_INTERVAL | DUR_HOURS | HOUR_FROM | HOUR_TO
:---- | :------------------ | :---------------------------- | --------: | :-------- | :------
info1 | 2021-05-01 00:00:00 | +000000000 13:39:30.000000000 | 13 | 10:20 | 23:59
info1 | 2021-05-02 00:00:00 | +000000001 00:00:00.000000000 | 24 | 00:00 | 23:59
info1 | 2021-05-03 00:00:00 | +000000000 07:08:09.000000000 | 7 | 00:00 | 07:08
info2 | 2021-05-02 00:00:00 | +000000000 05:14:23.000000000 | 5 | 05:06 | 10:20
db<>fiddle here
I am unable to group by on date from a timestamp column in below query:
CHG_TABLE
+----+--------+----------------+-----------------+-------+-----------+
| Key|Seq_Num | Start_Date | End_Date | Value |Record_Type|
+----+--------+----------------+-----------------+-------+-----------+
| 1 | 1 | 5/25/2019 2.05 | 12/31/9999 00.00| 800 | Insert |
| 1 | 1 | 5/25/2019 2.05 | 5/31/2019 11.12 | 800 | Update |
| 1 | 2 | 5/31/2019 11.12| 12/31/9999 00.00| 900 | Insert |
| 1 | 2 | 5/31/2019 11.12| 6/15/2019 12.05 | 900 | Update |
| 1 | 3 | 6/15/2019 12.05| 12/31/9999 00.00| 1000 | Insert |
| 1 | 3 | 6/15/2019 12.05| 6/25/2019 10.20 | 1000 | Update |
+---+---------+----------------+-----------------+-------+-----------+
RESULT:
+-----+------------------+----------------+-----------+----------+
| Key | Month_Start_Date | Month_End_Date |Begin_Value|End_Value |
+---- +------------------+----------------+-----------+----------+
| 1 | 6/1/2019 | 6/30/2019 | 1700 | 1000 |
| 1 | 7/1/2019 | 7/31/2019 | 1000 | 1000 |
+-----+------------------+----------------+-----------+----------+
Begin_Value : Sum(Value) for Max(Start_Date) < Month_Start_Date -> Should pick up latest date from last month
End_Value : Sum(Value) for Max(Start_Date) <= Month_End_Date -> Should pick up the latest date
SELECT k.key,
dd.month_start_date,
dd.month_end_date,
gendata.value first_value,
gendata.next_value last_value
FROM dim_date dd CROSS JOIN dim_person k
JOIN (SELECT ct.key,
dateadd('day',1,last_day(ct.start_date)) start_date ,
SUM(ct.value),
lead(SUM(ct.value)) OVER(ORDER BY ct.start_date) next_value
FROM (SELECT key,to_char(start_Date,'MM-YYYY') MMYYYY, max(start_Date) start_date
FROM CHG_TABLE
GROUP BY to_char(start_Date,'MM-YYYY'), key
) dt JOIN CHG_TABLE ct ON
dt.start_date = ct.start_date AND
dt.key = ct.key
group by ct.key, to_char(start_Date,'MM-YYYY')
) gendata ON
to_char(dd.month_end_date,'MM-YYYY') = to_char(to_char(start_Date,'MM-YYYY')) AND
k.key = gendata.key;
Error:
start_Date is not a valid group by expression
Related post:
Monthly Snapshot using Date Dimension
Hoping, I understood your question correctly.
You can check below query
WITH chg_table ( key, seq_num, start_date, end_date, value, record_type ) AS
(
SELECT 1,1,TO_DATE('5/25/2019 2.05','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 800, 'Insert' FROM DUAL UNION ALL
SELECT 1,1,TO_DATE('5/25/2019 2.05','MM/DD/YYYY HH24.MI'),TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'), 800, 'Update' FROM DUAL UNION ALL
SELECT 1,2,TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 900, 'Insert' FROM DUAL UNION ALL
SELECT 1,2,TO_DATE('5/31/2019 11.12','MM/DD/YYYY HH24.MI'),TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'), 900, 'Update' FROM DUAL UNION ALL
SELECT 1,3,TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'),TO_DATE('12/31/9999 00.00','MM/DD/YYYY HH24.MI'), 1000, 'Insert' FROM DUAL UNION ALL
SELECT 1,3,TO_DATE('6/15/2019 12.05','MM/DD/YYYY HH24.MI'),TO_DATE('6/25/2019 10.20','MM/DD/YYYY HH24.MI'), 1000, 'Update' FROM DUAL
)
select key , new_start_date Month_Start_Date , new_end_date Month_End_Date , begin_value ,
nvl(lead(begin_value) over(order by new_start_date),begin_value) end_value
from
(
select key , new_start_date , new_end_date , sum(value) begin_value
from
(
select key, seq_num, start_date
, value, record_type ,
trunc(add_months(start_date,1),'month') new_start_date ,
trunc(add_months(start_date,2),'month')-1 new_end_date
from chg_table
where record_type = 'Insert'
)
group by key , new_start_date , new_end_date
)
order by new_start_date
;
Db Fiddle link: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=c77a71afa82769b48f424e1c0fa1c0b6
I am assuming that you are getting an "ORA-00979: not a GROUP BY expression" and this is due to your use of the TO_CHAR(timestamp_col,'DD-MM-YYYY') in the GROUP BY clause.
Adding the TO_CHAR(timestamp_col,'DD-MM-YYYY') to the select side of your statement should resolve this and provide the results you are expecting.
a, b, dateadd('day',1,last_day(timestamp_col)) start_date, TO_CHAR(timestamp_col,'DD-MM-YYYY'), ...```
This is my NM_CUST_APPLIANCE_HISTORY table ( for custoner_id=96 ) .
Customer_id | Last_effective_date | Present_quentity
--------------+---------------------+-----------------
96 | 2009-12-20 | 10
96 | 2014-11-18 | 12
96 | 2015-11-26 | 14
I execute my query to get start_date and immediate date of next row as a end_date for a single customer ( customer_id=96 ) .
SELECT NM.CUSTOMER_ID customer_id,
NM.LATEST_EFFECTIVE_DATE start_date,
NVL (
CASE
WHEN nm.LATEST_EFFECTIVE_DATE IS NULL
THEN
TO_DATE ('12/12/9999', 'dd/mm/yyyy')
ELSE
FIRST_VALUE (
nm.LATEST_EFFECTIVE_DATE)
OVER (ORDER BY nm.LATEST_EFFECTIVE_DATE
RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)
END,
TO_DATE ('12/12/9999', 'dd/mm/yyyy'))
end_date,
NM.PRESENT_QUANTITY PRESENT_quantity
FROM nm_cust_appliance_history nm
WHERE NM.APPLIANCE_INFO_ID = 10484
AND NM.CUSTOMER_ID = 96
ORDER BY customer_id, start_date;
And the result comes perfectly AS I WANT. like below :
Customer_id | START_DATE | END_DATE | PRESENT_QUANTITY
------------+------------+------------+-----------------
96 | 2009-12-20 | 2014-11-18 | 10
96 | 2014-11-18 | 2015-11-26 | 12
96 | 2015-11-26 | 9999-12-12 | 14
But when i execute this query for all customer ( removing NM.CUSTOMER_ID = 96 from query ) it gives me same START_DATE and END_DATE and end_date comes added a day AS LIKE below ... I i also give you a snapshot of my output of query and marked out that customer result with red color box...
SELECT NM.CUSTOMER_ID customer_id,
NM.LATEST_EFFECTIVE_DATE start_date,
NVL (
CASE
WHEN nm.LATEST_EFFECTIVE_DATE IS NULL
THEN
TO_DATE ('12/12/9999', 'dd/mm/yyyy')
ELSE
FIRST_VALUE (
nm.LATEST_EFFECTIVE_DATE)
OVER (ORDER BY nm.LATEST_EFFECTIVE_DATE
RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)
END,
TO_DATE ('12/12/9999', 'dd/mm/yyyy'))
end_date,
NM.PRESENT_QUANTITY PRESENT_quantity
FROM nm_cust_appliance_history nm
WHERE NM.APPLIANCE_INFO_ID = 10484
--AND NM.CUSTOMER_ID = 96
ORDER BY customer_id, start_date;
Result is:
Customer_id | START_DATE | END_DATE | Present_quentity
--------------+-------------+------------+-----------------
74 | 2008-10-26 | 2008-10-27 | 5
> 96 | 2009-12-20 | 2009-12-21 | 10
> 96 | 2014-11-18 | 2014-11-19 | 12
> 96 | 2015-11-26 | 2015-11-27 | 14
100 | 2009-01-07 | 2009-01-09 | 7
Image of query Result
I want the result for all customer like the result of single customer.
How can i solve my problem?
Help me any one
Your window clause is looking at last_effective_dates across all your data. You need to add a partition by clause to restrict it to the current customer:
OVER (PARTITION BY nm.CUSTOMER_ID
ORDER BY nm.LATEST_EFFECTIVE_DATE
RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)
So:
SELECT NM.CUSTOMER_ID customer_id,
NM.LATEST_EFFECTIVE_DATE start_date,
NVL (
CASE
WHEN nm.LATEST_EFFECTIVE_DATE IS NULL
THEN
TO_DATE ('12/12/9999', 'dd/mm/yyyy')
ELSE
FIRST_VALUE (
nm.LATEST_EFFECTIVE_DATE)
OVER (PARTITION BY nm.CUSTOMER_ID
ORDER BY nm.LATEST_EFFECTIVE_DATE
RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)
END,
TO_DATE ('12/12/9999', 'dd/mm/yyyy'))
end_date,
NM.PRESENT_QUANTITY PRESENT_quantity
FROM nm_cust_appliance_history nm
WHERE NM.APPLIANCE_INFO_ID = 10484
ORDER BY customer_id, start_date;
If you ever need to run it for more than one appliance_info_id then you'll need to add that to the partition by clause too.
Using a dummy extra record to kind of simulate what you're seeing, supplied via a CTE:
with nm_cust_appliance_history(appliance_info_id, customer_id, latest_effective_date, present_quantity) as (
select 10484, 96, date '2009-12-20', 10 from dual
union all select 10484, 96, date '2014-11-18', 12 from dual
union all select 10484, 96, date '2015-11-26', 14 from dual
union all select 10484, 42, date '2009-12-21', 15 from dual
)
your original query gets:
CUSTOMER_ID START_DATE END_DATE PRESENT_QUANTITY
----------- ---------- ---------- ----------------
42 2009-12-21 2014-11-18 15
96 2009-12-20 2009-12-21 10
96 2014-11-18 2015-11-26 12
96 2015-11-26 9999-12-12 14
and the partition-by query above gets:
CUSTOMER_ID START_DATE END_DATE PRESENT_QUANTITY
----------- ---------- ---------- ----------------
42 2009-12-21 9999-12-12 15
96 2009-12-20 2014-11-18 10
96 2014-11-18 2015-11-26 12
96 2015-11-26 9999-12-12 14
I thought about writing a sql query.
I have a very simple table. There are two fields in this table.
CREATE TABLE [CHECKINOUT](
[USERID] [int] NOT NULL,
[CHECKTIME] [datetime] NOT NULL DEFAULT (getdate())
);GO
USERID CHECKTIME
1 2014-11-04 08:24:49.000
1 2014-11-03 16:57:00.000
1 2014-11-03 08:15:54.000
1 2014-10-28 12:57:58.000
1 2014-10-28 08:22:46.000
1 2014-10-24 16:58:33.000
1 2014-10-24 12:53:06.000
1 2014-10-24 08:21:38.000
1 2014-10-22 16:19:55.000
1 2014-10-21 08:26:21.000
There are sample table above.
I want to write this simple query using the pivot.
I wrote a pivot query but the value returned is null.
I wrote a query like this.
SELECT [USERID],[MORN_IN],[MORN_OUT],[NOON_IN],[NOON_OUT] FROM
(
SELECT [USERID], convert(NVARCHAR, ([CHECKTIME]), 104) as DATE_TIME FROM [CHECKINOUT]
) AS IN_OUT
PIVOT
(
MAX(DATE_TIME) --TO DATE
FOR DATE_TIME -- MY ROW COLUMN
IN
(
[MORN_IN],[MORN_OUT],[NOON_IN],[NOON_OUT] -- MY ROW COLUMN
)
) AS PIVOT_TABLE
incorrect query results--
USERID MORN_IN MORN_OUT NOON_IN NOON_OUT
1 NULL NULL NULL NULL
2 NULL NULL NULL NULL
3 NULL NULL NULL NULL
4 NULL NULL NULL NULL
5 NULL NULL NULL NULL
6 NULL NULL NULL NULL
7 NULL NULL NULL NULL
I want to do what?
the same user on the same day of their movements
I want to break into pieces.
for example:
00:00-11:00 =>MORN_IN
11:00-13:00 =>MORN_OUT(first record ONLY MIN(11:00-13:00))
12:00-15:00 =>NOON_IN (second record max(12:00-13:00) NOON_IN > MORN_OUT)
15:00-00:00 =>NOON_OUT
SELECT TOP 3 [USERID]
,[CHECKTIME]
FROM [CHECKINOUT] ORDER BY [USERID],[CHECKTIME] DESC
USERID my CHECKTIME
1 2014-10-24 16: 58: 33.000
1 2014-10-24 12: 53: 06,000
1 2014-10-24 08: 21: 38.000
now turn to the results of the pivot table (I can not do this part. but should return results like this)
USERID MORN_IN MORN_OUT NOON_IN NOON_OUT
1 2014-10-24 08: 21: 38.000 2014-10-24 12: 53: 06,000 NULL 2014-10-24 16: 58: 33.000
1
If time interval 13:00 - 16:30 is considered to be NOON_IN, then the following query:
SELECT DAY_DIVISION, [MORN_IN], [MORN_OUT], [NOON_IN], [NOON_OUT]
FROM
(SELECT CHECKTIME, CASE
WHEN CAST(CHECKTIME as time) >= '00:00:00' AND CAST(CHECKTIME as time) < '11:00:00' THEN 'MORN_IN'
WHEN CAST(CHECKTIME as time) >= '11:00:00' AND CAST(CHECKTIME as time) < '13:00:00' THEN 'MORN_OUT'
WHEN CAST(CHECKTIME as time) >= '13:00:00' AND CAST(CHECKTIME as time) < '16:30:00' THEN 'NOON_IN'
WHEN CAST(CHECKTIME as time) >= '16:30:00' THEN 'NOON_OUT'
END AS TIME_DIVISION,
RANK() OVER ( ORDER BY CAST(CHECKTIME as date) ASC) AS DAY_DIVISION
FROM CHECKINOUT) AS SourceTable
PIVOT
(
MAX(CHECKTIME)
FOR TIME_DIVISION IN ([MORN_IN], [MORN_OUT], [NOON_IN], [NOON_OUT])
) AS PivotTable;
yields this output:
DAY_DIVISION MORN_IN MORN_OUT NOON_IN NOON_OUT
------------------------------------------------------------------------------------
1 2014-10-21 08:26:21.000 NULL NULL NULL
2 NULL NULL 2014-10-22 16:19:55.000 NULL
3 2014-10-24 08:21:38.000 2014-10-24 12:54:06.000 NULL 2014-10-24 16:58:33.000
7 2014-10-28 08:22:46.000 2014-10-28 12:57:58.000 NULL NULL
9 2014-11-03 08:15:54.000 NULL NULL 2014-11-03 16:57:00.000
11 2014-11-04 08:24:49.000 NULL NULL NULL
Taking care of logins between 12:00 and 13:00 and multiple userIDs. (Tested on Oracle 11.2)
WITH
CheckInOutRaw(userID, checkTime) AS(
SELECT 1, '2014-11-04 08:24:49.000' FROM DUAL UNION ALL
SELECT 1, '2014-11-03 16:57:00.000' FROM DUAL UNION ALL
SELECT 1, '2014-11-03 08:15:54.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-28 12:57:58.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-28 08:22:46.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-24 16:58:33.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-24 12:53:06.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-24 08:21:38.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-22 16:19:55.000' FROM DUAL UNION ALL
SELECT 1, '2014-10-21 08:26:21.000' FROM DUAL UNION ALL
SELECT 2, '2014-11-04 08:24:49.000' FROM DUAL UNION ALL
SELECT 2, '2014-11-03 16:57:00.000' FROM DUAL UNION ALL
SELECT 2, '2014-11-03 08:15:54.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-29 11:07:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-29 12:07:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-29 16:57:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-28 11:07:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-28 12:07:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-28 16:57:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-28 08:22:46.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-27 12:57:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-27 12:07:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-27 16:57:58.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-27 08:22:46.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-24 16:58:33.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-24 12:53:06.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-24 08:21:38.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-22 13:19:55.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-22 16:19:55.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-21 08:26:21.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-20 12:19:55.000' FROM DUAL UNION ALL
SELECT 2, '2014-10-20 16:19:55.000' FROM DUAL
),
CheckInOutTemp AS (
SELECT
userID
, TO_TIMESTAMP(checkTime, 'YYYY-MM-DD HH24:MI:SSXFF3') checkTime
FROM
CheckInOutRaw
),
CheckInOut AS (
SELECT
userID
, checkTime
, TRUNC(checkTime) dt
, EXTRACT(HOUR FROM checkTime) h
, COUNT(CASE EXTRACT(HOUR FROM checkTime) WHEN 11 THEN 1 ELSE NULL END) OVER (PARTITION BY TRUNC(checkTime), userID) h11
, COUNT(CASE EXTRACT(HOUR FROM checkTime) WHEN 12 THEN 1 ELSE NULL END) OVER (PARTITION BY TRUNC(checkTime), userID) h12
FROM
CheckInOutTemp
),
S AS (
SELECT
userID
, checkTime
, dt
, CASE
WHEN h < 11 THEN 'MORN_IN'
WHEN h = 11 THEN 'MORN_OUT'
WHEN h11 = 1 AND h = 12 THEN 'NOON_IN'
WHEN h = 12 THEN 'MORN_OUT'
WHEN h < 16 THEN 'NOON_IN'
ELSE 'NOON_OUT'
END slot
FROM CheckInOut
WHERE NOT ((h = 12) AND (h12 = 2))
UNION ALL
SELECT
userID
, MIN(checkTime) checkTime
, dt
, 'MORN_OUT' slot
FROM CheckInOut
WHERE ((h = 12) AND (h12 = 2))
GROUP BY userID, dt
UNION ALL
SELECT
userID
, MAX(checkTime) checkTime
, dt
, 'NOON_IN' slot
FROM CheckInOut
WHERE ((h = 12) AND (h12 = 2))
GROUP BY userID, dt
)
SELECT
userID, TO_CHAR(dt, 'YYYY-MM-DD') dt, TO_CHAR(MORN_IN, 'HH24:MI:SS') morn_in, TO_CHAR(MORN_OUT, 'HH24:MI:SS') morn_out, TO_CHAR(NOON_IN, 'HH24:MI:SS') noon_in, TO_CHAR(NOON_OUT, 'HH24:MI:SS') noon_out
FROM (SELECT * FROM S PIVOT(MAX(checkTime) FOR slot IN ('MORN_IN' morn_in, 'MORN_OUT' morn_out, 'NOON_IN' noon_in, 'NOON_OUT' noon_out))) ORDER BY userID, DT DESC
;
Returns:
| USERID | DT | MORN_IN | MORN_OUT | NOON_IN | NOON_OUT |
|--------|------------|----------|----------|----------|----------|
| 1 | 2014-11-04 | 08:24:49 | (null) | (null) | (null) |
| 1 | 2014-11-03 | 08:15:54 | (null) | (null) | 16:57:00 |
| 1 | 2014-10-28 | 08:22:46 | 12:57:58 | (null) | (null) |
| 1 | 2014-10-24 | 08:21:38 | 12:53:06 | (null) | 16:58:33 |
| 1 | 2014-10-22 | (null) | (null) | (null) | 16:19:55 |
| 1 | 2014-10-21 | 08:26:21 | (null) | (null) | (null) |
| 2 | 2014-11-04 | 08:24:49 | (null) | (null) | (null) |
| 2 | 2014-11-03 | 08:15:54 | (null) | (null) | 16:57:00 |
| 2 | 2014-10-29 | (null) | 11:07:58 | 12:07:58 | 16:57:58 |
| 2 | 2014-10-28 | 08:22:46 | 11:07:58 | 12:07:58 | 16:57:58 |
| 2 | 2014-10-27 | 08:22:46 | 12:07:58 | 12:57:58 | 16:57:58 |
| 2 | 2014-10-24 | 08:21:38 | 12:53:06 | (null) | 16:58:33 |
| 2 | 2014-10-22 | (null) | (null) | 13:19:55 | 16:19:55 |
| 2 | 2014-10-21 | 08:26:21 | (null) | (null) | (null) |
| 2 | 2014-10-20 | (null) | 12:19:55 | (null) | 16:19:55 |
Looking at
| 2 | 2014-10-20 | (null) | 12:19:55 | (null) | 16:19:55 |
it does probably not make a lot of sense - but seems to be in the line with the specification.
SQL Fiddle
My SAMPLE table has the following five columns:
sample_id (PK) (NUMBER)
sampled_on (DATE)
received_on (DATE)
completed_on (DATE)
authorized_on (DATE)
I would like a query with one row per hour (constrained by a given date range) and five columns:
The hour YYYY-MM-DD HH24
Number of samples sampled during that hour
Number of samples received during that hour
Number of samples completed during that hour
Number of samples authorized during that hour
Please provide a query or at least a point in the right direction.
Reopened with bounty:
+300 reputation for the first person to incorporate Rob van Wijk's answer (single access to sample) into a view where I can efficiently query by date range (start_date/end_date or start_date/num_days).
Try:
CREATE OR REPLACE VIEW my_view AS
WITH date_bookends AS (
SELECT LEAST(MIN(t.sampled_on), MIN(t.received_on), MIN(t.completed_on), MIN(t.authorized_on)) 'min_date'
GREATEST(MAX(t.sampled_on), MAX(t.received_on), MAX(t.completed_on), MAX(t.authorized_on)) 'max_date'
FROM SAMPLE t),
all_hours AS (
SELECT t.min_date + numtodsinterval(LEVEL - 1,'hour') date_by_hour
FROM date_bookends t
CONNECT BY LEVEL <= ( t.max_date - t.min_date + 1) * 24)
SELECT h.date_by_hour,
COUNT(CASE WHEN h.hour = TRUNC(s.sampled_on,'hh24') THEN 1 END) sampled#
COUNT(CASE WHEN h.hour = TRUNC(s.received_on,'hh24') THEN 1 END) received#
COUNT(CASE WHEN h.hour = TRUNC(s.completed_on,'hh24') THEN 1 END) completed#
COUNT(CASE WHEN h.hour = TRUNC(s.authorized_on,'hh24') THEN 1 END) authorized#
FROM all_hours h
CROSS JOIN sample s
GROUP BY h.hour
Without using Subquery Factoring:
CREATE OR REPLACE VIEW my_view AS
SELECT h.date_by_hour,
COUNT(CASE WHEN h.hour = TRUNC(s.sampled_on,'hh24') THEN 1 END) sampled#
COUNT(CASE WHEN h.hour = TRUNC(s.received_on,'hh24') THEN 1 END) received#
COUNT(CASE WHEN h.hour = TRUNC(s.completed_on,'hh24') THEN 1 END) completed#
COUNT(CASE WHEN h.hour = TRUNC(s.authorized_on,'hh24') THEN 1 END) authorized#
FROM (SELECT t.min_date + numtodsinterval(LEVEL - 1,'hour') date_by_hour
FROM (SELECT LEAST(MIN(t.sampled_on), MIN(t.received_on), MIN(t.completed_on), MIN(t.authorized_on)) 'min_date'
GREATEST(MAX(t.sampled_on), MAX(t.received_on), MAX(t.completed_on), MAX(t.authorized_on)) 'max_date'
FROM SAMPLE t) t
CONNECT BY LEVEL <= ( t.max_date - t.min_date + 1) * 24) h
CROSS JOIN sample s
GROUP BY h.hour
The query accesses the SAMPLES table twice - the first time to get the earliest & latest date to frame the construction of the date_by_hour value.
This may not be the prettiest or most optimal solution, but it seems to work. Explanation: first convert all the dates to YYYY-MM-DD HH24 format, next gather number sampled/received/completed/authorized by date+HH24, finally join together.
with sample_hour as
(select sample_id,
to_char(sampled_on, 'YYYY-MM-DD HH24') sampled_on,
to_char(received_on, 'YYYY-MM-DD HH24') received_on,
to_char(completed_on, 'YYYY-MM-DD HH24') completed_on,
to_char(authorized_on, 'YYYY-MM-DD HH24') authorized_on
from sample),
s as
(select sampled_on thedate, count(*) num_sampled
from sample_hour
group by sampled_on),
r as
(select received_on thedate, count(*) num_received
from sample_hour
group by received_on),
c as
(select completed_on thedate, count(*) num_completed
from sample_hour
group by completed_on),
a as
(select authorized_on thedate, count(*) num_authorized
from sample_hour
group by authorized_on)
select s.thedate, num_sampled, num_received, num_completed, num_authorized
from s
left join r on s.thedate = r.thedate
left join c on s.thedate = c.thedate
left join a on s.thedate = a.thedate
;
This assumes a table "sample" created something like this:
create table sample
(sample_id number not null primary key,
sampled_on date,
received_on date,
completed_on date,
authorized_on date);
Here is an example. First create the table and insert some random data.
SQL> create table sample
2 ( sample_id number primary key
3 , sampled_on date
4 , received_on date
5 , completed_on date
6 , authorized_on date
7 )
8 /
Tabel is aangemaakt.
SQL> insert into sample
2 select level
3 , trunc(sysdate) + dbms_random.value(0,2)
4 , trunc(sysdate) + dbms_random.value(0,2)
5 , trunc(sysdate) + dbms_random.value(0,2)
6 , trunc(sysdate) + dbms_random.value(0,2)
7 from dual
8 connect by level <= 1000
9 /
1000 rijen zijn aangemaakt.
Then introduce the variables for your given date range and fill them.
SQL> var DATE_RANGE_START varchar2(10)
SQL> var DATE_RANGE_END varchar2(10)
SQL> exec :DATE_RANGE_START := '2009-10-23'
PL/SQL-procedure is geslaagd.
SQL> exec :DATE_RANGE_END := '2009-10-24'
PL/SQL-procedure is geslaagd.
First you'll have to generate all hours in your given date range. This makes sure that in case you have an hour where no dates are present, you'll still have a record with 4 zeros. The implementation is in the all_hours query. The rest of the query (with only one table access to your sample table!) can then be quite simple like this.
SQL> with all_hours as
2 ( select to_date(:DATE_RANGE_START,'yyyy-mm-dd') + numtodsinterval(level-1,'hour') hour
3 from dual
4 connect by level <=
5 ( to_date(:DATE_RANGE_END,'yyyy-mm-dd')
6 - to_date(:DATE_RANGE_START,'yyyy-mm-dd')
7 + 1
8 ) * 24
9 )
10 select h.hour
11 , count(case when h.hour = trunc(s.sampled_on,'hh24') then 1 end) sampled#
12 , count(case when h.hour = trunc(s.received_on,'hh24') then 1 end) received#
13 , count(case when h.hour = trunc(s.completed_on,'hh24') then 1 end) completed#
14 , count(case when h.hour = trunc(s.authorized_on,'hh24') then 1 end) authorized#
15 from all_hours h
16 cross join sample s
17 group by h.hour
18 /
HOUR SAMPLED# RECEIVED# COMPLETED# AUTHORIZED#
------------------- ---------- ---------- ---------- -----------
23-10-2009 00:00:00 18 25 20 20
23-10-2009 01:00:00 26 24 16 13
23-10-2009 02:00:00 16 26 17 15
23-10-2009 03:00:00 19 18 27 13
23-10-2009 04:00:00 28 20 18 23
23-10-2009 05:00:00 17 13 19 21
23-10-2009 06:00:00 18 23 16 15
23-10-2009 07:00:00 19 24 14 22
23-10-2009 08:00:00 21 19 23 22
23-10-2009 09:00:00 25 20 23 24
23-10-2009 10:00:00 16 21 25 18
23-10-2009 11:00:00 21 29 21 18
23-10-2009 12:00:00 33 28 24 20
23-10-2009 13:00:00 24 19 15 15
23-10-2009 14:00:00 20 27 16 25
23-10-2009 15:00:00 15 25 27 13
23-10-2009 16:00:00 19 14 27 18
23-10-2009 17:00:00 22 22 15 27
23-10-2009 18:00:00 20 19 29 23
23-10-2009 19:00:00 20 18 17 23
23-10-2009 20:00:00 11 18 20 27
23-10-2009 21:00:00 13 25 24 19
23-10-2009 22:00:00 22 13 22 29
23-10-2009 23:00:00 20 20 19 24
24-10-2009 00:00:00 18 17 18 29
24-10-2009 01:00:00 23 30 26 21
24-10-2009 02:00:00 28 19 28 25
24-10-2009 03:00:00 21 21 11 23
24-10-2009 04:00:00 23 20 21 17
24-10-2009 05:00:00 24 16 23 23
24-10-2009 06:00:00 23 26 22 30
24-10-2009 07:00:00 25 26 18 12
24-10-2009 08:00:00 24 20 23 17
24-10-2009 09:00:00 18 26 15 19
24-10-2009 10:00:00 20 19 25 18
24-10-2009 11:00:00 19 27 17 20
24-10-2009 12:00:00 23 16 18 20
24-10-2009 13:00:00 15 15 22 19
24-10-2009 14:00:00 23 23 16 29
24-10-2009 15:00:00 18 31 32 28
24-10-2009 16:00:00 22 15 18 13
24-10-2009 17:00:00 25 17 20 26
24-10-2009 18:00:00 19 20 21 16
24-10-2009 19:00:00 22 13 28 29
24-10-2009 20:00:00 23 17 23 14
24-10-2009 21:00:00 18 18 21 22
24-10-2009 22:00:00 22 20 18 21
24-10-2009 23:00:00 21 18 22 22
48 rijen zijn geselecteerd.
Hope this helps.
Regards,
Rob.
I'd do are 4 queries like this (one for each date):
SELECT <date to hour>, count(*) FROM sample GROUP BY <date to hour>
And then put the data together in the application. If you really want a single query, you can join the individual queries on hour.
Try this...
WITH src_data AS
( SELECT sample_id
, TRUNC( sampled_on, 'HH24' ) sampled_on
, TRUNC( received_on, 'HH24' ) received_on
, TRUNC( completed_on, 'HH24' ) completed_on
, TRUNC( authorized_on, 'HH24' ) authorized_on
FROM sample
)
, src_hours AS
( SELECT sampled_on the_date
FROM src_data
WHERE sampled_on IS NOT NULL
UNION
SELECT received_on the_date
FROM src_data
WHERE received_on IS NOT NULL
UNION
SELECT completed_on the_date
FROM src_data
WHERE completed_on IS NOT NULL
UNION
SELECT authorized_on the_date
FROM src_data
WHERE authorized_on IS NOT NULL
)
SELECT h.the_date
, ( SELECT COUNT(*)
FROM src_data s
WHERE s.sampled_on = h.the_date ) num_sampled_on
, ( SELECT COUNT(*)
FROM src_data r
WHERE r.received_on = h.the_date ) num_received_on
, ( SELECT COUNT(*)
FROM src_data c
WHERE c.completed_on = h.the_date ) num_completed_on
, ( SELECT COUNT(*)
FROM src_data a
WHERE a.authorized_on = h.the_date ) num_authorized_on
FROM src_hours h
Maybe somthing like creating this view:
create view hours as
select hour, max(cnt_sample) cnt_sample, max(cnt_received) cnt_received, max(cnt_completed) cnt_completed, max(cnt_authorized) cnt_authorized
from (
select to_char(sampled_on , 'yyyymmddhh24') hour,
count(sample_id) over (partition by to_char(sampled_on ,'yyyymmddhh24')) cnt_sample,
0 cnt_received,
0 cnt_completed,
0 cnt_authorized from sample union all
select to_char(received_on , 'yyyymmddhh24') hour,
0 cnt_sample,
count(sample_id) over (partition by to_char(received_on ,'yyyymmddhh24')) cnt_received,
0 cnt_completed,
0 cnt_authorized from sample union all
select to_char(completed_on , 'yyyymmddhh24') hour,
0 cnt_sample,
0 cnt_received,
count(sample_id) over (partition by to_char(completed_on ,'yyyymmddhh24')) cnt_completed,
0 cnt_authorized from sample union all
select to_char(authorized_on, 'yyyymmddhh24') hour,
0 cnt_sample,
0 cnt_received,
0 cnt_completed,
count(sample_id) over (partition by to_char(authorized_on ,'yyyymmddhh24')) cnt_authorized from sample
)
group by hour
;
and then selecting from the view:
select * from hours where hour >= '2001010102' and hour <= '2001010105'
order by hour;
I now propose:
create view hours_ as
with four as (
select 1 as n from dual union all
select 2 as n from dual union all
select 3 as n from dual union all
select 4 as n from dual )
select
case when four.n = 1 then trunc(sampled_on , 'hh24')
when four.n = 2 then trunc(received_on , 'hh24')
when four.n = 3 then trunc(completed_on , 'hh24')
when four.n = 4 then trunc(authorized_on, 'hh24')
end hour_,
sum ( case when four.n = 1 then 1
else 0
end ) sample_,
sum ( case when four.n = 2 then 1
else 0
end ) receive_,
sum ( case when four.n = 3 then 1
else 0
end ) complete_,
sum ( case when four.n = 4 then 1
else 0
end ) authorize_
from
four cross join sample
group by
case when four.n = 1 then trunc(sampled_on , 'hh24')
when four.n = 2 then trunc(received_on , 'hh24')
when four.n = 3 then trunc(completed_on , 'hh24')
when four.n = 4 then trunc(authorized_on, 'hh24')
end ;
In order to see if the view is indeed accessed only once:
explain plan for select * from hours_
where hour_ between sysdate -1 and sysdate;
select * from table (dbms_xplan.display);
Which results in:
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 61 | 16 (7)| 00:00:01 |
| 1 | VIEW | HOURS_ | 1 | 61 | 16 (7)| 00:00:01 |
| 2 | HASH GROUP BY | | 1 | 39 | 16 (7)| 00:00:01 |
|* 3 | FILTER | | | | | |
| 4 | NESTED LOOPS | | 1 | 39 | 15 (0)| 00:00:01 |
| 5 | VIEW | | 4 | 12 | 8 (0)| 00:00:01 |
| 6 | UNION-ALL | | | | | |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 9 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 10 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
|* 11 | TABLE ACCESS FULL| SAMPLE | 1 | 36 | 2 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Here's what I'm thinking, but I'm not sure it's optimal enough for a view.
select
the_date,
sum(decode(the_type,'S',the_count,0)) samples,
sum(decode(the_type,'R',the_count,0)) receipts,
sum(decode(the_type,'C',the_count,0)) completions,
sum(decode(the_type,'A',the_count,0)) authorizations
from(
select
trunc(sampled_on,'HH24') the_date,
'S' the_type,
count(1) the_count
FROM sample
group by trunc(sampled_on,'HH24')
union all
select
trunc(received_on,'HH24'),
'R',
count(1)
FROM sample
group by trunc(received_on,'HH24')
union all
select
trunc(completed_on,'HH24'),
'C',
count(1)
FROM sample
group by trunc(completed_on,'HH24')
union all
select
trunc(authorized_on,'HH24'),
'A',
count(1)
FROM sample
group by trunc(authorized_on,'HH24')
)
group by the_date
Then, to query, you could just query with normal date contructs:
select * from magic_view where the_date > sysdate-1;
EDIT
Okay, so I created a sample table and did some metrics:
create table sample (
sample_id number primary key,
sampled_on date,
received_on date,
completed_on date,
authorized_on date
);
insert into sample (
select
level,
trunc(sysdate) + dbms_random.value(0,2),
trunc(sysdate) + dbms_random.value(0,2),
trunc(sysdate) + dbms_random.value(0,2),
trunc(sysdate) + dbms_random.value(0,2),
from dual
connect by level <= 1000
);
The explain plan is:
---------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)|
---------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 4000 | 97K| 25 (20)|
| 1 | HASH GROUP BY | | 4000 | 97K| 25 (20)|
| 2 | VIEW | | 4000 | 97K| 24 (17)|
| 3 | UNION-ALL | | | | |
| 4 | HASH GROUP BY | | 1000 | 9000 | 6 (17)|
| 5 | TABLE ACCESS FULL| SAMPLE | 1000 | 9000 | 5 (0)|
| 6 | HASH GROUP BY | | 1000 | 9000 | 6 (17)|
| 7 | TABLE ACCESS FULL| SAMPLE | 1000 | 9000 | 5 (0)|
| 8 | HASH GROUP BY | | 1000 | 9000 | 6 (17)|
| 9 | TABLE ACCESS FULL| SAMPLE | 1000 | 9000 | 5 (0)|
| 10 | HASH GROUP BY | | 1000 | 9000 | 6 (17)|
| 11 | TABLE ACCESS FULL| SAMPLE | 1000 | 9000 | 5 (0)|
---------------------------------------------------------------------
On my machine, the a query against this view for the past 24 hours completes in 23ms. Not bad, but it's only 1,000 rows. Before you discount the 4 separate queries, you'll need to do performance analysis of the individual solutions.
Similar to René Nyffenegger's idea. Filter by each type of date field, and then amalgamate the counts.
Note, that it's not possible to do this query in one Select, because you need to both Group and Order By each date field, this is impossible without splitting into separate sub-queries.
I have coded a date range of '2009-11-04' to '2009-11-04 23:59:59' for this example:
SELECT
DateHour,
SUM(sampled) total_sampled,
SUM(received) total_received,
SUM(completed) total_completed,
SUM(authorized) total_authorized
FROM
(SELECT
TO_CHAR(CREATED_DATE, 'YYYY-MM-DD HH24') DateHour,
1 sampled,
0 received,
0 completed,
0 authorized
FROM
SAMPLE
WHERE
sampled_on >= TO_DATE('2009-11-04', 'YYYY-MM-DD')
AND sampled_on <= TO_DATE('2009-11-04 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
UNION ALL
SELECT
TO_CHAR(CREATED_DATE, 'YYYY-MM-DD HH24') DateHour,
0 sampled,
1 received,
0 completed,
0 authorized
FROM
SAMPLE
WHERE
received_on >= TO_DATE('2009-11-04', 'YYYY-MM-DD')
AND received_on <= TO_DATE('2009-11-04 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
UNION ALL
SELECT
TO_CHAR(CREATED_DATE, 'YYYY-MM-DD HH24') DateHour,
0 sampled,
0 received,
1 completed,
0 authorized
FROM
SAMPLE
WHERE
completed_on >= TO_DATE('2009-11-04', 'YYYY-MM-DD')
AND completed_on <= TO_DATE('2009-11-04 23:59:59', 'YYYY-MM-DD HH24:MI:SS')
UNION ALL
SELECT
TO_CHAR(CREATED_DATE, 'YYYY-MM-DD HH24') DateHour,
0 sampled,
0 received,
0 completed,
1 authorized
FROM
SAMPLE
WHERE
authorized_on >= TO_DATE('2009-11-04', 'YYYY-MM-DD')
AND authorized_on <= TO_DATE('2009-11-04 23:59:59', 'YYYY-MM-DD HH24:MI:SS'))
GROUP BY
DateHour
ORDER BY
DateHour