Counting distinct members based on number of months in duration of time - sql

I have a membership table with the following columns:
Member_number | StartDate | EndDate
XYZ | 01-Jan-2002 | 01-March-2002
ABC | 01-Feb-2002 | 01-March-2002
Basically, I want to show how many members were present in specific month. My problem is I don't know how to break this time span into months. How can I see this result?
Month | NumberOfMembers
Jan | 1
Feb | 2
March | 2

Given a members table that looks something like this:
create table dbo.members
(
member_number int not null primary key ,
start_date datetime not null ,
end_date datetime not null ,
)
And a table-valued function that generates sequences of consecutive integers, like this:
create function dbo.IntegerRange ( #from int , #thru int )
returns #sequence table
(
value int not null primary key clustered
)
as
begin
declare #increment int = case when #from > #thru then -1 else 1 end ;
with sequence(value) as
(
select value = #from
union all
select value + #increment
from sequence
where value < #thru
)
insert #sequence
select value
from sequence
order by value
option ( MAXRECURSION 0 )
return
end
A query like this should give you what you want:
select [year] = period.yyyy ,
[month] = case period.mm ,
when 1 then 'Jan'
when 2 then 'Feb'
when 3 then 'Mar'
when 4 then 'Apr'
when 5 then 'May'
when 6 then 'Jun'
when 7 then 'Jul'
when 8 then 'Aug'
when 9 then 'Sep'
when 10 then 'Oct'
when 11 then 'Nov'
when 12 then 'Dev'
else '***'
end ,
member_cnt = sum( case when m.member_number is not null then 1 else 0 end )
from ( select yyyy = yyyy.value ,
mm = mm.value ,
dtFrom = dateadd( month , mm.value - 1 , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) ,
dtThru = dateadd( day , - 1 , dateadd( month , mm.value , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) )
from dbo.IntegerRange(2000,2013) yyyy
full join dbo.IntegerRange(1,12) mm on 1=1
) period
left join dbo.members m on period.dtFrom <= m.end_date
and period.dtThru >= m.start_date
group by period.yyyy ,
period.mm
order by period.yyyy ,
period.mm
The first table expression in the from clause creates a virtual table of the periods (months, in this case, but the technique doesn't limit itself to months or even weeks) covering the reporting period:
from ( select yyyy = yyyy.value ,
mm = mm.value ,
dtFrom = dateadd( month , mm.value - 1 , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) ,
dtThru = dateadd( day , - 1 , dateadd( month , mm.value , dateadd( year , yyyy.value - 1900 , convert(date,'') ) ) )
from dbo.IntegerRange(2000,2013) yyyy
full join dbo.IntegerRange(1,12) mm on 1=1
) period
That is then joined, via a left outer join, ensuring that all periods are reported, not just those periods with active members, to the members table to collect, for each reporting period in the virtual period table above, the set of members who were active during the period:
left join dbo.members m on period.dtFrom <= m.end_date
and period.dtThru >= m.start_date
We then group by the year and month of each period and then order the results by year/month number:
group by period.yyyy ,
period.mm
order by period.yyyy ,
period.mm
In creating the results set to be returned, we return the year of the period, the month number (converted to a friendly name), and the count of active members. Note that we have to use the sum() aggregate function here rather than count() as empty periods will have a single row returned (with null in all columns). Count(), unlike all other aggregate functions, includes null values in the aggregation. Sum() is applied to a case expression acting as a discriminant function returning 1 or 0 identifying whether the row indicates useful or missing data:
select [year] = period.yyyy ,
[month] = case period.mm ,
when 1 then 'Jan'
when 2 then 'Feb'
when 3 then 'Mar'
when 4 then 'Apr'
when 5 then 'May'
when 6 then 'Jun'
when 7 then 'Jul'
when 8 then 'Aug'
when 9 then 'Sep'
when 10 then 'Oct'
when 11 then 'Nov'
when 12 then 'Dev'
else '***'
end ,
member_cnt = sum( case when m.member_number is not null then 1 else 0 end )
Easy!

DECLARE #minMonth DATE
SELECT #minMonth = MIN(StartDate) FROM Table1
DECLARE #maxMonth DATE
SELECT #maxMonth = MAX(EndDate) FROM Table1
;WITH CTE_Months AS
(
SELECT #minMonth AS Mnth
UNION ALL
SELECT DATEADD(MM,1,Mnth) FROM CTE_Months
WHERE Mnth<#MaxMonth
)
SELECT Mnth AS Month, COUNT(*) as Members
FROM CTE_Months m
LEFT JOIN Table1 t on m.Mnth BETWEEN t.StartDate AND t.EndDate
GROUP BY Mnth
SQLFiddle Demo
CTE will find all months from min StartDate to max EndDate, if you need different min and max, just change how you get #MinMonth and #MaxMonth
If you don't want to show zeros for months that possibly don't have any members, replace LEFT JOIN with INNER at the end.

select t.Month, count(*) Members
from (
select case
when startdate <= to_date('01-Jan-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Jan-2002', 'DD-MON-YYYY') then ' Jan'
when startdate <= to_date('01-Feb-2002', 'DD-MON-YYYY') AND enddate >= to_date('28-Feb-2002', 'DD-MON-YYYY') then ' Feb'
when startdate <= to_date('01-Mar-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Mar-2002', 'DD-MON-YYYY') then ' Mar'
when startdate <= to_date('01-Apr-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Apr-2002', 'DD-MON-YYYY') then ' Apr'
when startdate <= to_date('01-May-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-May-2002', 'DD-MON-YYYY') then ' May'
when startdate <= to_date('01-Jun-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Jun-2002', 'DD-MON-YYYY') then ' Jun'
when startdate <= to_date('01-Jul-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Jul-2002', 'DD-MON-YYYY') then ' Jul'
when startdate <= to_date('01-Aug-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Aug-2002', 'DD-MON-YYYY') then ' Aug'
when startdate <= to_date('01-Sep-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Sep-2002', 'DD-MON-YYYY') then ' Sep'
when startdate <= to_date('01-Oct-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Oct-2002', 'DD-MON-YYYY') then ' Oct'
when startdate <= to_date('01-Nov-2002', 'DD-MON-YYYY') AND enddate >= to_date('30-Nov-2002', 'DD-MON-YYYY') then ' Nov'
when startdate <= to_date('01-Dec-2002', 'DD-MON-YYYY') AND enddate >= to_date('31-Dec-2002', 'DD-MON-YYYY') then ' Dec'
end as Month
from member) t
group by t.Month
Where member is the table name.

Related

Count of days for rest of month returning incorrect value for EOM date

The below code when run for the last day of the month it is giving me a week day count of 1 when it should be 0 - how can I fix it?
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekCount
from mycte
Your initial CTE is actually creating a date of 2021-04-01 which is a week day, so that's where your count of 1 is coming from. If you want to restrict the counts to just the month in question, you could add a WHERE clause to your end query like this. This way, you get zeros for all counts.
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue +1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekDayCount
from mycte
where DATEPART(MM,DateValue)=DATEPART(MM,#EndDate)

SQL Event Calendar

I have an Event Calendar that I am trying to create in SQL.The issue is I can not pivot the events like I do the dates. Is there a way to do two pivots or something similiar so that the events line up under the dates.
Here is the table before a pivot
YearNum MonthName week_number DayNum DayofWeek EventStart EventSubject EventLocation EventDate
2018 August 33 14 Tuesday NULL NULL NULL NULL
2018 August 33 15 Wednesday NULL NULL NULL NULL
2018 August 33 16 Thursday 13:00 Dan's Birthday EC-E 2018-08-16
2018 August 33 17 Friday NULL NULL NULL NULL
2018 August 33 18 Saturday NULL NULL NULL NULL
2018 August 34 19 Sunday NULL NULL NULL NULL
2018 August 34 20 Monday NULL NULL NULL NULL
After the pivot, this is how it looks
Here is the SQL Query that I am currently working with:
SELECT MonthName, YearNum, Sunday, Monday,Tuesday,Wednesday,Thursday,Friday,Saturday, EventStart, EventSubject, EventLocation, EventDate
FROM
(
SELECT CalendarDates.YearNum, CalendarDates.MonthName, DATEPART(WEEK, CalendarDates.StandardDate) AS week_number, CalendarDates.DayNum,
CalendarDates.DayofWeek, EventList.EventStart, EventList.EventSubject, EventList.EventLocation, EventList.EventDate
FROM (SELECT EventStart, EventSubject, EventLocation, EventDate
FROM EventList AS EventList_1) AS EventList RIGHT OUTER JOIN
CalendarDates ON EventList.EventDate = CalendarDates.StandardDate
WHERE (CalendarDates.MonthNum = '8') AND (CalendarDates.YearNum = '2018')
)
pivotDates
PIVOT (MIN(DayNum) FOR DayofWeek IN (Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday)) AS Pivots
This is how the output should look like
This problem could be better solved in the presentation layer, as the SQL does a good job on computing/filtering data, but not in user interface.
Anyways, try this:
DECLARE #CurrentMonth INT = 8,
#CurrentYear INT = 2018
;WITH RawData AS -- Your original appointment dates
(
SELECT CAST('2018-08-16' AS DATE) Date,
CAST('13:00' AS TIME) Start,
'Someone birthday' Subject,
'Location' Location
), AllMonthDays AS -- list of all days in the filtered month
(
SELECT CAST(CAST(#CurrentYear AS VARCHAR) + '-' +
CAST(#CurrentMonth AS VARCHAR) + '-01' AS DATE) Date UNION ALL
SELECT DATEADD(D, +1, Date)
FROM AllMonthDays
WHERE MONTH(DATEADD(D, +1, Date)) = #CurrentMonth
), CalendarData AS -- Adds the weekday name, week numbering and appointment data
(
SELECT AllMonthDays.Date,
DATEPART(WEEK , AllMonthDays.Date) WeekNumber,
DATENAME(DW , AllMonthDays.Date) WeekdayName,
NULLIF(CONCAT(
CAST(RawData.Start AS CHAR(5)), ' ',
RawData.Subject, '# ', RawData.Location), ' # ') Appointment
FROM AllMonthDays
LEFT JOIN RawData
ON RawData.Date = AllMonthDays.Date
), CalendarFormat AS -- PIVOT the data, putting the weekdays as columns
(
SELECT *
FROM CalendarData
PIVOT
( --MIN(Date) FOR -- uncomment this to a better understanding
MIN(Appointment) FOR -- weird part: using MIN in a text column
WeekdayName IN
( [Sunday], [Monday], [Tuesday],
[Wednesday], [Thursday], [Friday], [Saturday]
)
) p
)
SELECT MIN(WeekNumber ) [WeekNumber],
MIN([Sunday] ) [Sunday],
MIN([Monday] ) [Monday],
MIN([Tuesday] ) [Tuesday],
MIN([Wednesday]) [Wednesday],
MIN([Thursday] ) [Thursday],
MIN([Friday] ) [Friday],
MIN([Saturday] ) [Saturday]
FROM CalendarFormat
GROUP BY WeekNumber -- suppress duplications
ORDER BY WeekNumber

How to sum total by a field name by month for the previous six month

I have a table with following fields
Status, branch, reason code description, year, month, day, count
I am trying to get a report like this for each branch:
Reason code desc.,July,August,sept.,October.,nov.,Dec.,Total,% of Total
Reason 1. 4. 6. 2. 5. 0. 2. 19 79.1
Reason 2. 1 0. 2. 1. 1. 0 5. 20.9
--------------------------
5. 6. 4. 6. 1. 2. 24 100.0
Assuming that your database is Oracle the query can be:
select RCMonth.*, RCTotal.Total, round(RCTotal.Total*100/Total.Total, 2) "Total%"
from
(
select reasoncode , to_char(createdon,'Mon') mon
from rc_report
where createdon > sysdate - 180
union all
select 'Sum' , to_char(createdon,'Mon') mon
from rc_report
where createdon > sysdate - 180
) pivot
( count(*)
for mon in ('Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan')
) RCMonth
join ( select count(*) Total,
reasoncode
from rc_report
where createdon > sysdate - 180
group by reasoncode
union all
select count(*) Total,
'Sum'
from rc_report
where createdon > sysdate - 180
) RCTotal
on RCTotal.ReasonCode = RCMonth.ReasonCode
cross join (
select count(*) Total
from rc_report
where createdon > sysdate - 180 ) Total
order by RCMonth.ReasonCode
Sample result is:
Row# REASONCODE 'Aug' 'Sep' 'Oct' 'Nov' 'Dec' 'Jan' TOTAL Total%
1 Reason1 2 2 0 3 2 0 9 81.82
2 Reason2 0 1 0 1 0 0 2 18.18
3 Sum 2 3 0 4 2 0 11 100
Table definition is :
create table rc_report
(reasoncode varchar2(20),
createdon date)
And also version for SQL Server in SQL Fiddle
It's only a little bit different.
select RCMonth.*, RCTotal.Total, round( cast ( RCTotal.Total as decimal) *100 /Total.Total,2) "Total%"
from
(
select reasoncode, [Aug], [Sep], [Oct], [Nov], [Dec], [Jan] from (
select reasoncode , left( datename(Month,createdon),3) mon
from rc_report
where createdon >= DATEADD(MONTH, -6, GETDATE())
union all
select 'Sum' , left( datename(Month, createdon),3) mon
from rc_report
where createdon >= DATEADD(MONTH, -6, GETDATE())
) Source
pivot
( count(Source.mon)
for mon in ([Aug], [Sep], [Oct], [Nov], [Dec], [Jan])
) as PivotTable
) RCMonth
join ( select count(*) Total,
reasoncode
from rc_report
where createdon >= DATEADD(MONTH, -6, GETDATE())
group by reasoncode
union all
select count(*) Total,
'Sum'
from rc_report
where createdon >= DATEADD(MONTH, -6, GETDATE())
) RCTotal
on RCTotal.ReasonCode = RCMonth.ReasonCode
cross join (
select count(*) Total
from rc_report
where createdon >= DATEADD(MONTH, -6, GETDATE()) ) Total
order by RCMonth.ReasonCode

How to change row value into column header

select
extract(year from datetimestamp ) Yr,extract(month from datetimestamp) Mn,
c.weekday_of_month wk, a.aircraft_type,count( a.aircraft_type) from fcm_bv.Flights b
join fcm_bv.Fleet a on b.aircraftid=a.tail
join SYS_CALENDAR.CALENDAR c
on cast(b.datetimestamp AS DATE FORMAT 'YYYY-MM-DD') = cast(c.calendar_date AS DATE FORMAT 'YYYY-MM-DD')
where cast(datetimestamp as date) >= '2011-09-01'
and cast(datetimestamp as date) <= '2011-09-30' order by wk
group by Yr,Mn,wk,a.fleet,a.aircraft_type
While Running above Query I am getting out put like this
Yr Mn wk AIRCRAFT_TYPE Count(AIRCRAFT_TYPE)
2011 9 1 B737-700 1744
2011 9 1 B737-800 131
2011 9 1 B737-800W 2711
2011 9 1 B737-8BK 180
2011 9 1 B737-700W 329
But I need output in below format
Yr Mn wk B737-700 B737-800 B737-800W B737-8BK B737-700W
2011 9 1 1744 131 2711 180 329
Could any one help me
In the past when I have needed to do this the pivot was against a discrete, managable volume of categories and the following SQL has served me well:
SELECT EXTRACT(YEAR FROM b.datetimestamp) AS Yr
, EXTRACT(MONTH FROM b.datetimestamp) AS Mn
, C.weekday_of_month
, COUNT(CASE WHEN a.aircraft_type = 'B737-700' THEN a.aircraft_type ELSE NULL END) AS B737-700
, COUNT(CASE WHEN a.aircraft_type = 'B737-800' THEN a.aircraft_type ELSE NULL END) AS B737-800
, /* Other Known Aircrafts */
, COUNT(CASE WHEN a.aircrat_type NOT IN ('<list of known aircraft types>') THEN a.aircraft_type ELSE NULL END) AS Uncategorized_Aircraft
FROM fcm_bv.Flights b
join fcm_bv.Fleet a on b.aircraftid=a.tail
join SYS_CALENDAR.CALENDAR c
on cast(b.datetimestamp AS DATE FORMAT 'YYYY-MM-DD') = cast(c.calendar_date AS DATE FORMAT 'YYYY-MM-DD')
WHERE cast(datetimestamp as date) >= '2011-09-01'
AND cast(datetimestamp as date) <= '2011-09-30' order by wk
GROUP BY Yr,Mn,wk,a.fleet
If you have to pivot against a constantly changing category it may be best to leave the pivoting to MS Excel or BI tool of choice.
Hope this helps.

SQL Results group by month

I'm trying to return some results spread over a rolling 12 month period eg:
MONTH IN OUT
January 210 191
February 200 111
March 132 141
April 112 141
May 191 188
etc...
How do I spread the results over a date range, populating the first column with the month name?
IN MSSQL it would be something like:
SELECT COUNT(problem.problem_type = 'IN') AS IN,
COUNT(problem.problem_type = 'OUT') AS OUT,
DATEPART(year, DateTime) as Year,
DATEPART(month, DateTime) as Month
FROM problem
WHERE (DateTime >= dbo.FormatDateTime('2010-01-01'))
AND
(DateTime < dbo.FormatDateTime('2010-01-31'))
GROUP BY DATEPART(year, DateTime),
DATEPART(month, DateTime);
But this is against an Oracle database so DATEPART and DateTime are not available.
My Problem table is roughly:
problem_ID Problem_type IN_Date OUT_Date
1 IN 2010-01-23 16:34:29.0 2010-02-29 13:06:28.0
2 IN 2010-01-27 12:34:29.0 2010-01-29 12:01:28.0
3 OUT 2010-02-13 13:24:29.0 2010-09-29 15:04:28.0
4 OUT 2010-02-15 16:31:29.0 2010-07-29 11:03:28.0
Use:
SELECT SUM(CASE WHEN p.problem_type = 'IN' THEN 1 ELSE 0 END) AS IN,
SUM(CASE WHEN p.problem_type = 'OUT' THEN 1 ELSE 0 END) AS OUT,
TO_CHAR(datetime, 'YYYY') AS year,
TO_CHAR(datetime, 'MM') AS month
FROM PROBLEM p
WHERE p.DateTime >= TO_DATE('2010-01-01', 'YYYY-MM-DD')
AND p.DateTime < TO_DATE('2010-01-31', 'YYYY-MM-DD')
GROUP BY TO_CHAR(datetime, 'YYYY'), TO_CHAR(datetime, 'MM')
You could also use:
SELECT SUM(CASE WHEN p.problem_type = 'IN' THEN 1 ELSE 0 END) AS IN,
SUM(CASE WHEN p.problem_type = 'OUT' THEN 1 ELSE 0 END) AS OUT,
TO_CHAR(datetime, 'MM-YYYY') AS mon_year
FROM PROBLEM p
WHERE p.DateTime >= TO_DATE('2010-01-01', 'YYYY-MM-DD')
AND p.DateTime < TO_DATE('2010-01-31', 'YYYY-MM-DD')
GROUP BY TO_CHAR(datetime, 'MM-YYYY')
Reference:
TO_CHAR
TO_DATE
You probably want something like
SELECT SUM( (CASE WHEN problem_type = 'IN' THEN 1 ELSE 0 END) ) in,
SUM( (CASE WHEN problem_type = 'OUT' THEN 1 ELSE 0 END) ) out,
EXTRACT( year FROM DateTime ) year,
EXTRACT( month FROM DateTime ) month
FROM problem
WHERE DateTime >= date '2010-01-01'
AND DateTime < date '2010-01-31'
GROUP BY EXTRACT( year FROM DateTime ),
EXTRACT( month FROM DateTime )