Oracle - Convert Hourly Data from Rows to Columns - sql

I have an Oracle table filled with rows of data associated to datetimes.
EX:
Value Datetime
----- ----------------
123 12/08/2018 00:00
456 12/08/2018 01:00
789 12/08/2018 02:00
... ...
312 12/08/2018 23:00
321 12/09/2018 00:00
... ...
I need to take this data and group it by day, but somehow transpose the hourly data to columns with one column for each hour in a 24 hour period.
EX:
Date Value(00:00) Value(01:00) Value(02:00) ... Value(23:00)
---- ------------ ------------ ------------ --- ------------
12/08/2018 123 456 789 ... 312
12/09/2018 321 654 987 ... 423
... ... ... ... ... ...
What is the most effective way of querying to obtain the data from the table in this format? I apologize if this question has already been answered. I've searched a good bit online, but I'm honestly somewhat at odds of even knowing the right keywords to search for this answer. Thank you in advance.

I've tried to simplify the answer the best I could for stack overflow. I was able to get the information formatted using a query similar to the one below.
SELECT date,
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 0 AND EXTRACT(HOUR FROM datetime) < 1 THEN value ELSE 0 END) as "value(00:00)",
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 1 AND EXTRACT(HOUR FROM datetime) < 2 THEN value ELSE 0 END) as "value(01:00)",
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 2 AND EXTRACT(HOUR FROM datetime) < 3 THEN value ELSE 0 END) as "value(02:00)",
...
FROM (
SELECT TO_CHAR(datetime,'YYYY-MM-DD') AS date,
value,
datetime
FROM table
)
GROUP BY date

The process of taking rows and turning them into columns is called pivoting. As of 11g (?), Oracle has the PIVOT keyword to do this natively. Note there is no "time" datatype in Oracle, so I converted them to two-digit characters before pivoting. There must be an aggregate function in the pivot, so make sure you do not have duplicate rows with the same date.
select * from (
select "Value", to_char( "Datetime", 'HH24' ) as dt from table1
)
pivot ( max( "Value" ) for ( dt ) in ( '00' AS "hour0",
'01' as "hour1", '02' as "hour2" ));
Edit:
To aggregate by the day, ignoring the time component, add a TRUNCed column to the inline view.
select * from (
select "Value", trunc("Datetime") as the_day,
to_char( "Datetime", 'HH24' ) as dt from table1
)
pivot ( max( "Value" ) for ( dt ) in ( '00', '01', '02' ));
SQL Fiddle 2

Related

Avg date of birth year from single table (oracle sql)

Average date of birth from a single table where the column's date format is '01-JAN-2001'. Looking to return total count of users, and the average DOB YEAR (oracle sql)
mock table:
|User_ID|birth_date|
|123|01-JAN-2001|
|123|01-JAN-2001|
|123|01-JAN-2001|
Assuming your intention is to just average the year, you can parse it to a date, extract the year and average it as a number:
SELECT COUNT(*), AVG(EXTRACT(YEAR FROM TO_DATE(birth_date, 'dd-mon-yyyy'))
FROM users
(Oracle 12c:) Test table and data:
create table dt (
userid_ number generated always as identity ( start with 123 )
, d_ date
);
begin
for i in 1 .. 15
loop
insert into dt ( d_ ) values (
to_date ( trunc( dbms_random.value( 2446067, 2458100 ) ), 'J' )
) ;
end loop;
end;
/
SQL> select userid_, to_char( d_, 'DD-MON-YYYY' ) dob from dt;
USERID_ DOB
---------- --------------------
123 19-JUN-2000
124 06-OCT-2005
125 27-JAN-2012
126 09-JUL-2003
127 23-JUL-2010
128 07-FEB-1992
129 20-DEC-2002
130 19-MAY-2002
131 23-FEB-1990
132 26-DEC-1990
133 19-JUN-1999
134 16-DEC-1994
135 13-APR-2017
136 31-MAR-2000
137 23-MAY-1987
Query and result:
select
count(*) user_count
, trunc( avg( extract( year from d_ ) ) ) avg_dob_year
from dt ;
USER_COUNT AVG_DOB_YEAR
---------- ------------
15 2000
(Not taking "days" into account, just "years").
When using the method suggested by #mathguy
"... It can be calculated by subtracting a fixed date, like
2000/01/01, from all dates, taking the average of the differences, and
adding it to the fixed date."
... this may be a query we could start with (I'm sure it can be refined):
select
count(*) usercount
, to_date( '2000/01/01', 'YYYY/MM/DD' ) fixeddate
, avg( differences ) difftofixed
, to_date('2000/01/01', 'YYYY/MM/DD' ) + avg( differences ) fixedplusdiff
, extract( year from ( to_date('2000/01/01', 'YYYY/MM/DD' ) + avg( differences ) ) ) dob_year
from (
select
d_ - to_date( '2000/01/01', 'YYYY/MM/DD' ) differences
from dt
);
-- result
USERCOUNT FIXEDDATE DIFFTOFIXED FIXEDPLUSDIFF DOB_YEAR
15 01-JAN-00 250.6 07-SEP-00 2000
I don’t know why you’re trying to get the average date but I think it would go something like this :
“SELECT COUNT(user_id), AVG(EXTRACT(YEAR FROM TO_DATE(birth_date, 'dd-MM-yyyy'))
FROM users”

Checking if a Birthday was between a 6 month span which crosses the year break

I am trying to write SQL code (using SQL Developer) that checks if a person had a birthday within the past 6 month insurance term.
This is what my code currently looks like.
SELECT DRIVER_KEY, CASE WHEN BDAY BETWEEN EFFDAY AND EXPDAY THEN 1 ELSE 0 END AS BDAYIND FROM (
SELECT DISTINCT A.DRIVER_KEY
, TO_CHAR(A.BIRTH_DATE,'mm/dd') AS BDAY
, TO_CHAR(SUBSTR(A.EFFECTIVE_DATE_KEY,5,2)||'/'||SUBSTR(A.EFFECTIVE_DATE_KEY,7,2) ) AS EFFDAY
, TO_CHAR(SUBSTR(A.EXPIRATION_DATE_KEY,5,2)||'/'||SUBSTR(A.EXPIRATION_DATE_KEY,7,2) ) AS EXPDAY
FROM DRIVER_TABLE A
);
It works - so long as the term doesn't cross the break in year. However, my code currently says that 01/25 is NOT between 09/19 and 03/19... How do I fix this?
EDIT: As APC pointed out, my solution does not work for leap years. I would normally delete this post, but it was already selected as the answer to the question. I updated my code below to use the year logic from Brian Leach's solution instead of the to_date strings. Please upvote Brian or APC's answers instead.
Here is my create statement with arbitrary dates:
create table DRIVER_TABLE
(
BIRTH_DATE date,
EFFECTIVE_DATE_KEY date,
EXPIRATION_DATE_KEY date
);
insert into DRIVER_TABLE
values(to_date('05/01/1980','MM/DD/YYYY'),
to_date('11/01/2016','MM/DD/YYYY'),
to_date('04/01/2017','MM/DD/YYYY'));
Here is the query:
select case when BirthdayEFFYear between EFFECTIVE_DATE_KEY and EXPIRATION_DATE_KEY
or BirthdayEXPYear between EFFECTIVE_DATE_KEY and EXPIRATION_DATE_KEY
or to_number(EXPIRATION_DATE_KEY - EFFECTIVE_DATE_KEY) / 365 > 1
then 1 else 0 end BDAYIND
from(
select add_months(BIRTH_DATE,12 * (extract(year from EFFECTIVE_DATE_KEY) - extract(year from BIRTH_DATE))) BirthdayEFFYear,
add_months(BIRTH_DATE,12 * (extract(year from EXPIRATION_DATE_KEY) - extract(year from BIRTH_DATE))) BirthdayEXPYear,
EFFECTIVE_DATE_KEY,EXPIRATION_DATE_KEY
from DRIVER_TABLE A
)
SQLFiddle
Compare dates as dates, not as strings.
Apparently EFFECTIVE_DATE_KEY contains the year in the first four characters, and as such the following should give you what you're looking for:
SELECT DRIVER_KEY,
CASE
WHEN BDAY BETWEEN EFFDAY AND EXPDAY THEN 1
ELSE 0
END AS BDAYIND
FROM (SELECT DISTINCT A.DRIVER_KEY,
A.BIRTH_DATE AS BDAY,
TO_DATE(A.EFFECTIVE_DATE_KEY, 'YYYYMMDD') AS EFFDAY,
TO_DATE(A.EXPIRATION_DATE_KEY, 'YYYYMMDD') AS EXPDAY
FROM DRIVER_TABLE A);
Best of luck.
'01/25' is not between '09/19' and '03/19' because between() is never true when the second argument is smaller than the first argument. You fall ito this trap because you're working with strings. It is always easier to work with dates using the DATE datatype.
It looks like your columns effective_date and expiry_date may not be stored as dates but rather a string; unfortunately this is a common data modelling mistake. If so, you need to cast them to DATE first before applying the following.
This solution has a subquery which selects the pertinent columns from driver_table and also calculates each driver's current age in years. The age is used to derive the last birthday, which is then compared in the main query to the bounds of the insurance term. Because we derive an actual date we can use Oracle's standard date arithmetic so the bdayind is calculated correctly.
SQL> with cte as (
2 select driver_key
3 , date_of_birth
4 , trunc(months_between(sysdate, date_of_birth)/12) as age
5 , add_months(date_of_birth, 12 * (trunc(months_between(sysdate, date_of_birth)/12))) as last_birthday
6 , effective_date
7 , expiry_date
8 from driver_table
9 )
10 select driver_key
11 , date_of_birth as dob
12 , age
13 , effective_date as eff_date
14 , expiry_date as exp_date
15 , last_birthday as last_bday
16 , case
17 when last_birthday between effective_date and expiry_date
18 then 1
19 else 0 end as bdayind
20 from cte
21 /
DRIVER_KEY DOB AGE EFF_DATE EXP_DATE LAST_BDAY BDAYIND
---------- --------- ---- --------- --------- --------- ----------
12 02-APR-98 19 01-DEC-16 31-MAY-17 02-APR-17 1
22 02-APR-98 19 01-JAN-17 30-JUN-17 02-APR-17 1
32 02-SEP-98 18 01-DEC-16 31-MAY-17 02-SEP-16 0
42 02-SEP-98 18 01-JAN-17 30-JUN-17 02-SEP-16 0
SQL>
The subquery produces both age and last_birthday just for demonstration purposes. In real life you only need the last_birthday column.
This solution differs slightly from the others in that:
It works for any birthday between any effective and expiration dates
It accounts for leap years
The raw_data is just setting up the dates for the example:
WITH
raw_data
AS
(SELECT DATE '1963-08-03' AS birthday
, DATE '2017-04-01' AS effectiveday
, DATE '2017-10-31' AS expirationday
, 'Billy' AS name
FROM DUAL
UNION ALL
SELECT DATE '1995-03-20' AS birthday
, DATE '2017-04-01' AS effectiveday
, DATE '2017-10-31' AS expirationday
, 'Sue' AS name
FROM DUAL
UNION ALL
SELECT DATE '1997-01-15' AS birthday
, DATE '2016-12-01' AS effectiveday
, DATE '2017-05-31' AS expirationday
, 'Olga' AS name
FROM DUAL),
mod_data
AS
(SELECT raw_data.*
, ADD_MONTHS (
birthday
, (extract(year from effectiveday) - extract (year from birthday)) * 12
)
effectiveanniversary
, ADD_MONTHS (
birthday
, (extract(year from expirationday) - extract (year from birthday)) * 12
)
expirationanniversary
FROM raw_data)
SELECT name, mod_data.birthday, effectiveday, expirationday
, CASE
WHEN effectiveanniversary BETWEEN effectiveday AND expirationday
OR expirationanniversary BETWEEN effectiveday AND expirationday
THEN
1
ELSE
0
END
found_between
FROM mod_data
NAME BIRTHDAY EFFECTIVEDAY EXPIRATIONDAY FOUND_BETWEEN
Billy 1963/08/03 2017/04/01 2017/10/31 1
Sue 1995/03/20 2017/04/01 2017/10/31 0
Olga 1997/01/15 2016/12/01 2017/05/31 1

SQL Query to combine results and show in a PIVOT

Below mentioned are the two of my queries:
SELECT WINDOWS_NT_LOGIN, COUNT(DPS_NUMBER) as TotalDPS
FROM DispatcherProductivity
WHERE DPS_Processed_Time_Stamp>='12/04/2014 10:30 AM'
AND DPS_Processed_Time_Stamp<='12/05/2014 10:30 AM'
GROUP BY WINDOWS_NT_LOGIN
ORDER BY TotalDPS
SELECT STATUS, COUNT(DPS_NUMBER) AS TotalDPS
FROM DispatcherProductivity
WHERE DPS_Processed_Time_Stamp>='12/04/2014 10:30 AM'
AND DPS_Processed_Time_Stamp<='12/05/2014 10:30 AM'
GROUP BY STATUS
ORDER BY TotalDPS
Their respective Results are:
WINDOWS_NT_LOGIN TotalDPS
A_S 72
T_I_S 133
STATUS TotalDPS
ID 1
Can 2
NHD 3
SED 14
Ord 185
I would like to get the results in this format:
WINDOWS_NT_LOGIN ID Can NHD SED Ord
A_S 2 70
T_I_S 1 2 3 12 115
Thanks
You can use the PIVOT function for this:
SELECT pvt.WINDOWS_NT_LOGIN,
pvt.[ID],
pvt.[Can],
pvt.[NHD],
pvt.[SED],
pvt.[Ord]
FROM ( SELECT WINDOWS_NT_LOGIN, STATUS, DPS_NUMBER
FROM DispatcherProductivity
WHERE DPS_Processed_Time_Stamp>='20141204 10:30:00'
AND DPS_Processed_Time_Stamp<='20141205 10:30:00'
) AS t
PIVOT
( COUNT(DPS_NUMBER)
FOR STATUS IN ([ID], [Can], [NHD], [SED], [Ord])
) AS pvt;
N.B. I changed your dates to the culture invariant format yyyyMMdd hh:mm:ss, however, I was not sure if 12/04/2014 was supposed to tbe 12th April, or 4th December (the exact problem with that format), so it is possible I have put the wrong dates in. I assumed you meant 4th December as that is today.
For further reading read Bad habits to kick : mis-handling date / range queries
I tend to use conditional aggregations for pivots. In this case:
SELECT WINDOWS_NT_LOGIN, COUNT(DPS_NUMBER) as TotalDPS,
SUM(CASE WHEN status = 'ID' THEN DPS_Number END) as ID,
SUM(CASE WHEN status = 'Can' THEN DPS_Number END) as Can,
SUM(CASE WHEN status = 'NHD' THEN DPS_Number END) as NHD,
SUM(CASE WHEN status = 'SED' THEN DPS_Number END) as SED,
SUM(CASE WHEN status = 'Ord' THEN DPS_Number END) as Ord
FROM DispatcherProductivity
WHERE DPS_Processed_Time_Stamp >= '12/04/2014 10:30 AM' AND
DPS_Processed_Time_Stamp <= '12/05/2014 10:30 AM'
GROUP BY WINDOWS_NT_LOGIN;
I would also recommend that you use YYYY-MM-DD format for your dates. I, for one, don't know if your dates are for December or April and May.

SQL Server - Stored Procedure issue to get Matrix type of data

Need help, struggling to do this please
Got the following fields in table
Package_Name
Package_StartTime
Package_Endtime
What I require is:
Based on data range give me how much time each package took time to execute
like
Package Name - 21 Sept 22 Sept 23 Sept
ABC 3 mins 4 mins 2 mins
This way I want to see the execution time pattern of the packages.
For every package, this query will calculate execution time in minutes for every day between StartTime and EndTime.
Solution:
CREATE TABLE dbo.Package
(
Name NVARCHAR(10) PRIMARY KEY
,StartTime DATETIME NOT NULL
,EndTime DATETIME NOT NULL
);
INSERT dbo.Package
SELECT 'A', '2011-01-01T00:02:00', '2011-01-01T00:05:00'
UNION ALL
SELECT 'B', '2011-01-01T23:50:00', '2011-01-02T00:04:00'
UNION ALL
SELECT 'C', '2011-01-01T23:50:00', '2011-01-01T23:59:00'
UNION ALL
SELECT 'D', '2011-01-02T22:10:00', '2011-01-05T01:00:00';
WITH PivotSource
AS
(
SELECT a.Name
,CONVERT(VARCHAR(25),b.DayStartTime,112) [Day]
,DATEDIFF(MINUTE,b.DayStartTime,b.DayEndTime) DayMinutes
FROM dbo.Package a
CROSS APPLY
(
SELECT v.number
,CONVERT(VARCHAR(25),DATEADD(DAY,v.number,a.StartTime),112) IntermediateTime
,CASE
WHEN v.number=0 THEN a.StartTime
ELSE CONVERT(VARCHAR(25),DATEADD(DAY,v.number,a.StartTime),112)
END DayStartTime
,CASE
WHEN CONVERT(VARCHAR(25),a.EndTime,112)=CONVERT(VARCHAR(25),DATEADD(DAY,v.number,a.StartTime),112) THEN a.EndTime
ELSE CONVERT(VARCHAR(25),DATEADD(DAY,v.number,a.StartTime),112)+' 23:59:59.999'
END DayEndTime
FROM master.dbo.spt_values v
WHERE v.type = 'P'
AND v.number <= DATEDIFF(DAY,a.StartTime,a.EndTime)
) b
)
SELECT pvt.*
FROM PivotSource cte
PIVOT( SUM(cte.DayMinutes) FOR cte.[Day] IN ([20110101],[20110102],[20110103],[20110104],[20110105]) ) pvt
DROP TABLE dbo.Package;
Results:
Name 20110101 20110102 20110103 20110104 20110105
---------- ----------- ----------- ----------- ----------- -----------
A 3 NULL NULL NULL NULL
B 10 4 NULL NULL NULL
C 9 NULL NULL NULL NULL
D NULL 110 1440 1440 60
Try this to get the data
select package_Name as Package,Package_StartTime as Start,
DateDiff(s,Package_StartTime,Package_EndTime) as Time
from table
where Package_StartTime >= #StartOfRange and Package_EndTime <= #EndOfRange
You could format the seconds to time inside SQL if you want using
cast((DateDiff(s,Package_StartTime,Package_EndTime) / 60) as varchar(2)) +
':' + cast((DateDiff(s,Package_StartTime,Package_EndTime) % 60) as varchar(2))
This will return a result set looking like this...
Package Start Time
ABC 9/21/2011 360
ABC 9/22/2011 570
etc.
or
Package Start Time
ABC 9/21/2011 6:00
ABC 9/22/2011 9:30
etc.
Depending on how you get the Time column
If you are using SQL 2005 and above, you can use PIVOT to spread this across columns... Here is a good link with an example of how to use PIVOT
Quick PIVOT example, not tested, but hopefully gets you started.
select * from PackageTableAbove
pivot (max (Time) for Start in ([9/21/2011],[9/22/2011],
[9/23/2011],[9/24/20111])) as PackageTimePerDay
If you don't want to hard-code the dates, you can look at using dynamic SQL to build the above statement. I am using Max(Time) under the assumption each package runs once per day.

SQL Group by different time periods with day

I need to query a sales table and have the resulting table show the breakdown for different time periods within the given day.
For example, the table has the following fields:
Id (int), EntryDate (datetime), SaleReference (varchar)
I'd like to produce a table that looks like this:
Date Sales 9am-12pm Sales 12pm-3pm Sales 6pm-9pm
---------- -------------- -------------- -------------
01-01-2010 10 20 6
02-01-2010 12 16 3
03-01-2010 43 11 2
Any help on this would be greatly appreciated.
Thanks
Assuming SQL Server below. If not a similar logic will probably apply with your RDBMS but likely a different function to get the hour part from the datetime and the behaviour of BETWEEN may be different too (In SQL Server it is an inclusive range).
SELECT CAST([Date] AS Date) AS [Date],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 9 AND 11 THEN 1 ELSE NULL END)
AS [Sales 9am-12pm],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 12 AND 14 THEN 1 ELSE NULL END)
AS [Sales 12pm-3pm],
COUNT(CASE WHEN DATEPART(hour, [Date]) BETWEEN 18 AND 20 THEN 1 ELSE NULL END)
AS [Sales 6pm-9pm]
FROM Table
GROUP BY CAST([Date] AS Date) /*For SQL2008*/
NB: Previous versions of SQL Server require a few more hoops to get just the date part out of a datetime. e.g. CAST(FLOOR( CAST( GETDATE() AS FLOAT ) ) AS DATETIME (From here)
Assuming your database supports this kind of date math, you can say:
CASE WHEN EntryDate - date(EntryDate) >= INTERVAL '9 hours'
AND EntryDate - date(EntryDate) < INTERVAL '12 hours'
THEN ...
(That's the PostgreSQL interval syntax, btw... might be nonstandard.) But there are probably more elegant ways of doing it.