SQL Fill empty query with 0 based on date range - sql

I have the following Oracle SQL query results:
TDATE OPEN Closed
19/05/15 1 1
20/05/15 0 1
26/05/15 2 0
27/05/15 1 0
28/05/15 2 0
For example I would like to query from the 19 - 30 May.
And the results i would like to get is:
TDATE OPEN Closed
19/05/15 1 1
20/05/15 0 1
21/05/15 0 0
22/05/15 0 0
23/05/15 0 0
24/05/15 0 0
25/05/15 0 0
26/05/15 2 0
27/05/15 1 0
28/05/15 2 0
29/05/15 0 0
30/05/15 0 0
Where the query is within the date range and records that do not exist will be returned as 0 and 0 for Open and Closed.
Any help would be appreciated.

An empty table with zeroes and all the dates could be made as
INSERT INTO empytable
(SELECT TRUNC(#firstdat + (ROWNUM - 1)) dat, 0, 0
FROM DUAL CONNECT BY ROWNUM <= #days)
Then you can load your results into this table, or combine them otherwise.
The placeholder firstdat should be a date for the addition to work.

In Oracle, using CTE,
WITH table_ (TDATE, OPEN, Closed) AS (
SELECT to_date('19/05/15', 'dd/mm/yy'), 1, 1 from dual UNION ALL
SELECT to_date('20/05/15', 'dd/mm/yy'), 0, 1 from dual UNION ALL
SELECT to_date('26/05/15', 'dd/mm/yy'), 2, 0 from dual UNION ALL
SELECT to_date('27/05/15', 'dd/mm/yy'), 1, 0 from dual UNION ALL
SELECT to_date('28/05/15', 'dd/mm/yy'), 2, 0 from dual),
--------------
-- End of data preparation
--------------
arr_table as (
select to_date('19/05/15', 'dd/mm/yy') + level - 1 dummy_date
from dual
connect by ROWNUM < = to_date('28/05/15', 'dd/mm/yy') - to_date('19/05/15', 'dd/mm/yy') + 1)
SELECT a.dummy_date, COALESCE( b.open, 0) AS OPEN, COALESCE( b.closed, 0) AS closed
FROM arr_table a
LEFT OUTER JOIN table_ b
ON b.tdate = a.dummy_date
ORDER BY a.dummy_date;
Output:
| DUMMY_DATE | OPEN | CLOSED |
|-----------------------|------|--------|
| May, 19 2015 00:00:00 | 1 | 1 |
| May, 20 2015 00:00:00 | 0 | 1 |
| May, 21 2015 00:00:00 | 0 | 0 |
| May, 22 2015 00:00:00 | 0 | 0 |
| May, 23 2015 00:00:00 | 0 | 0 |
| May, 24 2015 00:00:00 | 0 | 0 |
| May, 25 2015 00:00:00 | 0 | 0 |
| May, 26 2015 00:00:00 | 2 | 0 |
| May, 27 2015 00:00:00 | 1 | 0 |
| May, 28 2015 00:00:00 | 2 | 0 |
So basically, the query will be :
with arr_table as (
select to_date(<start_date>, 'dd/mm/yy') + level - 1 dummy_date
from dual
connect by ROWNUM < = to_date(<end_date>, 'dd/mm/yy') - to_date(<start_date>, 'dd/mm/yy') + 1)
SELECT a.dummy_date, COALESCE( b.open, 0) AS OPEN, COALESCE( b.closed, 0) AS closed
FROM arr_table a
LEFT OUTER JOIN <your_table> b
ON b.tdate = a.dummy_date
ORDER BY a.dummy_date;

Related

SQL Count Rows GROUP BY Month Name

I have a table and it has the following structure:
DEVICE_ID | DATE | STATUS
------------------------------------------
1 | 2021/01/05 | accepted
2 | 2021/01/23 | success
3 | 2021/02/07 | success
4 | 2021/03/11 | accepted
5 | 2021/03/20 | unsuccess
6 | 2021/03/26 | success
I want to calculate no of records in 2021 by status and GROUP BY month name like this :
MONTH | ACCEPTED | SUCCESS | UNSUCCESS
------------------------------------------------
January | 1 | 1 | 0
February | 0 | 1 | 0
March | 1 | 1 | 1
April | 0 | 0 | 0
May | 0 | 0 | 0
June | 0 | 0 | 0
July | 0 | 0 | 0
August | 0 | 0 | 0
September | 0 | 0 | 0
October | 0 | 0 | 0
November | 0 | 0 | 0
December | 0 | 0 | 0
Please help me to solve this issue
Explanation - because you want the full month list you need to be able to have all 12 months somewhere in the data. Then you want the custom status columns pivoted to display as you asked.
You should next time at least or tell us what you tried. It helps us figure out how youre thinking about it and how we can help you get past whatever roadblocks youve encountered.
IF OBJECT_ID('TempDb..#tmp') IS NOT NULL DROP TABLE #tmp;
IF OBJECT_ID('TempDb..#tmp2') IS NOT NULL DROP TABLE #tmp2;
CREATE TABLE #tmp
(
Device_ID INT
, Date VARCHAR(12)
, Status VARCHAR(15)
)
;
CREATE TABLE #tmp2
(
MOnthName VARCHAR(25)
)
;
INSERT INTO #tmp2
(MonthName)
VALUES
('January'),
('February'),
('March'),
('April'),
('May'),
('June'),
('July'),
('August'),
('September'),
('October'),
('November'),
('December')
;
INSERT INTO #tmp
(
Device_ID
, Date
, Status
)
VALUES
(1,'2021/01/05','accepted'),
(2,'2021/01/23','success'),
(3,'2021/02/07','success'),
(4,'2021/03/11','accepted'),
(5,'2021/03/20','unsuccess'),
(6,'2021/03/26','success')
;
SELECT
MOnthName
, success
, accepted
, unsuccess
FROM
(
SELECT
tt.MonthName
, Status
FROM
#tmp2 tt
LEFT JOIN #tmp t ON tt.MOnthName = DATENAME(month, CAST(Date AS DATE))
GROUP BY
tt.MonthName
, Status
) AS SourceTable
PIVOT
(
COUNT(Status) FOR Status IN ([accepted], [success], [unsuccess])
) AS PivotTable
ORDER BY
CASE
WHEN MonthName ='January' THEN 1
WHEN MonthName ='February' THEN 2
WHEN MonthName ='March' THEN 3
WHEN MonthName ='April' THEN 4
WHEN MonthName ='May' THEN 5
WHEN MonthName ='June' THEN 6
WHEN MonthName ='July' THEN 7
WHEN MonthName ='August' THEN 8
WHEN MonthName ='September' THEN 9
WHEN MonthName ='October' THEN 10
WHEN MonthName ='November' THEN 11
WHEN MonthName ='December' THEN 12
END
create table yourtable(DEVICE_ID int, DATE date, STATUS varchar(50));
insert into yourtable values(1, '2021/01/05' , 'accepted');
insert into yourtable values(2, '2021/01/23' , 'success');
insert into yourtable values(3, '2021/02/07' , 'success');
insert into yourtable values(4, '2021/03/11' , 'accepted');
insert into yourtable values(5, '2021/03/20' , 'unsuccess');
insert into yourtable values(6, '2021/03/26' , 'success');
Query:
;WITH months(MonthNumber) AS
(
SELECT 0
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 11
)
SELECT DATENAME(MONTH,DATEADD(MONTH,MonthNumber,'01-01-2021')) AS [month],
coalesce(sum(case when status='ACCEPTED' then 1 end),0) ACCEPTED,
coalesce(sum(case when status='SUCCESS' then 1 end),0) SUCCESS,
coalesce(sum(case when status='UNSUCCESS' then 1 end),0) UNSUCCESS
FROM months m left join yourtable y
on m.monthnumber=month(y.[date])-1
group by monthnumber
Output:
month
ACCEPTED
SUCCESS
UNSUCCESS
January
1
1
0
February
0
1
0
March
1
1
1
April
0
0
0
May
0
0
0
June
0
0
0
July
0
0
0
August
0
0
0
September
0
0
0
October
0
0
0
November
0
0
0
December
0
0
0
db<>fiddle here

SQL query to get running total for records matching condition over last 12 months

I am trying to query a table and get a running total for each of the last 12 months. A record could fall in more than one month if the range of two date fields falls on multiple months. The fields are DueDate and DeferralDate.
So for example, lets say I have the following 4 records:
Id | Date1 | Date2
1 01/20/2020 05/29/2020
2 02/01/2020 08/14/2020
3 04/01/2020 04/30/2020
4 07/08/2020 12/31/2020
My result would look like this:
Nov 19 | Dec 19 | Jan 20 | Feb 20 | Mar 20 | Apr 20 | May 20 | Jun 20 | Jul 20 | Aug 20 | Sept 20 | Oct 20
0 0 1 2 2 3 2 1 2 2 1 1
I have no idea how to go about this other than 12 separate queries but there's probably a better way to do it I'm unaware of. Hopefully someone can point me in the right direction.
Thanks in advance.
If you want this in columns, then it is conditional aggregation. Assuming you want any overlap in the month:
select sum(case when date1 < '2019-12-01' and date2 >= '20190-11-01' then 1 else 0 end) as cnt_201911,
sum(case when date1 < '2020-01-01' and date2 >= '20190-12-01' then 1 else 0 end) as cnt_201912,
sum(case when date1 < '2020-02-01' and date2 >= '2020-01-01' then 1 else 0 end) as cnt_202001,
sum(case when date1 < '2020-03-01' and date2 >= '2020-02-01' then 1 else 0 end) as cnt_202002,
. . .
from t
Select sum(count(date1) , count(date2)) ,
Format(date1,'MMMyy')
from tablename
Where month (date1) = month (date2)
Then you have to use Pivot to horizontalize the select result

Split column into multiple columns by criteria

I have a query like the following:
select
table.date,
table.shift,
sum(table.value)
from
db.table
where
table.date >= date '2020-01-01' and
table.filter = 'type'
group by
table.date,
table.shift
order by
table.date,
table.shift;
That returns data this way:
date | shift | sum(value)
-----------|-------|------------
2020-01-06 | 1 | 15
2020-01-06 | 3 | 12
2020-01-07 | 1 | 20
2020-01-07 | 2 | 38
2020-01-09 | 1 | 6
2020-01-09 | 2 | 22
2020-01-09 | 3 | 14
2020-01-10 | 1 | 17
2020-01-10 | 2 | 3
2020-01-10 | 3 | 10
I'm trying to get it like this but I don't know how:
date | 1 | 2 | 3
-----------|----|----|----
2020-01-06 | 15 | | 12
2020-01-07 | 20 | 38 |
2020-01-09 | 6 | 22 | 14
2020-01-10 | 17 | 3 | 10
No need for an addition subquery or CTE. You can pivot your dataset using conditional aggregation with slight modifications of your query: just remove shift from the group by clause, and then implement conditional logic in the sum()s:
select
date,
sum(case when shift = 1 then value end) shift1,
sum(case when shift = 2 then value end) shift2,
sum(case when shift = 3 then value end) shift3
from
db.table
where
date >= date '2020-01-01'
and filter = 'type'
group by date
order by date
Note:
there is no need to prefix the column names since a single table comes into play. I removed those
date is the name of datatype in Oracle, hence not a good choice for a column name
You can do conditional aggregation :
select t.date,
sum(case when t.shift = 1 then t.value else 0 end),
sum(case when t.shift = 2 then t.value else 0 end),
sum(case when t.shift = 3 then t.value else 0 end)
from db.table as t
where t.date >= date '2020-01-01' and
t.filter = 'type'
group by t.date;
You can use conditional aggregation
with cte as
(
select
table.date,
table.shift,
sum(table.value) as val
from
db.table
where
table.date >= date '2020-01-01' and
table.filter = 'type'
group by
table.date,
table.shift
order by
table.date,
table.shift
)
select date, max(case when shift=1 then val end) as 1,
max(case when shift=1 then val end) as 2,
max(case when shift=1 then val end) as 3
from cte
group by date
You can use PIVOT for this as follows:
SELECT
*
FROM ( SELECT
table.date,
table.shift,
table.value
from
db.table
where
table.date >= date '2020-01-01' and
table.FILTER = 'type' )
PIVOT
( SUM ( VALUE ) FOR SHIFT IN ( 1,2,3 ))
ORDER BY date;
Cheers!!

Oracle SQL Developer Subscriber - Creating a Cross Table

I have a table UPCALL_HISTORY that has 3 columns: SUBSCRIBER_ID, START_DATE and END_DATE. Let the number of unique subscribers be N.
I want to create a new table with 3 columns:
SUBSCRIBER_ID: All of the unique subscriber ids repeated 36 times in a row.
MONTHLY_CALENDAR_ID: For each SUBSCRIBER_ID, this column will have dates listed from July 2015 until July 2018 (36 months).
ACTIVE: This column will be used as a flag for each subscriber and whether they have a subscription during that month. This subscription data is in a table called UPCALL_HISTORY.
I am fairly new to SQL, don't have a lot of experience. I am good at Python but it seems that SQL doesn't work like Python.
Any query ideas that could help me build this table?
Let my UPCALL_HISTORY table be:
+---------------+------------+------------+
| SUBSCRIBER_ID | START_DATE | END_DATE |
+---------------+------------+------------+
| 119 | 01/07/2015 | 01/08/2015 |
| 120 | 01/08/2015 | 01/09/2015 |
| 121 | 01/09/2015 | 01/10/2015 |
+---------------+------------+------------+
I want a table that looks like:
+---------------+------------+--------+
| SUBSCRIBER_ID | MON_CA | ACTIVE |
+---------------+------------+--------+
| 119 | 01/07/2015 | 1 |
| * | 01/08/2015 | 0 |
| * | 01/09/2015 | 0 |
| (36 times) | 01/10/2015 | 0 |
| * | * | 0 |
| 119 | 01/07/2018 | 0 |
+---------------+------------+--------+
that continues for 120 and 121
EDIT: Added Example
Here's how I understood the question.
Sample table and several rows:
SQL> create table upcall_history
2 (subscriber_id number,
3 start_date date,
4 end_date date);
Table created.
SQL> insert into upcall_history
2 select 1, date '2015-12-25', date '2016-01-13' from dual union
3 select 1, date '2017-07-10', date '2017-07-11' from dual union
4 select 2, date '2018-01-01', date '2018-04-24' from dual;
3 rows created.
Create a new table. For distinct SUBSCRIBER_ID's, it creates 36 "monthly" rows, fixed (as you stated).
SQL> create table new_table as
2 select
3 x.subscriber_id,
4 add_months(date '2015-07-01', column_value - 1) monthly_calendar_id,
5 0 active
6 from (select distinct subscriber_id from upcall_history) x,
7 table(cast(multiset(select level from dual
8 connect by level <= 36
9 ) as sys.odcinumberlist));
Table created.
Update ACTIVE column value to "1" for rows whose MONTHLY_CALENDAR_ID is contained in START_DATE and END_DATE of the UPCALL_HISTORY table.
SQL> merge into new_table n
2 using (select subscriber_id, start_date, end_date from upcall_history) x
3 on ( n.subscriber_id = x.subscriber_id
4 and n.monthly_calendar_id between trunc(x.start_date, 'mm')
5 and trunc(x.end_date, 'mm')
6 )
7 when matched then
8 update set n.active = 1;
7 rows merged.
SQL>
Result (only ACTIVE = 1):
SQL> select * from new_table
2 where active = 1
3 order by subscriber_id, monthly_calendar_id;
SUBSCRIBER_ID MONTHLY_CA ACTIVE
------------- ---------- ----------
1 2015-12-01 1
1 2016-01-01 1
1 2017-07-01 1
2 2018-01-01 1
2 2018-02-01 1
2 2018-03-01 1
2 2018-04-01 1
7 rows selected.
SQL>
If you're on 12c you can use an inline view of all the months with cross apply to get the combinations of those with all IDs:
select uh.subscriber_id, m.month,
case when trunc(uh.start_date, 'MM') <= m.month
and (uh.end_date is null or uh.end_date >= add_months(m.month, 1))
then 1 else 0 end as active
from upcall_history uh
cross apply (
select add_months(trunc(sysdate, 'MM'), - level) as month
from dual
connect by level <= 36
) m
order by uh.subscriber_id, m.month;
I've made it a rolling 36-months window up to the current month, but you may actually want fixed dates as you had in the question.
With sample data from a CTE:
with upcall_history (subscriber_id, start_date, end_date) as (
select 1, date '2015-09-04', '2015-12-15' from dual
union all select 2, date '2017-12-04', '2018-05-15' from dual
)
that generates 72 rows:
SUBSCRIBER_ID MONTH ACTIVE
------------- ---------- ----------
1 2015-07-01 0
1 2015-08-01 0
1 2015-09-01 1
1 2015-10-01 1
1 2015-11-01 1
1 2015-12-01 0
1 2016-01-01 0
...
2 2017-11-01 0
2 2017-12-01 1
2 2018-01-01 1
2 2018-02-01 1
2 2018-03-01 1
2 2018-04-01 1
2 2018-05-01 0
2 2018-06-01 0
You can use that to create a new table, or populate an existing table; though if you do want a rolling window then a view might be more appropriate.
If you aren't on 12c then cross apply isn't available - you'll get "ORA-00905: missing keyword".
You can get the same result with two CTEs (one to get all the months, the other to get all the IDs) cross-joined, and then outer joined to your actual data:
with m (month) as (
select add_months(trunc(sysdate, 'MM'), - level)
from dual
connect by level <= 36
),
i (subscriber_id) as (
select distinct subscriber_id
from upcall_history
)
select i.subscriber_id, m.month,
case when uh.subscriber_id is null then 0 else 1 end as active
from m
cross join i
left join upcall_history uh
on uh.subscriber_id = i.subscriber_id
and trunc(uh.start_date, 'MM') <= m.month
and (uh.end_date is null or uh.end_date >= add_months(m.month, 1))
order by i.subscriber_id, m.month;
You can do this in 11g using Partitioned Outer Joins, like so:
WITH upcall_history AS (SELECT 119 subscriber_id, to_date('01/07/2015', 'dd/mm/yyyy') start_date, to_date('01/08/2015', 'dd/mm/yyyy') end_date FROM dual UNION ALL
SELECT 120 subscriber_id, to_date('01/08/2015', 'dd/mm/yyyy') start_date, to_date('01/09/2015', 'dd/mm/yyyy') end_date FROM dual UNION ALL
SELECT 121 subscriber_id, to_date('01/09/2015', 'dd/mm/yyyy') start_date, to_date('01/10/2015', 'dd/mm/yyyy') end_date FROM dual),
mnths AS (SELECT add_months(TRUNC(SYSDATE, 'mm'), + 1 - LEVEL) mnth
FROM dual
CONNECT BY LEVEL <= 12 * 3 + 1)
SELECT uh.subscriber_id,
m.mnth,
CASE WHEN mnth BETWEEN start_date AND end_date - 1 THEN 1 ELSE 0 END active
FROM mnths m
LEFT OUTER JOIN upcall_history uh PARTITION BY (uh.subscriber_id) ON (1=1)
ORDER BY uh.subscriber_id,
m.mnth;
SUBSCRIBER_ID MNTH ACTIVE
------------- ----------- ----------
119 01/07/2015 1
119 01/08/2015 0
119 01/09/2015 0
119 01/10/2015 0
<snip>
119 01/06/2018 0
119 01/07/2018 0
--
120 01/07/2015 0
120 01/08/2015 1
120 01/09/2015 0
120 01/10/2015 0
<snip>
120 01/06/2018 0
120 01/07/2018 0
--
121 01/07/2015 0
121 01/08/2015 0
121 01/09/2015 1
121 01/10/2015 0
<snip>
121 01/06/2018 0
121 01/07/2018 0
N.B. I have assumed some things about your start/end dates and what constitutes active; hopefully it should be easy enough for you to tweak the case statement to fit the logic that works best for your situation.
I also believe this is can be an example for CROSS JOIN. All I had to do was create a small table of all of the dates and then CROSS JOIN it with the table of subscribers.
Example: https://www.essentialsql.com/cross-join-introduction/

Get collection of integers from counter

Which would be the best way to get the number of people hired on each day of the week for 7 years from a table People that has their entry_date with a day-month-year as 01-Jun-91.
For example:
2000 2001 2002 etc..
SUN 2 0 1
MON 0 0 2
Do I have to create a counter for each day of each year? Like Sun2000, Sun2001 etc?
You need to join each day of the week with your entry_date and pivot the results.
SQL Fiddle
Query:
with x(days) as (
select 'sunday' from dual union all
select 'monday' from dual union all
select 'tuesday' from dual union all
select 'wednesday' from dual union all
select 'thursday' from dual union all
select 'friday' from dual union all
select 'saturday' from dual
)
select * from (
select x.days,
extract(year from emp.entry_date) entry_year
from x left outer join emp
on x.days = to_char(emp.entry_date,'fmday')
)
pivot(count(entry_year)
for entry_year in (
2007,
2008,
2009,
2010,
2011,
2012
)
)
order by
case days when 'sunday' then 1
when'monday' then 2
when'tuesday' then 3
when'wednesday' then 4
when'thursday' then 5
when'friday' then 6
when'saturday' then 7
end
Results:
| DAYS | 2007 | 2008 | 2009 | 2010 | 2011 | 2012 |
|-----------|------|------|------|------|------|------|
| sunday | 0 | 0 | 0 | 0 | 0 | 0 |
| monday | 0 | 0 | 0 | 2 | 0 | 0 |
| tuesday | 0 | 0 | 0 | 0 | 1 | 0 |
| wednesday | 0 | 0 | 0 | 1 | 2 | 1 |
| thursday | 0 | 0 | 0 | 0 | 0 | 3 |
| friday | 0 | 0 | 0 | 0 | 0 | 0 |
| saturday | 0 | 0 | 0 | 0 | 0 | 0 |
You need to use group by on the year and entry_date to get the count of employees joined for each date.
For example:
Rem -- Assuming following table structure
create table People(id number, name varchar2(20), entry_date date);
Rem -- Following groups the results
select extract(year from entry_date) "Year", entry_date, count(id)
from People
where extract(year from entry_date) between 2008 and 2015
group by extract(year from entry_date), entry_date
order by extract(year from entry_date), entry_date;
Check this sqlfiddle to explore more.
Depending on the version of Oracle you're using (10g doesn't have the PIVOT function, for example), you might try something like the following conditional aggregation:
SELECT day_abbrev
, SUM(CASE WHEN year_num = 2000 THEN person_cnt ELSE 0 END) AS "2000"
, SUM(CASE WHEN year_num = 2001 THEN person_cnt ELSE 0 END) AS "2001"
, SUM(CASE WHEN year_num = 2002 THEN person_cnt ELSE 0 END) AS "2002"
, SUM(CASE WHEN year_num = 2003 THEN person_cnt ELSE 0 END) AS "2003"
, SUM(CASE WHEN year_num = 2004 THEN person_cnt ELSE 0 END) AS "2004"
, SUM(CASE WHEN year_num = 2005 THEN person_cnt ELSE 0 END) AS "2005"
, SUM(CASE WHEN year_num = 2006 THEN person_cnt ELSE 0 END) AS "2006"
FROM (
SELECT TO_CHAR(entry_date, 'DY') AS day_abbrev
, EXTRACT(YEAR FROM entry_date) AS year_num
, COUNT(*) AS person_cnt
FROM people
GROUP BY TO_CHAR(entry_date, 'DY'), EXTRACT(YEAR FROM entry_date)
) GROUP BY day_abbrev
ORDER BY TO_CHAR(NEXT_DAY(SYSDATE, day_abbrev), 'D');