Grouping of records hour by hour in oracle - sql

My o/p of the query is like below
Date Hour Orders
2018-02-22 00 22
2018-02-22 03 12
2018-02-22 04 12
2018-02-22 08 12
But I want it like this
Date Hour Orders
2018-02-22 00 22
2018-02-22 01 0
2018-02-22 02 0
2018-02-22 03 12
2018-02-22 04 12
2018-02-22 05 0
2018-02-22 06 0
2018-02-22 07 0
2018-02-22 08 12
Even though there is no order placed during that hour, that hour should be displayed and it should show order placed as 0.

Another way to do that is to use oracle "data-densification" method. The key thing is to add partition by clause after outer join like below.
With Needed_Hours (hr) as (
select lpad(level-1, 2, '0')
from dual
connect by level <= (
select max(to_number(Hour)) - min(to_number(Hour)) + 1
from your_table
)
)
select t."Date", h.hr hour, nvl(t.orders, 0)orders
from Needed_Hours h
left join your_table t partition by (t."Date")
on h.hr = t.hour;

Assuming you have timestamp data(namely dt), split into two columns as date and hour and apply outer join among the data set derived through row generation by difference of extremum hours and the original table(namely t) such as
WITH t1 AS
(
SELECT level-1 AS lvl
FROM dual
CONNECT BY level <= ( SELECT EXTRACT(HOUR FROM MAX(dt) - MIN(dt))+1 FROM t )
), t2 AS
(
SELECT TRUNC(dt) AS "Date", EXTRACT( HOUR FROM dt ) AS hr, orders FROM t
)
SELECT MAX("Date") OVER (ORDER BY lvl ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS "Date",
lvl AS "Hour", NVL(orders,0) AS "Orders"
FROM t1
LEFT JOIN t2
ON t2.hr = t1.lvl
ORDER BY t1.lvl
Demo

Related

Add subquery in query - Oracle SQL

My query return all days in month.
SELECT
EXTRACT( DAY FROM day ) ||' '|| substr(TO_CHAR( day, 'fmDAY' ),0,3) AS day,
EXTRACT( DAY FROM day ) as day_id
FROM (
WITH temp ( col ) AS (
SELECT to_date(2, 'mm') --2 is February
FROM dual
)
SELECT col + level - 1 AS day
FROM temp
CONNECT BY level <= last_day(col) - col + 1
ORDER BY day
)
How get all days from query where DAY_ID not in ==> (Select day_id from table1)
Eg. table1 return 5,10,15
Query resault need to display all days except 5,10,15
You can generate your calendar and then use NOT EXISTS:
WITH month ( col ) AS (
SELECT ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 2 - 1)
FROM DUAL
),
calendar ( day, end_day ) AS (
SELECT col, LAST_DAY(col)
FROM month
UNION ALL
SELECT day + INTERVAL '1' DAY, end_day
FROM calendar
WHERE day < end_day
)
SELECT EXTRACT( DAY FROM day ) ||' '|| TO_CHAR( day, 'fmDY' ) AS day,
EXTRACT( DAY FROM day ) as day_id
FROM calendar c
WHERE NOT EXISTS (
SELECT 1
FROM table1 t
WHERE c.day = t.day_id
)
ORDER BY c.day;
Which, for your sample data:
CREATE TABLE table1 ( day_id ) AS
SELECT DATE '2021-02-05' FROM DUAL UNION ALL
SELECT DATE '2021-02-10' FROM DUAL UNION ALL
SELECT DATE '2021-02-15' FROM DUAL;
Outputs:
DAY
DAY_ID
1 MON
1
2 TUE
2
3 WED
3
4 THU
4
6 SAT
6
7 SUN
7
8 MON
8
9 TUE
9
11 THU
11
12 FRI
12
13 SAT
13
14 SUN
14
16 TUE
16
17 WED
17
18 THU
18
19 FRI
19
20 SAT
20
21 SUN
21
22 MON
22
23 TUE
23
24 WED
24
25 THU
25
26 FRI
26
27 SAT
27
28 SUN
28
db<>fiddle here
One way to generate all rows in month for dates in another table is to use a recursive CTE:
create table table1 as
select date '2021-04-16' as day_in from dual;
with cte (dte) as (
select trunc(day_in, 'MON') as dte
from table1
union all
select dte + interval '1' day
from cte
where dte < last_day(dte)
)
select *
from cte;
Here is a db<>fiddle.
EDIT:
If you want the days not in a table, then use:
with cte (dte) as (
select date '2021-02-01' as dte
from dual
union all
select dte + interval '1' day
from cte
where dte < last_day(dte)
)
select dte
from cte
where not exists (select 1 from table1 t1 where t1.day_id = cte.dte);
You can also use not exists using your query, but I find the recursive CTE easier to follow -- and it is standard SQL.

How can I generate zeros for missing id values?

I'm trying to generate an output that fills in missing counts with 0s.
I'm using Oracle SQL. So far, my solution is based on Grouping records hour by hour or day by day and filling gaps with zero or null with small additions.
WITH TEMP
AS ( SELECT MINDT + ( (LEVEL - 1) / 24) DDD
FROM (SELECT TRUNC (MIN (MY_TIMESTAMP), 'HH24') MINDT,
TRUNC (MAX (MY_TIMESTAMP), 'HH24') MAXDT
FROM MAIN_TABLE.TABLE_VIEW THV
WHERE MY_TIMESTAMP BETWEEN TO_DATE ('08/01/2018:00:00:00',
'MM/DD/YYYY:HH24:MI:SS')
AND TO_DATE (
'08/03/2018:23:59:59',
'MM/DD/YYYY:HH24:MI:SS')) V
CONNECT BY MINDT + ( (LEVEL - 1) / 24) <= MAXDT)
SELECT TO_CHAR (TRUNC (D1, 'HH24'), 'YYYY-MM-DD HH24'), COUNT (D2), ID
FROM (SELECT NVL (MY_TIMESTAMP, DDD) D1,
MY_TIMESTAMP D2,
THV.ID ID
FROM MAIN_TABLE.TABLE_VIEW THV
RIGHT OUTER JOIN
(SELECT DDD FROM TEMP) AD
ON DDD = TRUNC (MY_TIMESTAMP, 'HH24')
WHERE MY_TIMESTAMP BETWEEN TO_DATE ('08/01/2018:00:00:00',
'MM/DD/YYYY:HH24:MI:SS')
AND TO_DATE ('08/03/2018:23:59:59',
'MM/DD/YYYY:HH24:MI:SS'))
GROUP BY ID, TRUNC (D1, 'HH24')
ORDER BY ID, TRUNC (D1, 'HH24')
Right now I'm getting:
CNT ID DT
4 1 2018-08-01 00
1 1 2018-08-01 01
1 1 2018-08-01 04
20 1 2018-08-01 05
76 1 2018-08-01 07
But what I want is:
CNT ID DT
4 1 2018-08-01 00
1 1 2018-08-01 01
0 1 2018-08-01 02
0 1 2018-08-01 03
1 1 2018-08-01 04
20 1 2018-08-01 05
0 1 2018-08-01 06
76 1 2018-08-01 07
Any help would be appreciated.
It works pretty smooth if you have a table to join with that has all the hours you expect to have in the results. For a table to have all the hours, it would just have 24 records.
It can be a temp table, but if it was a real table, it would simplify your report to a standard query. And if this report is used regularly, why not have an extra table? I've seen DBAs have a generic "numbers" table with lots of numbers in it for tricks like this (to get 0-23, query the table where n between 0 and 23). Another example, if you want every individual date for a 90 day period, can use a numbers table for 0-89 and add that value to a start date to be able to join on every possible date in that period.

SQL Query building: howto decompose periods of time in different rows

How can I build a SQL Query to decompose some periods, for example in months.
database table:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.03.2015 10
desired query result:
id fromdate todate value
--------------------------------------------
100 01.01.2015 01.02.2015 5,25
100 01.02.2015 01.03.2015 4,75
where value is based on days between the 2 dates, for example:
value(january) = 31(january nr of days) * 10(original value) / 59(total days) = 5,25
Thank you
For calculations like this you can use date dimension - a table that contains all the dates in your domain as single rows (see this for example).
Once you have date dimension in your database things become simple:
WITH data_by_date AS
( -- Here we join dates to your periods to turn each row in
-- as many rows as there are days in the period.
-- We also turn value field into value_per_day.
SELECT
d.date,
d.month_year,
t.id,
value / (t.todate - t.fromdate) as value_per_day
FROM
dim_date d INNER JOIN
my_table t ON d.date >= t.fromdate AND d.date < t.todate
)
SELECT -- Here we group by results by month.
dd.id,
MIN(dd.date) as fromdate,
MAX(dd.date) as todate,
SUM(dd.value_per_day) as value
FROM data_by_date dd
GROUP BY dd.id, dd.month_year
Use a hierarchical query to generate a list of months for each entry:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST (id, fromdate, todate, value ) AS
SELECT 100, DATE '2015-01-01', DATE '2015-03-01', 10 FROM DUAL
UNION ALL SELECT 200, DATE '2014-12-22', DATE '2015-01-06', 30 FROM DUAL
Query 1:
SELECT ID,
fromdate,
todate,
VALUE * ( todate - fromdate ) / ( maxdate - mindate ) AS value
FROM (
SELECT ID,
GREATEST( t.fromdate, m.COLUMN_VALUE ) AS fromdate,
LEAST( t.todate, ADD_MONTHS( m.COLUMN_VALUE, 1 ) ) AS todate,
t.fromdate AS mindate,
t.todate AS maxdate,
t.value
FROM TEST t,
TABLE(
CAST(
MULTISET(
SELECT ADD_MONTHS( TRUNC( t.fromdate, 'MM' ), LEVEL - 1 )
FROM DUAL
CONNECT BY
ADD_MONTHS( TRUNC( t.fromdate, 'MM' ), LEVEL - 1 ) < t.todate
)
AS SYS.ODCIDATELIST
)
) m
)
Results:
| ID | FROMDATE | TODATE | VALUE |
|-----|----------------------------|----------------------------|-------------------|
| 100 | January, 01 2015 00:00:00 | February, 01 2015 00:00:00 | 5.254237288135593 |
| 100 | February, 01 2015 00:00:00 | March, 01 2015 00:00:00 | 4.745762711864407 |
| 200 | December, 22 2014 00:00:00 | January, 01 2015 00:00:00 | 20 |
| 200 | January, 01 2015 00:00:00 | January, 06 2015 00:00:00 | 10 |
Use function add_months() and hierarchical subquery to generate periods for each id:
select id, d1, d2, round(value*(d2-d1)/nod, 2) value
from (
select id, value, todate-fromdate nod, add_months(fromdate, level-1) d1,
least(add_months(fromdate, level), todate) d2
from data
connect by add_months(fromdate, level) <= trunc(add_months(todate, 1)-1)
and id = prior id and prior dbms_random.value is not null )
SQLFiddle demo

Todays latest and yesterdays latest record

I have a table with code and Date
Code Date
----------------------------
A1 21 May 2015 15:47
A2 21 May 2015 10:30
A3 20 May 2015 10:30
A4 21 May 2015 10:30
A1 19 May 2015 15:20
A2 21 May 2015 12:30
A3 19 May 2015 05:30
A4 18 May 2015 15:38
A1 19 May 2015 05:30
A2 20 May 2015 05:30
A3 21 May 2015 05:30
A4 21 May 2015 05:30
A3 21 May 2015 06:30
A1 21 May 2015 05:30
I need to get the Todays latest record, and yesterdays latest record of A1,A2,A3,A4 showing like below
Flag Code Date
-----------------------------------------
Today A1 21 May 2015 15:47
Today A2 21 May 2015 10:30
Today A3 21 May 2015 06:30
Today A4 21 May 2015 10:30
Yesterday A1 --
Yesterday A2 20 May 2015 05:30
Yesterday A3 20 May 2015 10:30
Yesterday A4 --
Help me how to write query to get data
This seems to give your expected output, including the two "dashed" results for yesterday:
declare #t table (Code char(2),[Date] datetime)
insert into #t(Code,Date) values
('A1','2015-05-21T15:47:00'),
('A2','2015-05-21T10:30:00'),
('A3','2015-05-20T10:30:00'),
('A4','2015-05-21T10:30:00'),
('A1','2015-05-19T15:20:00'),
('A2','2015-05-21T12:30:00'),
('A3','2015-05-19T05:30:00'),
('A4','2015-05-18T15:38:00'),
('A1','2015-05-19T05:30:00'),
('A2','2015-05-20T05:30:00'),
('A3','2015-05-21T05:30:00'),
('A4','2015-05-21T05:30:00'),
('A3','2015-05-21T06:30:00'),
('A1','2015-05-21T05:30:00')
;With Dated as (
select *,DATEADD(day,DATEDIFF(day,0,[Date]),0) as BetterDate
from #t
), Numbered as (
select *,ROW_NUMBER() OVER (
PARTITION BY Code,BetterDate
ORDER BY [Date] desc) as rn
from Dated
), Codes as (
select distinct Code from #t
)
select
'Today' as Occasion,
c.Code,
COALESCE(CONVERT(varchar(20),n1.Date),'-') as Date
from
Codes c
left join
Numbered n1
on
c.Code = n1.Code and
n1.rn = 1 and
n1.BetterDate = DATEADD(day,DATEDIFF(day,0,GETDATE()),0)
union all
select
'Yesterday',
c.Code,
COALESCE(CONVERT(varchar(20),n1.Date),'-') as Date
from
Codes c
left join
Numbered n1
on
c.Code = n1.Code and
n1.rn = 1 and
n1.BetterDate = DATEADD(day,DATEDIFF(day,0,GETDATE()),-1)
order by Occasion,Code
After we set up the sample data, we start constructing the query by way of a few CTEs. The first, Dated, just removes the time portion from the mis-named Date column.
Numbered then assigns row numbers to each result, based on dates and codes.
Codes gets the set of all codes for which we have data, so that we can produce results whether or not a particular code has an entry for today or yesterday.
Finally, we use these CTEs to construct your result set, by way of a UNION ALL
Result:
Occasion Code Date
--------- ---- --------------------
Today A1 May 21 2015 3:47PM
Today A2 May 21 2015 12:30PM
Today A3 May 21 2015 6:30AM
Today A4 May 21 2015 10:30AM
Yesterday A1 -
Yesterday A2 May 20 2015 5:30AM
Yesterday A3 May 20 2015 10:30AM
Yesterday A4 -
select case
when cast([Date] as date) >= cast(getdate() as date) then 'Today'
else 'Yesterday'
end as Flag
, Code
, Date
from (
select row_number() over (
partition by Code, cast([Date] as date)
order by [Date] desc) rn
, *
from YourTable
where cast([Date] as date) > dateadd(day, -1, cast(getdate() as date))
) as SubQueryAlias
where rn = 1
Example at SQL Fiddle.
Try this:
SELECT f.Name, c.code, MAX(y.[Date]) AS [Date]
FROM (SELECT -1 ID, 'yesterday' Name
UNION ALL
SELECT 0, 'today') f
CROSS JOIN
(SELECT code
FROM yourTable
GROUP BY code) c
LEFT OUTER JOIN
yourTable y ON c.code = y.code AND DATEDIFF(DAY, GETDATE(), y.[Date]) = f.ID
WHERE
ISNULL(DATEDIFF(DAY, GETDATE(), y.[Date]), 0) > -2
GROUP BY
f.Name, c.code, ISNULL(DATEDIFF(DAY, GETDATE(), y.[Date]), 0)
Here is some code:
DECLARE #t TABLE(Code CHAR(2), Date DATETIME)
INSERT INTO #t
VALUES ( 'A1', '21 May 2015 15:47' ),
( 'A2', '21 May 2015 10:30' ),
( 'A3', '20 May 2015 10:30' ),
( 'A4', '21 May 2015 10:30' ),
( 'A1', '19 May 2015 15:20' ),
( 'A2', '21 May 2015 12:30' ),
( 'A3', '19 May 2015 05:30' ),
( 'A4', '18 May 2015 15:38' ),
( 'A1', '19 May 2015 05:30' ),
( 'A2', '20 May 2015 05:30' ),
( 'A3', '21 May 2015 05:30' ),
( 'A4', '21 May 2015 05:30' ),
( 'A3', '21 May 2015 06:30' ),
( 'A1', '21 May 2015 05:30' )
;WITH codes AS(SELECT DISTINCT Code, d FROM #t
CROSS JOIN (VALUES(CAST(GETDATE() AS DATE)),
(CAST(DATEADD(dd, -1, GETDATE()) AS DATE)))d(d))
SELECT CASE WHEN DAY(GETDATE()) = DAY(Date) THEN 'Today' ELSE 'Yestarday' END Day ,
c.Code ,
MAX(Date) AS Date
FROM codes c
LEFT JOIN #t t ON t.Code = c.Code AND CAST(t.Date AS DATE) = c.d
WHERE Date IS NULL OR Date > CAST(DATEADD(dd, -1, GETDATE()) AS DATE)
GROUP BY c.Code , DAY(Date)
ORDER BY Day, Code
Output:
Day Code Date
Today A1 2015-05-21 15:47:00.000
Today A2 2015-05-21 12:30:00.000
Today A3 2015-05-21 06:30:00.000
Today A4 2015-05-21 10:30:00.000
Yestarday A1 NULL
Yestarday A2 2015-05-20 05:30:00.000
Yestarday A3 2015-05-20 10:30:00.000
Yestarday A4 NULL

Month grouping - get count for each month

Looking to get record counts for each month. However, several months has no records therefore no row is returned. How can I get a count of 0 for that month?
select months, count(rowid) as counter from (
select to_char(date_entered, 'MM') as months
from mydatatable
where to_char(date_entered, 'yyyy') = '2011'
)
group by months
order by months
Result:
Month Count
01 32
03 12
04 11
06 10
07 222
08 32
Even tried playing with subq select 1,2,3,4,5,6,7,8,9,10,11,12 from dual and could not get it to work. No pivot capability yet... ;(
You're probably better off storing the 01 to 12 in a table, but the general approach is to use a left join:
Select
m.Mo,
Count(t.dateentered)
From (
Select '01' As Mo From Dual Union All
Select '02' From Dual Union All
Select '03' From Dual Union All
Select '04' From Dual Union All
Select '05' From Dual Union All
Select '06' From Dual Union All
Select '07' From Dual Union All
Select '08' From Dual Union All
Select '09' From Dual Union All
Select '10' From Dual Union All
Select '11' From Dual Union All
Select '12' From Dual
) m
Left Outer Join
mydatatable t
On
m.Mo = to_char(t.dateentered, 'MM') And
t.dateentered >= DATE'2011-01-01' And
t.dateentered < DATE'2012-01-01'
Group By
m.Mo
Order By
m.Mo
Update used a more index friendly way of restricting the year.
http://sqlfiddle.com/#!4/68085/10
You'll need to build your own 12-row month "table" and perform a left outer join. Take the query from your question and make it an inline view to supply the data.
SELECT m.month "Month", nvl(md.data, 0) "Count"
FROM
(
select '01' month from dual union all
select '02' month from dual union all
select '03' month from dual union all
select '04' month from dual union all
select '05' month from dual union all
select '06' month from dual union all
select '07' month from dual union all
select '08' month from dual union all
select '09' month from dual union all
select '10' month from dual union all
select '11' month from dual union all
select '12' month from dual
) m LEFT OUTER JOIN (
/* Your Query Here */
) md ON m.month = md.month
ORDER BY m.month;
The results should be something like this:
Month Count
------ ----------
01 32
02 0
03 12
04 11
05 0
06 10
07 222
08 32
09 0
10 0
11 0
12 0
You can build a dummy table containing the month numbers using the connect by syntax of a hierarchical query, and then left-join to your data:
with months as (
select to_char(level, 'FM00') as month
from dual
connect by level <= 12
)
select m.month,
count(mdt.rowid) as counter
from months m
left join mydatatable mdt
on mdt.date_entered >= to_date('01/' || m.month || '/2011', 'DD/MM/YYYY')
and mdt.date_entered <
add_months(to_date('01/' || m.month || '/2011', 'DD/MM/YYYY'), 1)
group by m.month
order by m.month;
With some made up data:
create table mydatatable (date_entered date, dummy number);
insert into mydatatable values (date '2011-06-02', 0);
insert into mydatatable values (date '2011-07-01', 0);
insert into mydatatable values (date '2011-10-01', 0);
insert into mydatatable values (date '2011-10-31', 0);
insert into mydatatable values (date '2011-11-01', 0);
... this gives:
MONTH COUNTER
----- -------
01 0
02 0
03 0
04 0
05 0
06 1
07 1
08 0
09 0
10 2
11 1
12 0
Or SQL Fiddle as that seems to be the thing to do these days...
It's generally better to avoid something like to_char(date_entered, 'yyyy') = '2011' because you're applying the to_char() function to every row in the table, and if there is an index on that column then it won't be used. Instead try to convert your filter to match the column's data type, like date_entered > date '2011-01-01' and date_entered < date '2012-01-01'. In this case it can be taken care of in the join condition anyway - I'm converting each month into a date range in 2011, and only looking for matching records within that month range.
This is very strange... Maybe I misunderstood the question or data...? It is always good idea to add tables and data to your questions. You should get all data for all months with count. I tried this:
SELECT * FROM stack_test
/
CURR_MONTH VAL
---------------
01 10
02 15
03 20
04
05
As you can see months 4 and 5 have no values:
SELECT months, COUNT(rowid) counter
FROM
(
SELECT curr_month months
FROM stack_test
)
GROUP BY months
ORDER BY months
/
MONTHS COUNTER
-------------------
01 1
02 1
03 1
04 1
05 1
And another example: month 2 has no value but I still get count of course. Maybe you need to sum up your values...:
SELECT mth, SUM(val) total_sum, Count(*) total_cnt
FROM
(
SELECT mth, (CASE WHEN Mth = '01' THEN '10' ELSE '0' END) val
FROM
( -- Annual table - replace 2 with 12 in Add_Months for the whole year --
SELECT Trunc(SYSDATE,'Y')+Level-1 Curr_Year_By_Date
, To_char(Trunc(SYSDATE, 'MM') + Rownum-1, 'MM' ) Mth
FROM dual
CONNECT BY Level <= Add_Months(Trunc(SYSDATE,'Y'),2)-Trunc(SYSDATE,'Y')
)
)
GROUP BY mth
ORDER BY 1
/
MTH TOTAL_SUM TOTAL_CNT
-------------------------------------
01 310 31
02 0 28