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

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

Related

How to get total amount per previous weeks

I have this table for example:
Date
amount
2021-02-16T21:06:38
10
2021-02-16T21:07:01
5
2021-02-17T01:10:12
-1
2021-02-19T12:00:00
3
2021-02-24T12:00:00
20
2021-02-25T12:00:00
-1
I want the total amount of all previous weeks, per week. So the result in this case would be:
Date
amount
2021-02-15
0
2021-02-22
17
2021-03-01
36
Note: The dates are now the start of each week (Monday).
Any help would this would be greatly appreciated.
Try This:
select week_date, sum(amount) over (order by week_date )
from (
SELECT date(date_) + cast(abs(extract(dow FROM date_) -7 ) + 1 as int) "week_date",
sum(amount) "amount"
from example group by 1) t
DEMO
Above Query will cover only the week in which transaction records are there. If you want to cover all missing week then try below query:
with cte as (
SELECT date(date_) + cast(abs(extract(dow FROM date_) -7 ) + 1 as int) "week_date",
sum(amount) "amount"
from example group by 1
)
select
t1."Date",coalesce(sum(cte.amount) over (order by t1."Date"),0)
from cte right join
(select generate_series(min(week_date)- interval '1 week', max(week_date),interval '1 week') "Date" from cte) t1 on cte.week_date=t1."Date"
DEMO
Use generate_series() to generate the dates you want. Then use left join to bring in the data and aggregate with a cumulative sum:
select gs.week,
coalesce(sum(e.amount), 0) as week_amount,
sum(coalesce(sum(e.amount), 0)) over (order by gs.week) as running_amount
from generate_series('2021-02-15'::date, '2021-03-01'::date, interval '1 week') gs(week) left join
example e
on e.date < gs.week and
e.date >= gs.week - interval '1 week'
group by gs.week
order by gs.week;
Here is a db<>fiddle.

Count number of members each month

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);

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;

How to setup a cumulative count grouped by month with underlying conditions

I've come across somewhat of an interesting scenario where I'm needing to aggregate enrollment counts and group them by the individual month and all subsequent months leading up to the completion date. The starting counter will be placed into the month when the enrollment began, and now I'm needing to set up a cumulative sum to carry out the single count.
Here's a couple of test records I'm working with
I've set up the following query to compile the date_month CTE to compile the full 12 months derived from my Start/End Range variables. I've then joined it to my test table in order to establish the Counter placements.
DECLARE #EnrollmentDateStart DATETIME = '2020-01-01'
DECLARE #EnrollmentDateEnd DATETIME = '2020-12-01'
;WITH CTE_Months(year_month) AS
(
SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, #EnrollmentDateStart), 0))
FROM ( SELECT TOP (DATEDIFF(MONTH, #EnrollmentDateStart, #EnrollmentDateEnd) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
SELECT
[Year] = YEAR(cm.year_month),
[Month] = DATENAME(MONTH, cm.year_month),
SUM(IIF(tt.[Enrollment Start Date] >= #EnrollmentDateStart,1,0)) AS EnrollmentCount
FROM CTE_Months cm
LEFT OUTER JOIN #TMP_Testing_Table tt
ON tt.[Enrollment Start Date] >= cm.year_month
AND tt.[Enrollment Start Date] < DATEADD(MONTH, 1, cm.year_month)
GROUP BY tt.Department, cm.year_month
At this stage, I'm pulling back the following results, so I now have the Enrollment Counts placed into the correct starting months derived from the Enrollment Start Date.
Now I'm trying to figure out what would be the best course of action to place the subsequent count for the additional months leading up to the Completion date?
For example - The first User (UserId: 1) was Enrolled in March, 2020, and Completed in August, 2020, so essentially I'm looking to produce the following result to reflect the number of months ranging between March <> July (Last month prior to Completion)
January: 0
February: 0
March: 1
April: 1
May: 1
June: 1
July: 1
August: 0
September: 0
October: 0
November: 0
December: 0
Thinking a cumulative total should be able to address the subsequent for the month by month range, however, I would then need to zero out the total for all subsequent months on and after the recorded Completion date for this record in question.
Seeing if I can get your thoughts/suggestions on how to address this scenario? Apologies if the information/explanation is confusing, but please let me know, and I'll do my best to elaborate.
....................
SELECT
[Year] = YEAR(cm.year_month),
[Month] = DATENAME(MONTH, cm.year_month),
count(tt.userid) AS EnrollmentCount
FROM CTE_Months cm
LEFT OUTER JOIN #TMP_Testing_Table tt on cm.year_month > eomonth([Enrollment Start Date], -1)
and cm.year_month <= tt.[Enrollment End Date]
GROUP BY cm.year_month

get last 3 month on year in sql server

I want to get last 3 months name from current month. For example current month is December. So, I want get like this October, November and December.
This is my query:
SELECT CONVERT(CHAR, DATENAME(MONTH, IssueDate)) AS MonthName, ItemId
FROM dbo.Issue AS Issue
GROUP BY CONVERT(CHAR, DATENAME(MONTH, IssueDate)), ItemId
HAVING (ItemId = 427)
This returns:
But, my need is:
N.B. When December month close and January month open then October auto excluded as like (November, December and January)
this link is my Database only 2 table (size-243 KB with Zip) on the google drive https://goo.gl/S4m0R5
Add a date diff in a where clause to filter to the last 3 months, and then order by the month number at the end:
SELECT CONVERT(CHAR, DATENAME(MONTH, [IssueDate])) AS MonthName, ItemId
FROM [dbo].[Issue] AS Issue
WHERE datediff(m, [IssueDate], getdate()) between 0 and 2
GROUP BY CONVERT(CHAR, DATENAME(MONTH, [IssueDate])), ItemId, MONTH(IssueDate)
HAVING (ItemId= 427)
order by MONTH(IssueDate);
You can use DATEADD function:
WHERE IssueDate >= dateadd( month, -2, dateadd( day, -datepart( day, getdate() ) + 1, cast( getdate() as date ) ) )
That will give you IssueDate >= '2015-10-01' given today.
That will also work with index you have on IssueDate, if you start doing something like DATEADD / DATEDIFF etc. on IssueDate then the index can only be scanned end-to-end because it needs to processs all rows in the table so renders the index significantly less effective.
DECLARE #t TABLE
(
IssueDate DATETIME,
ItemId INT
)
INSERT INTO #t (IssueDate, ItemId)
VALUES
('20160105', 427),
('20151212', 427),
('20151213', 427),
('20151110', 427),
('20151001', 427),
('20150905', 427)
SELECT DATENAME(MONTH, dt)
FROM (
SELECT DISTINCT TOP(3) DATEADD(MONTH, DATEDIFF(MONTH, 0, IssueDate), 0) AS dt
FROM #t
WHERE ItemId = 427
ORDER BY dt DESC
) t
results -
------------------------------
January
December
November
You can use a recursive CTE to get month names for the last 12 months and then limit it to the last 3 month names in the second part of the query:
;WITH months(MonthNumber) AS
(
SELECT 0
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 12
)
SELECT DATENAME(MONTH,DATEADD(MONTH,-MonthNumber,GETDATE())) AS [month]
FROM dbo.Issue AS Issue
CROSS JOIN months m
WHERE m.MonthNumber <3
GROUP BY DATENAME(MONTH,DATEADD(MONTH,-MonthNumber,GETDATE())) , ItemId
HAVING (ItemId = 427)