Month grouping - get count for each month - sql

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

Related

How to separate range of year on oracle

I am working on a db oracle and I need to create a query where it return a range of date. For example:
Supose that I had a field of like this:
I need to get this dates and apply a range of years to return someting like:
|'0-5'|'6-10'|'11-15'|...
| 10 | 35 | 20 |...
where each range contains a number of people in this range of years old.
I tried to use SELECT CASE...
SELECT CASE
WHEN DATE_BORN <= DATE_BORN + 5 THEN '0 - 5
WHEN DATE_BORN >= DATE_BORN + 6 AND DATE_BORN <= 10 THEN '6 - 10'
END AS AGE_RANGE,
COUNT(*)
FROM MY_TABLE
GROUP BY 1
So I saw that this way change only days not year.
How can I write this query?
That's conditional aggregation:
SQL> with test (date_born) as
2 (select date '2000-05-12' from dual union all
3 select date '2001-05-12' from dual union all
4 select date '2012-05-12' from dual union all
5 select date '2013-05-12' from dual union all
6 select date '2004-05-12' from dual union all
7 select date '2008-05-12' from dual union all
8 select date '2009-05-12' from dual union all
9 select date '2001-05-12' from dual union all
10 select date '2012-05-12' from dual union all
11 select date '2001-05-12' from dual union all
12 select date '2004-05-12' from dual union all
13 select date '2005-05-12' from dual
14 )
15 select
16 sum(case when extract (year from date_born) between 2000 and 2005 then 1 else 0 end) as "2000 - 2005",
17 sum(case when extract (year from date_born) between 2006 and 2010 then 1 else 0 end) as "2006 - 2010",
18 sum(case when extract (year from date_born) between 2011 and 2015 then 1 else 0 end) as "2011 - 2015"
19 from test;
2000 - 2005 2006 - 2010 2011 - 2015
----------- ----------- -----------
7 2 3
SQL>
Here is a dynamic way to do this (using the sample table above)
First I think it's easier to have your ranges in rows rather than columns, easier for having a variety of dates that may change.
Second your first grouping is 6 years, so I changed it to just be series of 5 years:
with test (date_born) as
(select date '2000-05-12' from dual union all
select date '2001-05-12' from dual union all
select date '2012-05-12' from dual union all
select date '2013-05-12' from dual union all
select date '2004-05-12' from dual union all
select date '2008-05-12' from dual union all
select date '2009-05-12' from dual union all
select date '2001-05-12' from dual union all
select date '2012-05-12' from dual union all
select date '2001-05-12' from dual union all
select date '2004-05-12' from dual union all
select date '2005-05-12' from dual
)
,mydata AS (
SELECT
(SELECT min(extract(YEAR FROM date_born)) FROM test)+((LEVEL-1)*5)dt1
,(SELECT min(extract(YEAR FROM date_born)) FROM test)+((LEVEL-1)*5)+4 dt2
FROM dual CONNECT BY LEVEL*5 <=
(SELECT max(extract(YEAR FROM date_born))-min(extract(YEAR FROM date_born)) FROM test)+5)
SELECT d.*, count(t.date_born) cnt FROM mydata d
LEFT JOIN test t ON extract(YEAR FROM date_born) BETWEEN d.dt1 AND d.dt2
GROUP BY dt1, dt2
ORDER BY dt1;
You get this for your solution
DT1 DT2 CNT
2000 2004 6
2005 2009 3
2010 2014 3
Solution is basically extracting years from dates, finding min/max of this data set, using connect to get all years in between, and then joining to count your matching records

Count daily fidelity

I have the below table and I would like to count, day by day, the number of distinct people who logged in everyday. For example, for day 1, everyone logged in, so it's 4. For day 4, there's just one person ID who logged in everyday since day 1, so the count would be 1.
DAY
PERSON_ID
1
01
1
02
1
03
1
04
2
01
2
02
2
03
3
01
4
02
4
01
Expected output.
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
1
4
01, 02, 03, 04
2
3
01, 02, 03
3
1
01
4
1
01
EDIT: the query should also work on the below data.
with t ( DAY, PERSON_ID ) AS(
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL)
Expected output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
EXPLANATION
10
3
01, 02, 04
Three unique people in day 10
12
2
01, 02
Day 11 does not have values, so it's not included. From those in day 10, only 2 appear in day 12
13
1
01
From those in day 10 and 12, only 01 appears in day 13
14
1
01
From those in day 10, 12 and 13, only 01 appears in day 14
You can use listagg() with group by clause. If day is always start from the 1 and increases by 1 then you can use below query. He with the help of exits I have selected only those person_id which are available in all the previous days.
create table yourtable(DAY int, PERSON_ID varchar(10));
insert into yourtable values(1, '01');
insert into yourtable values(1, '02');
insert into yourtable values(1, '03');
insert into yourtable values(1, '04');
insert into yourtable values(2, '01');
insert into yourtable values(2, '02');
insert into yourtable values(2, '03');
insert into yourtable values(3, '01');
insert into yourtable values(4, '02');
insert into yourtable values(4, '01');
Query:
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(day)=a.day)
group by day;
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
1
4
01,02,03,04
2
3
01,02,03
3
1
01
4
1
01
db<fiddle here
Instead of day sequence if you had increasing dates in day column:
create table yourtable(DAY date, PERSON_ID varchar(10));
insert into yourtable values(date '2021-01-01', '01');
insert into yourtable values(date '2021-01-01', '02');
insert into yourtable values(date '2021-01-01', '03');
insert into yourtable values(date '2021-01-01', '04');
insert into yourtable values(date '2021-01-02', '01');
insert into yourtable values(date '2021-01-02', '02');
insert into yourtable values(date '2021-01-02', '03');
insert into yourtable values(date '2021-01-03', '01');
insert into yourtable values(date '2021-01-04', '02');
insert into yourtable values(date '2021-01-04', '01');
Query:
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(day)=( max(day)- min(day))+1)
group by day;
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
01-JAN-21
4
01,02,03,04
02-JAN-21
3
01,02,03
03-JAN-21
1
01
04-JAN-21
1
01
db<fiddle here
Revised answer
create table yourtable(DAY int, PERSON_ID varchar(10));
insert into yourtable(day,person_id)
with cte ( DAY, PERSON_ID ) AS(
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL)
select * from cte ;
Query#1 (for Oracle 19c and later)
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(distinct person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(DISTINCT day)=(select COUNT( distinct DAY) from yourtable where day<=a.day))
group by day;
Query#1 (for Oracle 18c and earlier)
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG( person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from
(
select distinct day, person_id
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(DISTINCT day)=(select COUNT( distinct DAY) from yourtable where day<=a.day))
)t group by day
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
10
3
01,02,04
12
2
01,02
13
1
01
14
1
01
db<fiddle here
In Standard SQL, I would approach this by doing the following:
Enumerate the days for each person.
Determine the earliest day for each person.
Filter where the earliest day is "1" and the enumeration equals the days.
Then aggregate:
select day, count(*),
listagg(person_id, ',') within group (order by person_id)
from (select t.*,
row_number() over (partition by person_id order by day) as seqnum,
min(day) over (partition by person_id) as min_day
from t
) t
where seqnum = day and min_day = 1
group by day
order by day;
Note only is this simpler than using match recognize, but I would guess that the performance would be much better too.
You can use either:
SELECT DAY,
COUNT(DISTINCT person_id) AS num_people
FROM (
SELECT t.*,
DENSE_RANK() OVER (ORDER BY day)
- DENSE_RANK() OVER (PARTITION BY person_id ORDER BY day) AS day_grp
FROM table_name t
)
WHERE day_grp = 0
GROUP BY day
ORDER BY day
or MATCH_RECOGNIZE to find the successive days:
SELECT day,
COUNT(
DISTINCT
CASE cls WHEN 'CONSECUTIVE_DAYS' THEN person_id END
) AS num_people
FROM (
SELECT t.*,
DENSE_RANK() OVER (ORDER BY day) AS day_rank
FROM table_name t
)
MATCH_RECOGNIZE(
PARTITION BY person_id
ORDER BY day
MEASURES
classifier() AS cls
ALL ROWS PER MATCH
PATTERN ( ^ consecutive_days* )
DEFINE
consecutive_days AS COALESCE( PREV(day_rank) + 1, 1 ) = day_rank
)
GROUP BY day
ORDER BY day
Which, for the sample data:
CREATE TABLE table_name ( DAY, PERSON_ID ) AS
SELECT 1, '01' FROM DUAL UNION ALL
SELECT 1, '02' FROM DUAL UNION ALL
SELECT 1, '03' FROM DUAL UNION ALL
SELECT 1, '04' FROM DUAL UNION ALL
SELECT 2, '01' FROM DUAL UNION ALL
SELECT 2, '02' FROM DUAL UNION ALL
SELECT 2, '03' FROM DUAL UNION ALL
SELECT 3, '01' FROM DUAL UNION ALL
SELECT 3, '02' FROM DUAL UNION ALL
SELECT 4, '01' FROM DUAL;
Outputs:
DAY
NUM_PEOPLE
1
4
2
3
3
2
4
1
and for the sample data:
CREATE TABLE table_name ( DAY, PERSON_ID ) AS
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL
Outputs:
DAY
NUM_PEOPLE
10
3
12
2
13
1
14
1
db<>fiddle here

find gap between months in two consecutive year oracle sql

Need to find record having gap between months in a table if the data is present in two different year.
I have column like id, value,month, year.
Id, value, month,year
1, 123, oct, 2020
1, 128, nov, 2020
1, 127, jan ,2021
2, 121, Dec, 2020
2, 154, jan, 2021
Output I need:
Id 1 as there is a gap in month (Dec is Missing for id=1)
Here's one option. Read comments within code.
SQL> with test (id, value, month, year) as
2 -- sample data; you have that, don't type it
3 (select 1, 123, 'oct', 2020 from dual union all
4 select 1, 128, 'nov', 2020 from dual union all
5 select 1, 127, 'jan', 2021 from dual union all
6 select 2, 121, 'dec', 2020 from dual union all
7 select 2, 154, 'jan', 2021 from dual
8 ),
9 temp as
10 -- "convert" month and year to real date value
11 (select id,
12 value,
13 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
14 from test
15 ),
16 temp2 as
17 -- select difference in months between DATUM and next month (LEAD!)
18 (select id,
19 months_between
20 (datum,
21 to_date(month ||' '|| year, 'mon yyyy', 'nls_date_language=english') datum
22 ) diff
23 from temp
24 )
25 select distinct id
26 from temp2
27 where abs(diff) > 1;
ID
----------
1
SQL>
It can probably be compressed, but step-by-step CTEs show what's going on.
I would construct a date and use lag():
select t.*
from (select t.*,
lag(dte) over (partition by id order by dte) as prev_dte
from (select t.*,
to_date(year || '-' || month || '-01', 'YYYY-MON-DD') as dte
from t
) t
) t
where prev_dte <> dte - interval '1' month;
Here is a db<>fiddle.
Here is an example using the LAG function and finding rows where where the prior month is not one month behind (or non existent)
WITH
sample_data (Id,
VALUE,
month,
year)
AS
(SELECT 1, 123, 'oct', 2020 FROM DUAL
UNION ALL
SELECT 1, 128, 'nov', 2020 FROM DUAL
UNION ALL
SELECT 1, 127, 'jan', 2021 FROM DUAL
UNION ALL
SELECT 2, 121, 'Dec', 2020 FROM DUAL
UNION ALL
SELECT 2, 154, 'jan', 2021 FROM DUAL)
SELECT DISTINCT id
FROM (SELECT sd.id,
CASE
WHEN ADD_MONTHS (TO_DATE (sd.year || sd.month, 'YYYYMON'), -1) =
TO_DATE (
LAG (sd.year || sd.month)
OVER (
PARTITION BY id
ORDER BY
sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON'))),
'YYYYMON')
OR LAG (sd.id)
OVER (
PARTITION BY id
ORDER BY sd.year, EXTRACT (MONTH FROM TO_DATE (sd.month, 'MON')))
IS NULL
THEN
'Y'
ELSE
'N'
END AS valid_prev_month
FROM sample_data sd)
WHERE valid_prev_month = 'N';

Not able to group data according to month given in a date using SQL

I have the following set of sample data from a database
Period Company Metric Values
01/01/18 A Vol 2
02/01/18 A Vol 4
04/02/18 A Vol 5
05/02/18 B Vol 6
06/03/18 B Vol 4
07/04/18 C Vol 1
08/05/18 C Vol 6
I wish to display a total of "Values" for each company according to month.
For Example 'company A' has a total value of 6 for first month and value of 5 for second month
As a first step, I tried working as
SELECT COUNT(*) FROM `TABLE 2` WHERE DATEPART(MONTH, `Period`) = 01;
But it is throwing an error and it also does not have a group by function
Can anyone please tell how it can be done
You're using count() function instead of sum() function.
As per your requirement, the query should something similar to
SELECT COMPANY,SUM(VALUES) FROM TABLE2
GROUP BY DATEPART(MONTH, Period)
If you want the query without group by, use direct where clause.
SELECT SUM(VALUES) FROM TABLE2 WHERE DATEPART(MONTH, Period) = 01;
Try this Only for MySQL, DATEPART is not available in MySQL
SELECT MONTH(Period) as gmonth, Period, Company, SUM(Values) FROM `TABLE 2`
GROUP BY MONTH(Period);
As you tagged the question with the Oracle tag as well, here you go: TO_CHAR function with the 'mm' format mask fetches month from the date (01 for January, 02 for February, etc.). As there's an aggregate function (count) involved, you have to GROUP BY column that isn't aggregated. (As far as I can tell, that is valid for any other database.)
SQL> with test (period, company) as
2 (select date '2018-01-01', 'A' from dual union all
3 select date '2018-01-02', 'A' from dual union all
4 select date '2018-02-04', 'A' from dual union all
5 select date '2018-02-05', 'B' from dual union all
6 select date '2018-03-06', 'B' from dual union all
7 select date '2018-04-07', 'C' from dual union all
8 select date '2018-05-08', 'A' from dual
9 )
10 select to_char(period, 'mm') month, count(*)
11 from test
12 group by to_char(period, 'mm')
13 order by 1;
MO COUNT(*)
-- ----------
01 2
02 2
03 1
04 1
05 1
SQL>

Howto get freq of count's data in month 1 - 12 in specific year

Howto i get frequency of count's data like result below
i want result like this to create dashboard
## Data in year 2014
Table 1
Month_name | Count_value
month1 0
month2 12
month3 15
month4 5
month5 6
month6 12
month7 9
month8 50
month9 0
month10 0
month11 0
month12 0
My DB Structure Like This
cust_name | doc_date
C001 13.02.2014
C098 17.06.2014
C099 05.06.2014
i want to count it group by month.
Try this:
select x.mo,
count(*) as freq
from ( select '01' as mo from RDB$DATABASE union all select '02' from RDB$DATABASE union all select '03' from RDB$DATABASE union all
select '04' from RDB$DATABASE union all select '05' from RDB$DATABASE union all select '06' from RDB$DATABASE union all
select '07' from RDB$DATABASE union all select '08' from RDB$DATABASE union all select '09' from RDB$DATABASE union all
select '10' from RDB$DATABASE union all select '11' from RDB$DATABASE union all select '12'from RDB$DATABASE ) x
left join table_1 t
on x.mo = substring(t.doc_date,3,2)
where t.doc_date like '%2014'
group by x.mo
order by 1
This does assume the doc_date field is stored in DD.MM.YYYY format like you posted.
I used a left join w/ an inline view that selects '01' through '12' so that months with no docs will still come back (your desired output shows months w/ zero)