Group by join date + add month in where sql - sql

I have the following table:
PERSON
ID
Name
date_created
date_left
What I want is a list of all months and the amount of users joined and the amount of users that left.
I already have the following query: it returns the amount of new users that joined in the month that I pass:
select MONTH(date_created) 'Month', YEAR(date_created) 'Year', count(*) as 'New Users'
from person p
where YEAR(date_created) = 2018 and MONTH( p.date_created) = 5
group by MONTH(date_created), YEAR(date_created)
It returns what I want:
How would I edit this to include a year report and add the column 'Users left' next to the 'new users' one?
My result would be:
MONTH YEAR NEW USERS USERS LEFT
1 2019 10 5

I would "unpivot" the data using cross apply:
select v.[year], v.[month], sum(v.iscreated) as num_created,
sum(v.isleft) as num_left
from person p cross apply
(values (year(p.date_created), month(p.date_created), 1, 0),
(year(p.date_left), month(p.date_left), 0, 1)
) v([year], [month], iscreated, isleft)
group by v.[year], v.[month]
order by v.[year], v.[month];

The straight-forward approach is probably to full outer join all entries and all leaves. SQL Server makes this a bit awkward by not featuring USING, so we must use ON and COALESCE on month and year instead.
select
coalesce(pin.year, pout.year) as year,
coalesce(pin.month, pout.month) as month,
coalesce(pin.cnt, 0) as count_in,
coalesce(pout.cnt, 0) as count_out
from
(
select year(date_created) as year, month(date_created) as month, count(*) as cnt
from person
group by year(date_created), month(date_created)
) pin
full outer join
(
select year(date_left) as year, month(date_left) as month, count(*) as cnt
from person
group by month(date_left), year(date_left)
) pout on pout.year = pin.year and pout.month = pin.month
order by year, month;

Maybe you could do it with a SubSelect? Tried it right now with ORACLE Syntax, I'm not sure if it works in SQL-Server.
SELECT * FROM
(
select MONTH(date_created) 'Month_C', YEAR(date_created) 'Year_C', count(*) as 'New Users'
from person p
where YEAR(date_created) = 2018 and MONTH( p.date_created) = 5
group by MONTH(date_created), YEAR(date_created)
)created_user,
(
select MONTH(date_left) 'Month_L', YEAR(date_left) 'Year_L', count(*) as 'New Users'
from person p
where YEAR(date_left) = 2018 and MONTH( p.date_left) = 5
group by MONTH(date_left), YEAR(date_left)
) left_user
where created_user.Year_C = left_user.Year_L
and created_user.Month_C = left_user.Month_L

Related

T-SQL query to summarize sales for ALL YEARS: with total per month per year, and cumulative monthly amounts

I need to create a Sales Report that shows all years sales per month, and cumulative sales.
The database table is simple:
Transactions
(
ID INT,
TransactionDate DATETIME,
SalesAmount MONEY
)
I want the results to look similar to ExcelSheet below (I am showing only 2017/2018 amounts, but actual query needs to return results for all available years according to TransactionDate)
This is aggregation and a cumulative sum:
select year(TransactionDate), month(TransactionDate),
sum(SalesAmount),
sum(sum(SalesAmount)) over (partition by year(TransactionDate) order by min(TransactionDate))
from Transactions
group by year(TransactionDate), month(TransactionDate)
order by year(TransactionDate), month(TransactionDate);
Try it:
With Q
as
(
Select DatePart(yyyy,TransactionDate) 'Year',DatePart(m,TransactionDate) 'Month', sum(SalesAmount) 'Sales'
From Transactions
Group by DatePart(yyyy,TransactionDate),DatePart(m,TransactionDate)
)
Select q.Year,q.Month,q.sales,( Select sum(q1.Sales)
From Q q1
Where q1.Year=q.Year
And q1.Month <= q.Month
) 'Cumulative Sale'
From Q q
Order by q.Year,q.Month
Try this:
with cte as
(
select year(TransactionDate) as Year, month(TransactionDate) as Month, SalesAmount
)
select a.Year, a.Month, a.SalesAmount, sum(b.SalesAmount) as cumulativeSalesAmount
from Transactions a inner join Transactions b on a.STORE_ID = b.STORE_ID and a.Year = b.Year and a.Month >= b.Month
group by a.Year, a.Month
order by 1, 2

combine two sql queries having each a with clause

I have got two queries who are doing the job for me but I would like to combine them to have the results in one table instead of copy past the results into excel.
First query gives me the number of users whose at least one subscription have expired per month in 2018:
WITH UniqueUsers AS
(
SELECT DISTINCT MONTH(ValidTo) ExpireMonth, UserId
FROM UserInAppPurchase
WHERE YEAR(ValidTo) = 2018
)
SELECT ExpireMonth, COUNT(UserId) UserCount
FROM UniqueUsers
GROUP BY ExpireMonth order by ExpireMonth;
Second query gives me the number of users whose at least made one subscription purchase per month in 2018:
WITH UniqueUsers AS
(
SELECT DISTINCT MONTH(PurchaseDate) PurchaseMonth, UserId
FROM UserInAppPurchase
WHERE YEAR(PurchaseDate) = 2018
)
SELECT PurchaseMonth, COUNT(UserId) UserCount
FROM UniqueUsers
GROUP BY PurchaseMonth order by PurchaseMonth;
Actually the PurchaseMonth and ExpireMonth are the same.
My expected output is: 1st column: months of 2018
2nd column: results from first query
3nd column: results from second query
It is not a big harm to just copy the two results and combine them manually, but I am curious how to do it directly in SQL.
Thanks for the help
I would unpivot the dates and just do aggregation:
SELECT MONTH(dte) as mon,
COUNT(DISTINCT ExpireUserId) as numExpiredUsers,
COUNT(DISTINCT PurchaseUserId) as numPurchaseUsers
FROM UserInAppPurchase uiap CROSS APPLY
(VALUES (ValidTo, UserId, NULL),
(PurchaseDate, NULL, UserId)
) v(dte, ExpireUserId, PurchaseUserId)
WHERE dte >= '2018-01-01' AND dte < '2019-01-01'
GROUP BY MONTH(dte)
ORDER BY MONTH(dte);
No subqueries, explicit JOINs, or CTEs are really needed for this logic.
You can use smth like this:
with months as
(
SELECT MONTH(ValidTo) as month_
FROM UniqueUsers
WHERE YEAR(ValidTo) = 2018
union
SELECT MONTH(PurchaseDate)
FROM UniqueUsers
WHERE YEAR(PurchaseDate) = 2018
),
UniqueUsers AS
(
SELECT MONTH(PurchaseDate) as ExpireMonth,
COUNT(UserId) UserCount
FROM UniqueUsers
WHERE YEAR(ValidTo) = 2018
GROUP BY MONTH(PurchaseDate)
),
UniqueUsers1 AS
(
SELECT MONTH(PurchaseDate) as PurchaseMonth,
COUNT(UserId) UserCount1
FROM UniqueUsers
WHERE YEAR(PurchaseDate) = 2018
GROUP BY MONTH(PurchaseDate)
)
select m.month_,
u.UserCount,
u1.UserCount1
from months m
left join UniqueUsers u
on m.month_ = u.ExpireMonth
left join UniqueUsers1 u1
on m.month_ = u1.PurchaseMonth
order by m.month_;
But I'm not sure your order by clause is what you want. Your months will be sorted as 1, 11, 12. Maybe you wanted another order, I just rewrote your queries in order to combine them.
Here you are, you can join CTEs
;WITH UniqueUsers1
AS (
SELECT DISTINCT MONTH(ValidTo) ExpireMonth
,UserId
FROM UserInAppPurchase
WHERE YEAR(ValidTo) = 2018
)
,UniqueUsers2
AS (
SELECT DISTINCT MONTH(PurchaseDate) PurchaseMonth
,UserId
FROM UserInAppPurchase
WHERE YEAR(PurchaseDate) = 2018
)
SELECT *
FROM (
SELECT ExpireMonth
,COUNT(UserId) UserCount
FROM UniqueUsers1 c1
GROUP BY ExpireMonth
) First
INNER JOIN (
SELECT PurchaseMonth
,COUNT(UserId) UserCount
FROM UniqueUsers2
GROUP BY PurchaseMonth
) Sec ON First.ExpireMonth = Sec.PurchaseMonth
You can store each result in separate temporary table, for example create two temporary tables like this:
CREATE Table #ExpiryDates (ExpireMonth int, TotalUsers int)
CREATE Table #PurchaseDates (PurchaseMonth int, TotalUsers int)
And then store first query result into the temporary table #ExpirayDates
;WITH UniqueUsers AS
(
SELECT DISTINCT MONTH(ValidTo) ExpireMonth, UserId
FROM UserInAppPurchase
WHERE YEAR(ValidTo) = 2018
)
INSERT INTO #ExpiryDates (ExpireMonth, TotalUsers)
SELECT ExpireMonth, COUNT(UserId) UserCount
FROM UniqueUsers
GROUP BY ExpireMonth order by ExpireMonth;
Second Query will be like this:
;WITH UniqueUsers AS
(
SELECT DISTINCT MONTH(PurchaseDate) PurchaseMonth, UserId
FROM UserInAppPurchase
WHERE YEAR(PurchaseDate) = 2018
)
INSERT INTO #PurchaseDates (PurchaseMonth, TotalUsers)
SELECT PurchaseMonth, COUNT(UserId) UserCount
FROM UniqueUsers
GROUP BY PurchaseMonth order by PurchaseMonth;
Finally, compine these into one query
SELECT p.PurchaseMonth MonthNumber, p.TotalUsers As UsersMadePurchases,
e.ExpireMonth, e.TotalUsers As UsersExpiredSubs
FROM #PurchaseDates p
LEFT JOIN #ExpiryDates e
ON e.ExpireMonth = p.PurchaseMonth
Result will be:
MonthNumber UsersMadePurchases ExpireMonth UsersExpiredSubs
4 1 NULL NULL
5 2 5 2
6 2 6 2
7 1 7 1
8 1 8 1
Let me know if this solves your problem.

SQL Editing Year

The query works but only give years 1985 values. How do I add unlimited amount of years (1985-2014)
use baseball;
SELECT CAST(tf.franchname AS CHAR(20)), s.yearID, s.lgid, AVG(s.salary)
FROM salaries s, teams t, teamsfranchises tf
WHERE s.teamID = t.teamID AND
t.franchID = tf.franchID AND
s.yearID = 1985 AND
(s.lgid='AL' OR s.lgid='NL') GROUP BY tf.franchname, s.yearID, s.lgid order BY
s.yearID;
You could just use BETWEEN.
Your where clause should then look like
(s.yearID BETWEEN 1985 AND 2014) and
Alternatively you could use the < and > operators:
(s.yearID >= 1984 and <= 2014)
If, for any reason you don't have a continous range of years (You only want 5 years). IN could also be an option:
s.yearID IN (1984, 1991, 1996, 2001, 2006)
Your query has a condition filtering on the year and s.yearID = 1985, you may want to change it using the keyword BETWEEN or removing it altogether depending of your need.
select cast(tf.franchname as char(20)), s.yearID, s.lgid, avg(s.salary)
from salaries s, teams t, teamsfranchises tf
where s.teamID = t.teamID and
t.franchID = tf.franchID and
(s.yearID between 1985 and 2014 )and
(s.lgid='AL' OR s.lgid='NL')
group by tf.franchname, s.yearID, s.lgid
order by s.yearID;
This is another view, when there is no data and still you want to get the year with zero count. Just check this link
In this you can create a temporary table which return the list of your years ie 1985 to 2015 , then just join with left outer join and see the magic.
I just get yourquery, you can replace with the accepted answer query too.
Declare #Startyear int = 1985
--1st approach to get continues year
;with yearlist as
(
select 1985 as year
union all
select yl.year + 1 as year
from yearlist yl
where yl.year + 1 <= YEAR(GetDate())
)
select year from yearlist order by year desc;
--2nd approach to get continues year
;WITH n(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM n WHERE n < (year(getdate()) -#Startyear)
)
SELECT year(DATEADD( YY, -n, GetDate()))
FROM n ORDER BY n
--take anyone approach and then join with your query
;with yearlist as
(
select 1985 as year
union all
select yl.year + 1 as year
from yearlist yl
where yl.year + 1 <= YEAR(GetDate())
)
select year from yearlist
left join
(
SELECT CAST(tf.franchname AS CHAR(20)), s.yearID, s.lgid, AVG(s.salary)
FROM salaries s, teams t, teamsfranchises tf
WHERE s.teamID = t.teamID AND
t.franchID = tf.franchID AND
s.yearID = 1985 AND
(s.lgid='AL' OR s.lgid='NL') GROUP BY tf.franchname, s.yearID, s.lgid order BY
s.yearID
) yourtable on yourtable.yearID = yearlist.year
order by year desc;

Group a query by every month

I have the following query :
select
(select Sum(Stores) from XYZ where Year = '2013' and Month = '8' )
-
(select Sum(SalesStores) from ABC where Year = '2013' and Month = '8') as difference
Here in the above query Year and Month are also columns of a table.
I would like to know if there is a way to run the same query so that , it is run against every month of the year ?
If there are months without data/rows within XYZ or ABC tables then I would use FULL OUTER JOIN:
SELECT ISNULL(x.[Month], y.[Month]) AS [Month],
ISNULL(x.Sum_Stores, 0) - ISNULL(y.Sum_SalesStores, 0) AS Difference
FROM
(
SELECT [Month], Sum(Stores) AS Sum_Stores
FROM XYZ
WHERE [Year] = '2013'
GROUP BY [Month]
) AS x
FULL OUTER JOIN
(
SELECT [Month], Sum(SalesStores) AS Sum_SalesStores
FROM ABC
WHERE [Year] = '2013'
GROUP BY [Month]
) AS y ON x.[Month] = y.[Month]
;WITH Months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month + 1
FROM Months
where Month < 12
)
SELECT '2013' [Year], m.Month, COALESCE(SUM(Stores), 0) - COALESCE(SUM(SalesStores), 0) [Difference]
FROM months m
LEFT JOIN XYZ x ON m.Month = x.Month
LEFT JOIN ABC a ON a.Month = m.Month
GROUP BY m.Month
You could use GROUP BY in your inner trades, and then run a join, like this:
SELECT left.Month, (left.sum - COALESCE(right.sum, 0)) as difference
FROM (
SELECT Month, SUM(Stores) as sum
FROM XYZ WHERE Year = '2013'
GROUP BY Month
) left
LEFT OUTER JOIN (
SELECT Month, SUM(Stores) as sum
FROM ABC WHERE Year = '2013'
GROUP BY Month
) right ON left.Month = right.Months
Note the use of COALESCE. It lets you preserve the value of the first SUM in case when there are no records for the month in the ABC table.
In the following example uses the UNION ALL operator with CTE
;WITH cte AS
(SELECT SUM(Stores) AS Stores, [Month]
FROM dbo.XYZ
WHERE [Year] = '2013'
GROUP BY [Month]
UNION ALL
SELECT -1.00 * SUM(SalesStores), [Month]
FROM dbo.ABC
WHERE [Year] = '2013'
GROUP BY [Month]
)
SELECT [Month], SUM(Stores) AS Difference
FROM cte
GROUP BY [Month]
Demo on SQLFiddle
;WITH Months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month + 1
FROM Months
where Month < 12
)
SELECT Months. Month ,
(select isnull(Sum(Stores),0) from XYZ where Year = '2013' and Month = Months.Month) - (select isnull(Sum(SalesStores),0) from ABC where Year = '2013' and Month =Months.Month) as difference
FROM Months

SQL Query in CRM Report

A "Case" in CRM has a field called "Status" with four options.
I'm trying to
build a report in CRM that fills a table with every week of the year (each row is a different week), and then counts the number of cases that have each Status option (the columns would be each of the Status options).
The table would look like this
Status 1 Status 2 Status 3
Week 1 3 55 4
Week 2 5 23 5
Week 3 14 11 33
So far I have the following:
SELECT
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM [DB].[dbo].[Contact]
Which gives me the following:
Status 1 Status 2 Status 3
2 43 53
Now I need to somehow split this into 52 rows for the past year and filter these results by date (columns in the Contact table). I'm a bit new to SQL queries and CRM - any help here would be much appreciated.
Here is a SQLFiddle with my progress and sample data: http://sqlfiddle.com/#!2/85b19/1
Sounds like you want to group by a range. The trick is to create a new field that represents each range (for you one per year) and group by that.
Since it also seems like you want an infinite range of dates, marc_s has a good summary for how to do the group by trick with dates in a generic way: SQL group by frequency within a date range
So, let's break this down:
You want to make a report that shows, for each contact, a breakdown, week by week, of the number of cases registered to that contact, which is divided into three columns, one for each StateCode.
If this is the case, then you would need to have 52 date records (or so) for each contact. For calendar like requests, it's always good to have a separate calendar table that lets you query from it. Dan Guzman has a blog entry that creates a useful calendar table which I'll use in the query.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
-- order by first date of week, grouping calendar year to produce week numbers
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar -- created from script
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
----include the below if the data is necessary
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20))
+ ', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber
FROM
CRM.dbo.Contact C
-- use a cartesian join to produce a table list
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber
This is different from the solution Ben linked to because Marc's query only returns weeks where there is a matching value, whereas you may or may not want to see even the weeks where there is no activity.
Once you have your core tables of contacts split out week by week as in the above (or altered for your specific time period), you can simply add a subquery for each StateCode to see the breakdown in columns as in the final query below.
WITH WeekNumbers AS
(
SELECT
FirstDateOfWeek,
WeekNumber = row_number() OVER (PARTITION BY CalendarYear ORDER BY FirstDateOfWeek)
FROM
master.dbo.Calendar
GROUP BY
FirstDateOfWeek,
CalendarYear
), Calendar AS
(
SELECT
WeekNumber =
(
SELECT
WeekNumber
FROM
WeekNumbers WN
WHERE
C.FirstDateOfWeek = WN.FirstDateOfWeek
),
*
FROM
master.dbo.Calendar C
WHERE
CalendarDate BETWEEN '1/1/2012' AND getutcdate()
)
SELECT
C.FullName,
--Cl.WeekNumber,
--Cl.CalendarYear,
--Cl.FirstDateOfWeek,
--Cl.LastDateOfWeek,
'Week: ' + CAST(Cl.WeekNumber AS VARCHAR(20)) +', Year: ' + CAST(Cl.CalendarYear AS VARCHAR(20)) WeekNumber,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Active'
) ActiveCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Resolved'
) ResolvedCases,
(
SELECT
count(*)
FROM
CRM.dbo.Incident I
INNER JOIN CRM.dbo.StringMap SM ON
I.StateCode = SM.AttributeValue
INNER JOIN
(
SELECT
DISTINCT ME.Name,
ME.ObjectTypeCode
FROM
CRM.MetadataSchema.Entity ME
) E ON
SM.ObjectTypeCode = E.ObjectTypeCode
WHERE
I.ModifiedOn >= Cl.FirstDateOfWeek
AND I.ModifiedOn < dateadd(day, 1, Cl.LastDateOfWeek)
AND E.Name = 'incident'
AND SM.AttributeName = 'statecode'
AND SM.LangId = 1033
AND I.CustomerId = C.ContactId
AND SM.Value = 'Canceled'
) CancelledCases
FROM
CRM.dbo.Contact C
CROSS JOIN
(
SELECT
DISTINCT WeekNumber,
CalendarYear,
FirstDateOfWeek,
LastDateOfWeek
FROM
Calendar
) Cl
ORDER BY
C.FullName,
Cl.WeekNumber