Count number of members each month - sql

I have a table like so
--Member--
ID char(10),
Name (nvarchar(50),
joiningDay date,
exitDay date
How can I count total number of member each month with a selected period of time with member that has exit will not be counted in that month
and the result should be like:
Start from month 1 to month 10
Month totalOfMember
1 5
2 6
3 9
... ...
10 35

If you want to select number of members joined in a month
select month(joiningday)[Month],sum(case when month(joiningday)=month(exitday) then 0 else 1 end)TotalOfMember
from Member where joiningDay>='1 jan 2020' and joiningDay<'1 jan 2021'
group by month(joiningday)
order by [Month]
But I would suggest to use Format(datevalue,'yyyy MM') to count member month and year wise instead of only month. 01 can be repeated in a date range if there are two January but 2020 01 and 2021 01 will be uniquely identified.
select Format(joiningday,'yyyy MM') [Month],sum(case when Format(joiningday,'yyyy MM') = Format(exitday,'yyyy MM') then 0 else 1 end)TotalOfMember
from Member where joiningDay>='1 jan 2020' and joiningDay<'1 jan 2021'
group by Format(joiningday,'yyyy MM')
order by [Month]
Or if you want to have month wise cumulative count for members :
select Format(joiningday,'yyyy MM') [Month],sum(case when Format(joiningday,'yyyy MM') = Format(exitday,'yyyy MM') then 0 else 1 end)over(order by Format(joiningday,'yyyy MM')) TotalOfMember
from Member where joiningDay>='1 jan 2020' and joiningDay<'1 jan 2021'
group by Format(joiningday,'yyyy MM')
order by [Month]

Join the table to itself to eliminate same-month exiters:
select
year(a.joinjngday) Year,
month(a.joiningday) Month,
count(*) total
from Member a
join Member b on a.ID = b.ID
and month(b.exitday) = month(a.joinjngday)
and year(b.exitday) = year(a.joiningday)
where b.ID is null -- exclude same month exits
group by month(a.joiningday)
order by year(a.joinjngday), month(a.joiningday)

The most efficient way to do this is to use a cumulative sum. Basically, you want a + count and - count for each month based on the number of members who join and leave. I would approach this as:
with changes as (
select dateformparts(year(dte), month(dte), 1) as yyyymm,
sum(inc) as month_changes
from members m cross apply
(values (joiningday, 1),
(exitday, -1)
) v(dte, inc)
)
select yyyymm, sum(month_changes)
from changes
group by yyyymm
order by yyyymm;
If you want this for a particular period of time, then filter the above. Also, this will not contain months that have no changes.
If you wanted this for just a handful of months -- even those with no changes -- then you might find that explicit counting is simpler:
select d.dte,
(select count(*)
from members m
where m.joiningday <= eomonth(d.dte) and
m.exitday > eomonth(d.dte)
) as members_in_month
from (values (convert(date, '2021-01-01')),
(convert(date, '2021-02-01')),
(convert(date, '2021-03-01'))
) d(dte);

Related

How do I include months that have no data?

I am trying to create a report that shows how many training records will expire within a chosen date range however when I run the report it excludes months that have no training records going out of date. I have tried various solutions I've seen posted but I haven't been able to get any of them to work in my case.
This is my query:
SELECT COUNT(ISNULL(TRAININGRECORDID, 0)) AS NUMBEROFRECORDS
,DEPARTMENTNUMBER
,DATENAME( Month, EXPIRY ) + '-' + DATENAME( Year, EXPIRY ) AS [MONTHYEAR]
FROM Training_Records TR
JOIN Departments TD ON TR.DEPARTMENTID = TD.DEPARTMENTID
WHERE TR.EXPIRY IS NOT NULL
AND TD.DEPARTMENTNUMBER IN (#DEPTNO)
AND TR.EXPIRY BETWEEN #StartDate AND #EndDate
GROUP BY TD.DEPARTMENTNUMBER, DATENAME(Year, TR.EXPIRY), DATENAME(Month, TR.EXPIRY)
ORDER BY TD.DEPARTMENTNUMBER, [MONTHYEAR]
An example of results from this query looks like this:
NUMBEROFRECORDS DEPARTMENTNUMBER MONTHYEAR
1 21 April-2023
4 23 June-2023
1 83 August-2023
I am displaying the results of this query in a matrix with MONTHYEAR as the columns. In the example above the report will display April, June and August 2023 but will skip over the months May, July 2023 because there are no records going out of date in those months but despite that I still want them displayed in my report/returned in my query.
I've tried various solutions I've found on here but none of them have worked for me. How would I go about including these months with no records going out of date?
You need to first get all of the months, and then outer join to them (not using BETWEEN). Here is an example that gets April, May, June, and July, and then shows how you would outer join that against your table.
DECLARE #StartDate date = '20220405',
#EndDate date = '20220708';
;WITH Months(TheMonth) AS
(
SELECT DATEFROMPARTS(YEAR(#StartDate), MONTH(#StartDate), 1)
UNION ALL
SELECT DATEADD(MONTH, 1, TheMonth)
FROM Months
WHERE TheMonth < DATEFROMPARTS(YEAR(#EndDate), MONTH(#EndDate), 1)
)
SELECT TheMonth -- , COALESCE(SUM({your table}.{column}),0)
FROM Months AS m
-- LEFT OUTER JOIN {your table}
-- ON {your table}.{date column} >= m.TheMonth
-- AND {your table}.{date column} < DATEADD(MONTH, 1, m.TheMonth);
Output:
TheMonth
2022-04-01
2022-05-01
2022-06-01
2022-07-01
Example db<>fiddle
If your range could last more than 100 months, you'll need to add:
OPTION (MAXRECURSION 0);

How to get number of billable customers per month SQL

This is what my table looks like:
NOTE: Don't worry about the BMI field being empty in some rows. We assume that each row is a reading. I have omitted some columns for privacy reasons.
I want to get a count of the number of active customers per month. A customer is active if they have at least 18 readings in total (1 reading per day for 18 days in a given month). How do I write this SQL query? Assume the table name is 'cust'. I'm using SQL Server. Any help is appreciated.
Presumably a patient is a customer in your world. If so, you can use two levels of aggregation:
select yyyy, mm, count(*)
from (select year(createdat) as yyyy, month(createdat) as mm,
patient_id,
count(distinct convert(date, createdat)) as num_days
from t
group by year(createdat), month(createdat), patient_id
) ymp
where num_days >= 18
group by yyyy, mm;
You need to group by patient and the month, then group again by just the month
SELECT
mth,
COUNT(*) NumPatients
FROM (
SELECT
EOMONTH(c.createdat) mth
FROM cust c
GROUP BY EOMONTH(c.createdat), c.patient_id
HAVING COUNT(*) >= 18
-- for distinct days you could change it to:
-- HAVING COUNT(DISTINCT CAST(c.createdat AS date)) >= 18
) c
GROUP BY mth;

sql user retention calculation

I have a table records like this in Athena, one user one row in a month:
month, id
2020-05 1
2020-05 2
2020-05 5
2020-06 1
2020-06 5
2020-06 6
Need to calculate the percentage=( users come both prior month and current month )/(prior month total users).
Like in the above example, users come both in May and June 1,5 , May total user 3, this should calculate a percentage of 2/3*100
with monthly_mau AS
(SELECT month as mauMonth,
date_format(date_add('month',1,cast(concat(month,'-01') AS date)), '%Y-%m') AS nextMonth,
count(distinct userid) AS monthly_mau
FROM records
GROUP BY month
ORDER BY month),
retention_mau AS
(SELECT
month,
count(distinct useridLeft) AS retention_mau
FROM (
(SELECT
userid as useridLeft,month as monthLeft,
date_format(date_add('month',1,cast(concat(month,'-01') AS date)), '%Y-%m') AS nextMonth
FROM records ) AS prior
INNER JOIN
(SELECT
month ,
userid
FROM records ) AS current
ON
prior.useridLeft = current.userid
AND prior.nextMonth = current.month )
WHERE userid is not null
GROUP BY month
ORDER BY month )
SELECT *, cast(retention_mau AS double)/cast(monthly_mau AS double)*100 AS retention_mau_percentage
FROM monthly_mau as m
INNER JOIN monthly_retention_mau AS r
ON m.nextMonth = r.month
order by r.month
This gives me percentage as 100 which is not right. Any idea?
Hmmm . . . assuming you have one row per user per month, you can use window functions and conditional aggregation:
select month, count(*) as num_users,
sum(case when prev_month = dateadd('month', -1, month) then 1 else 0 end) as both_months
from (select r.*,
cast(concat(month, '-01') AS date) as month_date,
lag(cast(concat(month, '-01') AS date)) over (partition by id order by month) as prev_month_date
from records r
) r
group by month;

SQL - date group by year, month, days - update

I used code to calculate difference between two date group by year, months, date:
;WITH calendar AS (
SELECT CAST(MIN([From date]) as datetime) as d,
MAX([To date]) as e
FROM ItemTable
UNION ALL
SELECT DATEADD(day,1,d),
e
FROM calendar
WHERE d < e
), cte AS(
SELECT i.Item,
DATEPART(year,c.d) as [Year],
DATEDIFF(month,MIN(c.d),MAX(c.d)) as NoOfMonth,
DATEDIFF(day,DATEADD(month,DATEDIFF(month,MIN(c.d),MAX(c.d)),MIN(c.d)),
MAX(c.d)) as NoOfDays
FROM ItemTable i
INNER JOIN calendar c
ON c.d between i.[From date] and i.[To date]
GROUP BY i.Item, DATEPART(year,c.d),[From date],[To date]
)
SELECT Item,
[Year],
SUM(NoOfMonth) as NoOfMonth,
SUM(NoOfDays) as NoOfDays
FROM cte
GROUP BY Item,[Year]
ORDER BY Item
OPTION (MAXRECURSION 0)
I found this code in SQL - date group by year, month, days
But not work for me...
When I execute my query
SELECT Item,
[From date],
[To date]
from ItemDate;
I got
('A1','2013-08-27','2013-09-27'),
('A1','2013-09-28','2013-11-28'),
('A1','2013-11-30','2013-12-03'),
('A1','2013-12-31','2014-03-31'),
('A1','2014-04-01','2014-07-01'),
('A1','2014-07-02','2014-10-02'),
('A1','2014-10-03','2014-12-31')
and when execute code from this link SQL - date group by year, month, days
I get this:
Item Year NoOfMonth NoOfDays
A1 2013 4 -27
A2 2014 10 58
This is not good.... It should be 3 months and 4 day for year 2013,
and for year 2014 11 month and 28 days
How to update the code to get the desired result?
Change the last select to:
SELECT Item,
[Year],
CASE WHEN SUM(NoOfDays) < 0 THEN SUM(NoOfMonth)-1
WHEN SUM(NoOfDays) > 30 THEN SUM(NoOfMonth)+1
ELSE SUM(NoOfMonth) END as NoOfMonth,
CASE WHEN SUM(NoOfDays) >= 30 THEN SUM(NoOfDays)-30
WHEN SUM(NoOfDays) < 0 THEN SUM(NoOfDays)+30
ELSE SUM(NoOfDays) END as NoOfDays
FROM cte
GROUP BY Item,[Year]
ORDER BY Item
OPTION (MAXRECURSION 0)
The main problem of such report - it is hard to define what is 1 month, DATEDIFF just takes number from 2 dates and subtract one from another.
I have choose 30 as a days count in month, and now I compare values of days with 30 so we can add +1 to month if the day count goes under zero or below 30

SQL Sum and Count Separated by Month

I am writing a script that query's a table and counts all rows that have a status of 10 and separates the total count by month. Something is
not right considering I have two Decembers in my results.
In November, there is only one date in it meaning two rows have a status of 10 and are under the same date (11-04). December has 252 rows on the same date (12-04) and 1 row with a 12-05 date .
How to query and separate a count and date by months?
Any help is most appreciated.
SELECT CONVERT(CHAR(3), Datename(month, datecomplete)) AS Month,
Count(*) AS Val
FROM nwds
WHERE status = 10
GROUP BY Datediff(month, 0, datecomplete),
datecomplete
My Results
Nov 2
Dec 252
Dec 1
Desired Results
Nov 2
Dec 253
SELECT LEFT(DATENAME(M, datecomplete), 3) AS Month,
Count(*) AS Val
FROM nwds
WHERE status = 10
GROUP BY LEFT(DATENAME(M, datecomplete), 3)
If you have ever get data for different years, you can add the year to the GROUP BY.
SELECT LEFT(DATENAME(M, datecomplete), 3) AS Month,
YEAR(datecomplete) AS Year,
Count(*) AS Val
FROM nwds
WHERE status = 10
GROUP BY LEFT(DATENAME(M, datecomplete), 3), YEAR(datecomplete)
datecomplete should be excluded from group by, or the results would be grouped by day. Grouping needs to be on month and year part of datecomplete column.
SELECT
CONVERT(CHAR(3), Datename(month, datecomplete)) AS Month,
count(*) AS Val
FROM nwds
WHERE status = 10
GROUP BY CONVERT(CHAR(3), Datename(month, datecomplete)) ,
datepart(yyyy, datecomplete)
Try this
select CONVERT(char(3),DATENAME(MONTH,datecomplete)) as [month],
count(*) as val
from nwds
WHERE status = 10
group by CONVERT(char(3),DATENAME(MONTH,datecomplete))