SQL: How to split data from quaterly to monthly with date - sql

I have the data in the sql table in quarterly format. I need to be able to split it into monthly with value split evenly ([value/3) in to each month. Can you please assist on how to achieve this using SQL? Thank you.
start
end
value
2022-01-01
2022-04-01
25629
2022-04-01
2022-07-01
993621
CREATE TABLE #your_tbl
("start_dt" timestamp, "end_dt" timestamp, "values" int)
;
INSERT INTO #your_tbl
("start_dt", "end_dt", "values")
VALUES
('2020-01-01 00:00:00', '2020-04-01 00:00:00', 114625),
('2020-04-01 00:00:00', '2020-07-01 00:00:00', 45216),
('2020-07-01 00:00:00', '2020-10-01 00:00:00', 513574)
DECLARE #datefrom datetime
DECLARE #dateto datetime
SET #datefrom='2022-04-01'
SET #dateto = '2022-07-01'
;WITH cte AS
(
SELECT #datefrom as MyDate
UNION ALL
SELECT DATEADD(month,1,MyDate)
FROM cte
WHERE DATEADD(month,1,MyDate)<#dateto
),
combined AS (
SELECT *
FROM #your_tbl q
JOIN cte m
ON YEAR(m.MyDate) >= q.start_dt
AND MONTH(m.MyDate) < q.end_dt
)
SELECT *, [values]/COUNT(1) OVER(PARTITION BY [start_dt], [end_dt]) as monthly_values
FROM combined
DROP TABLE #your_tbl

In Oracle can you use this script:
with mytable as (
select to_date('2022-01-01', 'YYYY-MM-DD') as startX, to_date('2022-04-01', 'YYYY-MM-DD') as endX, 25629 as valueX from dual union
select to_date('2022-04-01', 'YYYY-MM-DD') as startX, to_date('2022-07-01', 'YYYY-MM-DD') as endX, 993621 as valueX from dual union
select to_date('2022-07-01', 'YYYY-MM-DD') as startX, to_date('2022-10-01', 'YYYY-MM-DD') as endX, 21 as valueX from dual union
select to_date('2022-10-01', 'YYYY-MM-DD') as startX, to_date('2023-01-01', 'YYYY-MM-DD') as endX, 7777 as valueX from dual
),
mymonths as (
select '01' as month_n from dual union
select '02' as month_n from dual union
select '03' as month_n from dual union
select '04' as month_n from dual union
select '05' as month_n from dual union
select '06' as month_n from dual union
select '07' as month_n from dual union
select '08' as month_n from dual union
select '09' as month_n from dual union
select '10' as month_n from dual union
select '11' as month_n from dual union
select '12' as month_n from dual
)
select month_n, startX, valueX/3
from mytable, mymonths
where month_n between to_char(startX, 'MM') and to_char(endX-1, 'MM');
MONTHS_N STARTX VALUEX/3
-------- ---------- ----------
01 01/01/2022 8543
02 01/01/2022 8543
03 01/01/2022 8543
04 01/04/2022 331207
05 01/04/2022 331207
06 01/04/2022 331207
07 01/07/2022 7
08 01/07/2022 7
09 01/07/2022 7
10 01/10/2022 2592,33333
11 01/10/2022 2592,33333
12 01/10/2022 2592,33333
Thank you.

Assuming you can figure out how to generate monthly dates, which is RDBMS dependent, here's a solution that might work depending on if you can use window functions.
Note this doesn't hard-code divide by 3 in case you're in a partial quarter.
WITH combined AS (
SELECT *,
FROM your_tbl q
JOIN monthly_dates m
ON m.monthly_dt >= q.start_dt
AND m.monthly_dt < q.end_dt
)
SELECT *
, values / COUNT(1) OVER(PARTITION BY start_dt, end_dt) as monthly_values
FROM combined
sqlfiddle

Related

how to use windows function during merge in sql

I am working in oracle sql. I have two table which is linked to each other by one column - company_id (see on the picture); I want to merge table 1 to table 2 and calculate 6 month average (6 month before period from table 2) of income for each company_id and each date of table2. I appreciate any code/idea how to solve this task.
You can use an analytic range window to calculate the averages for table1 and then JOIN the result to table2:
SELECT t2.*,
t1.avg_income_6,
t1.avg_income_12
FROM table2 t2
LEFT OUTER JOIN (
SELECT company_id,
dt,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '5' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_6,
ROUND(AVG(income) OVER (
PARTITION BY company_id
ORDER BY dt
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
), 2) AS avg_income_12
FROM table1
) t1
ON (t2.company_id = t1.company_id AND t2.dt = t1.dt);
Which, for the sample data:
CREATE TABLE table1 (company_id, dt, income) AS
SELECT 1, date '2019-01-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2019-02-01', 58 FROM DUAL UNION ALL
SELECT 1, date '2019-03-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2019-04-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-05-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-06-01', 81 FROM DUAL UNION ALL
SELECT 1, date '2019-07-01', 38 FROM DUAL UNION ALL
SELECT 1, date '2019-08-01', 69 FROM DUAL UNION ALL
SELECT 1, date '2019-09-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2019-10-01', 90 FROM DUAL UNION ALL
SELECT 1, date '2019-11-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2019-12-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-01-01', 11 FROM DUAL UNION ALL
SELECT 1, date '2020-02-01', 83 FROM DUAL UNION ALL
SELECT 1, date '2020-03-01', 18 FROM DUAL UNION ALL
SELECT 1, date '2020-04-01', 28 FROM DUAL UNION ALL
SELECT 1, date '2020-05-01', 52 FROM DUAL UNION ALL
SELECT 1, date '2020-06-01', 21 FROM DUAL UNION ALL
SELECT 1, date '2020-07-01', 54 FROM DUAL UNION ALL
SELECT 1, date '2020-08-01', 30 FROM DUAL UNION ALL
SELECT 1, date '2020-09-01', 12 FROM DUAL UNION ALL
SELECT 1, date '2020-10-01', 25 FROM DUAL UNION ALL
SELECT 1, date '2020-11-01', 86 FROM DUAL UNION ALL
SELECT 1, date '2020-12-01', 4 FROM DUAL UNION ALL
SELECT 1, date '2021-01-01', 10 FROM DUAL UNION ALL
SELECT 1, date '2021-02-01', 72 FROM DUAL UNION ALL
SELECT 1, date '2021-03-01', 65 FROM DUAL UNION ALL
SELECT 1, date '2021-04-01', 25 FROM DUAL;
CREATE TABLE table2 (company_id, dt) AS
SELECT 1, date '2019-06-01' FROM DUAL UNION ALL
SELECT 1, date '2019-09-01' FROM DUAL UNION ALL
SELECT 1, date '2019-12-01' FROM DUAL UNION ALL
SELECT 1, date '2020-01-01' FROM DUAL UNION ALL
SELECT 1, date '2020-07-01' FROM DUAL UNION ALL
SELECT 1, date '2020-08-01' FROM DUAL UNION ALL
SELECT 1, date '2021-03-01' FROM DUAL UNION ALL
SELECT 1, date '2021-04-01' FROM DUAL;
Outputs:
COMPANY_ID
DT
AVG_INCOME_6
AVG_INCOME_12
1
2019-06-01 00:00:00
55.83
55.83
1
2019-09-01 00:00:00
60.17
55.11
1
2019-12-01 00:00:00
45.5
50.67
1
2020-01-01 00:00:00
41
46.17
1
2020-07-01 00:00:00
42.67
41.83
1
2020-08-01 00:00:00
33.83
38.58
1
2021-03-01 00:00:00
43.67
38.25
1
2021-04-01 00:00:00
43.67
38
db<>fiddle here
I don't think you need any window function here (if you were thinking of analytic functions); ordinary avg with appropriate join conditions should do the job.
Sample data:
SQL> with
2 table1 (company_id, datum, income) as
3 (select 1, date '2019-01-01', 65 from dual union all
4 select 1, date '2019-02-01', 58 from dual union all
5 select 1, date '2019-03-01', 12 from dual union all
6 select 1, date '2019-04-01', 81 from dual union all
7 select 1, date '2019-05-01', 38 from dual union all
8 select 1, date '2019-06-01', 81 from dual union all
9 select 1, date '2019-07-01', 38 from dual union all
10 select 1, date '2019-08-01', 69 from dual union all
11 select 1, date '2019-09-01', 54 from dual union all
12 select 1, date '2019-10-01', 90 from dual union all
13 select 1, date '2019-11-01', 10 from dual union all
14 select 1, date '2019-12-01', 12 from dual
15 ),
16 table2 (company_id, datum) as
17 (select 1, date '2019-06-01' from dual union all
18 select 1, date '2019-09-01' from dual union all
19 select 1, date '2019-12-01' from dual union all
20 select 1, date '2020-01-01' from dual union all
21 select 1, date '2020-07-01' from dual
22 )
Query begins here:
23 select b.company_id,
24 b.datum ,
25 round(avg(a.income), 2) result
26 from table1 a join table2 b on a.company_id = b.company_id
27 and a.datum > add_months(b.datum, -6)
28 and a.datum <= b.datum
29 group by b.company_id, b.datum;
COMPANY_ID DATUM RESULT
---------- -------- ----------
1 01.06.19 55,83
1 01.09.19 60,17
1 01.12.19 45,5
1 01.01.20 47
SQL>

Not a GROUP BY expression - OVER(PARTITION BY)

I am creating a query where it shows me the total per function per month but I am getting an error - not a GROUP BY expression.
I have this in the select statement -
sum(a.sched_hours) OVER(PARTITION BY d.function) AS total_hours
Anything I can do to show the correct results?
Do not use an analytic function, use the normal aggregation function instead:
SELECT function_name,
TRUNC(dt, 'MM') AS month,
SUM(sched_hours) AS total_hours
FROM table_name
GROUP BY
function_name,
TRUNC(dt, 'MM')
Which, for the sample data:
CREATE TABLE table_name (function_name, dt, sched_hours) AS
SELECT 'A', DATE '2022-01-01', 10 FROM DUAL UNION ALL
SELECT 'A', DATE '2022-01-02', 10 FROM DUAL UNION ALL
SELECT 'A', DATE '2022-02-01', 20 FROM DUAL UNION ALL
SELECT 'A', DATE '2022-02-02', 20 FROM DUAL UNION ALL
SELECT 'B', DATE '2022-01-01', 20 FROM DUAL UNION ALL
SELECT 'B', DATE '2022-01-02', 20 FROM DUAL UNION ALL
SELECT 'B', DATE '2022-02-01', 30 FROM DUAL UNION ALL
SELECT 'B', DATE '2022-02-02', 30 FROM DUAL;
Outputs:
FUNCTION_NAME
MONTH
TOTAL_HOURS
A
01-JAN-22
20
A
01-FEB-22
40
B
01-JAN-22
40
B
01-FEB-22
60
If you want a grant total for each function across all the months then nest the aggregation function inside the analytic function:
SELECT function_name,
TRUNC(dt, 'MM') AS month,
SUM(sched_hours) AS hours,
SUM(SUM(sched_hours)) OVER (PARTITION BY function_name) AS total_hours
FROM table_name
GROUP BY
function_name,
TRUNC(dt, 'MM')
Which outputs:
FUNCTION_NAME
MONTH
HOURS
TOTAL_HOURS
A
01-JAN-22
20
60
A
01-FEB-22
40
60
B
01-JAN-22
40
100
B
01-FEB-22
60
100
db<>fiddle here

How to select dates from dual, but with joined data?

I got an SQL problem I'm not capable to solve.
First of all, an SQL fiddle with it: http://sqlfiddle.com/#!4/fe7b07/2
As you see, I fill the table with some dates, which are bound to some ID. Those dates are day by day. So for this example, we'd have something like this, if we only look at January:
The timelines spanning from 2020-01-01 to 2020-01-31, the blocks are the dates in the database. So this would be the simple SELECT * FROM days output.
What I now want is to fill in some days to this output. These would span from timeline_begin to MIN(date_from); and from MAX(date_from) to timeline_end.
I'll mark these red in the following picture:
The orange span is not necessary to be added, too, but if your solution would do that too, that would be also ok.
Ok, so far so good.
For this I created the SELECT * FROM minmax, which will select the MIN(date_from) and MAX(date_from) for every id_othertable. Still no magic involved.
What I struggle is now creating those days for every id_othertable, while also joining the data they have on them (in this fiddle, it's just the some_info field).
I tried to write this in the SELECT * FROM days_before query, but I just can't get it to work. I read about the magical function CONNECT BY, which will on its own create dates line by line, but I can't get to join my data from the former table. Every time I join the info, I only get one line per id_othertable, not all those dates I need.
So the ideal solution I'm looking for would be to have three select queries:
SELECT * FROM days which select dates out of the database
SELECT * FROM days_before which will show the dates before MIN(date_from) of query 1
SELECT * FROM days_after for dates after MAX(date_from) of query 1
And in the end I'd UNION those three queries to have them all combined.
I hope I could explain my problem good enough. If you need any information or further explaining, please don't hesitate to ask.
EDIT 1: I created a pastebin with some example data: https://pastebin.com/jskrStpZ
Bear in mind that only the first query has actual information from the database, the other two have created data. Also, this example output only has data for id_othertable = 1, so the actual query should also have the information for id_othertable = 2, 3.
EDIT 2: just for clarification, the field date_to is just a simple date_from + 1 day.
If you have denormalised date it's quite simple:
with bas as (
select 1 id_other_table, to_date('2020-01-05', 'YYYY-MM-DD') date_from, to_date('2020-01-06', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-06', 'YYYY-MM-DD') date_from, to_date('2020-01-07', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-07', 'YYYY-MM-DD') date_from, to_date('2020-01-08', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 1 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'hello' some_info from dual
union all select 2 id_other_table, to_date('2020-01-10', 'YYYY-MM-DD') date_from, to_date('2020-01-11', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 2 id_other_table, to_date('2020-01-11', 'YYYY-MM-DD') date_from, to_date('2020-01-12', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 2 id_other_table, to_date('2020-01-12', 'YYYY-MM-DD') date_from, to_date('2020-01-13', 'YYYY-MM-DD') date_to, 'my' some_info from dual
union all select 3 id_other_table, to_date('2020-01-20', 'YYYY-MM-DD') date_from, to_date('2020-01-21', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
union all select 3 id_other_table, to_date('2020-01-21', 'YYYY-MM-DD') date_from, to_date('2020-01-22', 'YYYY-MM-DD') date_to, 'friend' some_info from dual
union all select 3 id_other_table, to_date('2020-01-22', 'YYYY-MM-DD') date_from, to_date('2020-01-23', 'YYYY-MM-DD') date_to, 'friend' some_info from dual)
, ad as (select trunc(sysdate,'YYYY') -1 + level all_dates from dual connect by level <= 31)
select distinct some_info,all_dates from bas,ad where (some_info,all_dates) not in (select some_info,date_from from bas)
If you have longer date ranges or mind of the time the query needs another solution is helpful. But that is harder to debug. Because it's quite hard to get the orange time slot
If you want the dates per id that are not in the database then you can use the LEAD analytic function:
WITH dates ( id, date_from, date_to ) AS (
SELECT id_othertable,
DATE '2020-01-01',
MIN( date_from )
FROM some_dates
WHERE date_to > DATE '2020-01-01'
AND date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
GROUP BY id_othertable
UNION ALL
SELECT id_othertable,
date_to,
LEAD( date_from, 1, ADD_MONTHS( DATE '2020-01-01', 1 ) )
OVER ( PARTITION BY id_othertable ORDER BY date_from )
FROM some_dates
WHERE date_to > DATE '2020-01-01'
AND date_from < ADD_MONTHS( DATE '2020-01-01', 1 )
)
SELECT id,
date_from,
date_to
FROM dates
WHERE date_from < date_to
ORDER BY id, date_from;
so for the test data:
CREATE TABLE some_dates ( id_othertable, date_from, date_to, some_info ) AS
SELECT 1, DATE '2020-01-05', DATE '2020-01-06', 'hello1' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-06', DATE '2020-01-07', 'hello2' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-07', DATE '2020-01-08', 'hello3' FROM DUAL UNION ALL
SELECT 1, DATE '2020-01-10', DATE '2020-01-13', 'hello4' FROM DUAL UNION ALL
SELECT 2, DATE '2020-01-10', DATE '2020-01-13', 'my' FROM DUAL UNION ALL
SELECT 3, DATE '2020-01-20', DATE '2020-01-23', 'friend' FROM DUAL UNION ALL
SELECT 4, DATE '2019-12-31', DATE '2020-01-05', 'before' FROM DUAL UNION ALL
SELECT 4, DATE '2020-01-30', DATE '2020-02-02', 'after' FROM DUAL UNION ALL
SELECT 5, DATE '2019-12-31', DATE '2020-01-10', 'only_before' FROM DUAL UNION ALL
SELECT 6, DATE '2020-01-15', DATE '2020-02-01', 'only_after' FROM DUAL UNION ALL
SELECT 7, DATE '2019-12-31', DATE '2020-02-01', 'exlude_all' FROM DUAL;
this outputs:
ID | DATE_FROM | DATE_TO
-: | :--------- | :---------
1 | 2020-01-01 | 2020-01-05
1 | 2020-01-08 | 2020-01-10
1 | 2020-01-13 | 2020-02-01
2 | 2020-01-01 | 2020-01-10
2 | 2020-01-13 | 2020-02-01
3 | 2020-01-01 | 2020-01-20
3 | 2020-01-23 | 2020-02-01
4 | 2020-01-05 | 2020-01-30
5 | 2020-01-10 | 2020-02-01
6 | 2020-01-01 | 2020-01-15
db<>fiddle here
If you want the days before then filter on:
WHERE day_from = DATE '2020-01-01'
and, similarly, if you want the days after then filter on:
WHERE day_to = ADD_MONTHS( DATE '2020-01-01', 1 )
If you want to specify the start date and number of months duration then use named bind parameters:
WITH dates ( id, date_from, date_to ) AS (
SELECT id_othertable,
:start_date,
MIN( date_from )
FROM some_dates
WHERE date_to > :start_date
AND date_from < ADD_MONTHS( :start_date, :number_months )
GROUP BY id_othertable
UNION ALL
SELECT id_othertable,
date_to,
LEAD( date_from, 1, ADD_MONTHS( :start_date, :number_months ) )
OVER ( PARTITION BY id_othertable ORDER BY date_from )
FROM some_dates
WHERE date_to > :start_date
AND date_from < ADD_MONTHS( :start_date, :number_months )
)
SELECT id,
date_from,
date_to
FROM dates
WHERE date_from < date_to
ORDER BY id, date_from;
Select whole range using connect by generator. Join your table partitioned by id.
select date_from, nvl(date_to, date_from +1) date_to, id_othertable, some_info
from (
select date '2020-01-01' + level - 1 as date_from
from dual
connect by level <= date '2020-01-31' - date '2020-01-01' ) gen
natural left join some_dates partition by (id_othertable)
sqlfiddle

Transposing rows into columns

I have following data
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
)
select * from sample_data
That gives me simple query results as below
DT PERIOD CODE
2015-01-05 00:00:00 1 A
2015-01-05 00:00:00 2 A
2015-01-05 00:00:00 3 P
2015-01-05 00:00:00 4 A
2015-01-09 00:00:00 5 P
I would like to transpose the results like this -
DATE ATTENDANCE
2015-01-05 12345
AAPAP
How can I do that?
Thanks a bunch!
This is an Oracle SQL question and not PL/SQL!
That could be something like:
select to_char(dt,'YYYY-MM-DD') date,listagg(period) within group (order by period)||chr(13)||chr(10)||listagg(code) within group (order by period) attendance
from (
with sample_data as (select to_date('05/01/2015', 'dd/mm/yyyy') dt, '1' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '2' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '3' Period, 'P' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '4' Period, 'A' code from dual union all
select to_date('05/01/2015', 'dd/mm/yyyy') dt, '5' Period, 'P' code from dual
) select * from sample_data
) group by to_char(dt,'YYYY-MM-DD')
Note that the function listagg only can be used from Oracle 11.2. If you are on an earlier version you can use xmlagg, but thats a bit more clotted.

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