tsql join with dateparts in subqueries - sql

I have two queries with subqueries for dateparts which I like to join.
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsViewed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
The output is of the two queries is:
DateMonth DateYear Datestring NoOfCouponsViewed
----------- ----------- ---------- -----------------
2 2012 Feb 2012 5
3 2012 Mar 2012 12
4 2012 Apr 2012 25
5 2012 May 2012 25
DateMonth DateYear Datestring NoOfCouponsRedeemed
----------- ----------- ---------- -------------------
2 2012 Feb 2012 3
3 2012 Mar 2012 4
4 2012 Apr 2012 5
5 2012 May 2012 11
What I like to achive is two have one joined query giving me:
DateMonth DateYear Datestring NoOfCouponsViewed NoOfCouponsRedeemed
----------- ----------- ---------- ----------------- -------------------
2 2012 Feb 2012 5 3
3 2012 Mar 2012 12 4
4 2012 Apr 2012 25 5
5 2012 May 2012 25 11
How can I do this ?

Make a inner join between the two queries and it should work:
SELECT sub.DateMonth, sub.DateYear, sub.Datestring,
MAX(sub.CouponTotalCount) NoOfCouponsViewed,
MAX(sub2.CouponTotalCount) NoOfCouponsViewed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
INNER JOIN
( SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub2 on sub.DateMonth = sub2.DateMonth and sub.DateYear = sub2.DateYear and sub.Datestring = sub2.Datestring
where sub.couponID=249 and sub2.couponID=249
GROUP BY sub.DateMonth, sub.DateYear, sub.Datestring

or a UNION:
SELECT u.DateMonth, u.DateYear, u.Datestring, MAX(u.NoOfCouponsViewed), MAX(u.NoOfCouponsRedeemed)
FROM (
SELECT DateMonth, DateYear, Datestring,
MAX(CouponTotalCount) NoOfCouponsViewed, 0 AS NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponViewed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
UNION
SELECT DateMonth, DateYear, Datestring, 0 AS NoOfCouponsViewed,
MAX(CouponTotalCount) NoOfCouponsRedeemed
FROM (
SELECT *, DATEPART(MONTH, DateInsert) DateMonth, DATEPART(YEAR, DateInsert) DateYear,
CONVERT(CHAR(4), DateInsert, 100) + CONVERT(CHAR(4), DateInsert, 120) Datestring
FROM FlurryCouponRedeemed
) sub
where couponID=249
GROUP BY DateMonth, DateYear, Datestring
) u
GROUP BY u.DateMonth, u.DateYear, u.Datestring

I would use UNION rather than JOIN
SELECT MonthInserted,
LEFT(DATENAME(MONTH, Datestring), 3) + ' ' + DATENAME(YEAR, MonthInserted) AS DateString
MAX(Viewed) NoOfCouponsViewed,
MAX(Redeemed) NoOfCouponsRedeemed
FROM ( SELECT CouponID,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert)) [MonthInserted],
CouponTotalCount AS Viewed,
0 AS Redeemed
FROM FlurryCouponViewed
UNION ALL
SELECT CouponID,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert)),
0,
CouponTotalCount
FROM FlurryCouponRedeemed
) sub
WHERE couponID = 249
GROUP BY MonthInserted
I think a UNION would perform better than the JOIN in the accepted answer as the MAX implies that there are multiple rows per month, and since month is the only common field so it will end up cross joining queries (i.e. if there are 1000 coupons viewed in june 2012 and 500 are redeemed the cross join means you would be selecting the max from 50,000 rows instead of 1500). I am not certain of the schema and logic so this may not be possible, but if there are dates in FlurryCouponRedeemed that are not in FlurryCouponViewed then these will not show.
I also like to keep dates as dates for as long as possible to help the optimiser do it's job, this is why I've replaced DATEPART(YEAR... & DATEPART(MONTH... AND CONVERT(VARCHAR(4), DateInsert... with DATEADD(MONTH, DATEDIFF(MONTH, 0, DateInsert))

Related

SQL Aggregate records by CASE WHEN

I am trying to aggregate the records' sum depending on the value that match the CASE WHEN
I am able to aggregate the sum but I don't know how I can group the records depending on the description value.
SQL Query:
SELECT
T1.Company, T1.DueDate,
SUM(T1.Amount) AS TotalAmount,
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) AS Age,
CASE
WHEN
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) <= 0
THEN
'Current'
WHEN
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) > 0
AND DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) <= 30
THEN
'Late'
END AS Description
FROM
Table1 T1
LEFT JOIN
Table2 T2
ON T1.Company = T2.Company
AND T1.YR = T2.YR
INNER JOIN
Table3 T3
ON T1.Company = T3.Company
AND T3.YR = YEAR(CURRENT_DATE)
GROUP BY T1.Company,T1.DueDate, T3.FromDate
My query returns the following.
Company | DueDate | TotalAmount | Age | Description
------------+----------------+-------------+------+------------
123 | 20200423 | 150 | 7 | Late
123 | 20200604 | 18000 | -35 | Current
123 | 20200515 | 500 | -15 | Current
However, what I really want to accomplish is to aggregate records if they have same Description value.
So, the correct result should be :
Company | TotalAmount |Description |
------------+--------------+------------+
123 | 150 | Late
123 | 18500 | Current
The 2nd and 3rd row falls under "Current". Therefore, it should combine the 2 rows and add 18000 + 500 = 18500. DueDate and Age column is just for reference. The most important fields are TotalAmount and Description
I tried grouping by the alias Description which is used in CASE-WHEN but I didn't work.
I'd appreciate any help.
Thanks!
You copy-and-paste the CASE statement into the GROUP BY clause:
GROUP BY T1.Company, CASE WHEN ...
which is a lot of code to repeat.
Or you can use a common table expression (CTE):
;WITH
cte AS
(
SELECT
T1.Company, T1.DueDate, T1.Amount
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) AS Age,
CASE
WHEN
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) <= 0
THEN
'Current'
WHEN
DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) > 0
AND DATEDIFF( day, CONVERT(DATETIME, CAST(T1.DueDate AS VARCHAR(8)), 112), DATEADD( day, - 1, CONVERT(DATETIME, CAST(T3.FromDate AS VARCHAR(8)), 112) ) ) <= 30
THEN
'Late'
END AS Description
FROM
Table1 T1
LEFT JOIN
Table2 T2
ON T1.Company = T2.Company
AND T1.YR = T2.YR
INNER JOIN
Table3 T3
ON T1.Company = T3.Company
AND T3.YR = YEAR(CURRENT_DATE)
)
SELECT Company, Description, SUM(Amount) AS TotalAmount
FROM cte
GROUP BY Company, Description

Displaying start date of week in SQL

I have a SQL query that to return the number of items per week. I have a query that returns so far this:
Number of Items | Week Number
-------------------------------
100 | 18
80 | 19
120 | 20
And would like to return the following:
Number of Items | Week Beginning
-------------------------------
100 | 1st May 2017
80 | 8th May 2017
120 | 15th May 2017
What I have so far is:
SELECT COUNT(*) AS 'Number of Items', DATEPART(WEEK, Date) FROM table
where DATEPART(Year, Date) = '2017' and DATEPART(MONTH, Date) = 5
group by DATEPART(WEEK, Date)
You are talking about the 1st day of the current week:
example: select FORMAT(dateadd(ww,datediff(ww,0,getdate()),0),'dd MMM yyyy')--if you are using SQL 2012+
answer:
SELECT COUNT(*) AS 'Number of Items', FORMAT(dateadd(ww,datediff(ww,0,date_column),0),'dd MMM yyyy')
FROM table
where DATEPART(Year, Date) = '2017' and DATEPART(MONTH, Date) = 5
group by DATEPART(WEEK, Date)
As you need Monday to be the first day of the week
select DATEPART(WEEK, MyDate),DATEADD(DAY,1,(DATEADD(DAY, 1-DATEPART(WEEKDAY, MyDate), MyDate)))
from (
select '5/3/2017' MyDate
union all select '5/10/2017'
union all select '5/14/2017')A
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Date) /7*7, 0) AS StartDateOfWeek
check this if it solves
DECLARE #WeekNum INT
, #YearNum char(4);
SELECT #WeekNum = 20
, #YearNum = 2017
-- once you have the #WeekNum and #YearNum set, the following calculates the date range.
SELECT DATEADD(wk, DATEDIFF(wk, 6, '1/1/' + #YearNum) + (#WeekNum-1), 6) AS StartOfWeek;
SELECT DATEADD(wk, DATEDIFF(wk, 5, '1/1/' + #YearNum) + (#WeekNum-1), 5) AS EndOfWeek;
thanks to http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=185440

Cumulative total for quarters

I currently have a query which produces data for each quarter, what I would like is for the total to be a running total. So quarter 2's figures will be added on to quarter 1's and so on until the end of the fiscal year, where the process will then start again.
select datepart(year,date) as Year,
CASE
WHEN MONTH(date) BETWEEN 1 AND 3 THEN convert(char(4), YEAR(date) - 1) + 'Q4'
WHEN MONTH(date) BETWEEN 4 AND 6 THEN convert(char(4), YEAR(date) - 0) + 'Q1'
WHEN MONTH(date) BETWEEN 7 AND 9 THEN convert(char(4), YEAR(date) - 0) + 'Q2'
WHEN MONTH(date) BETWEEN 10 AND 12 THEN convert(char(4), YEAR(date) - 0) + 'Q3'
END AS Quarter,
Data1,
Data2,
FROM TABLE
GROUP BY datepart(year,date),
CASE
WHEN MONTH(date) BETWEEN 1 AND 3 THEN convert(char(4), YEAR(date) - 1) + 'Q4'
WHEN MONTH(date) BETWEEN 4 AND 6 THEN convert(char(4), YEAR(date) - 0) + 'Q1'
WHEN MONTH(date) BETWEEN 7 AND 9 THEN convert(char(4), YEAR(date) - 0) + 'Q2'
WHEN MONTH(date) BETWEEN 10 AND 12 THEN convert(char(4), YEAR(date) - 0) + 'Q3'
END
This is what I currently get
year quarter data1 data2
2016 Q1 10 4
2016 Q2 5 6
2016 Q3 4 2
2017 Q4 1 1
I would like the output to look like this
year quarter data1 data2
2016 Q1 10 4
2016 Q2 15 10
2016 Q3 19 12
2017 Q4 20 13
Thanks for any help
You can wrap your query in a CTE and use CROSS APPLY in order to calculate the cumulative sum:
;WITH CTE AS (
-- your query
)
SELECT t1.year, t1.quarter, t1.data1, t3.cumulative_sum
FROM CTE AS t1
CROSS APPLY (
SELECT SUM(data2) AS cumulative_sum
FROM CTE AS t2
WHERE t2.year <= t1.year AND t2.quarter <= t1.quarter) AS t3
First, your query can be simplified to:
SELECT datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter,
SUM(Data1) as Data1, SUM(Data2) as Data2
FROM TABLE
GROUP BY datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter
ORDER BY quarter;
Note the use of datename(). This returns a string rather than a number, which is easier for string concatenation. In addition, this simplifies the logic just by subtracting 3 months to get the quarter.
In SQL Server 2012+, you can use the built in functionality for cumulative sum. In SQL Server 2008, one method uses apply:
WITH t as (
SELECT datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter,
SUM(Data1) as Data1, SUM(Data2) as Data2
FROM TABLE
GROUP BY datepart(year, date) as Year,
datename(year, date) + 'Q' + datename(quarter, dateadd(month, -3, date)) as quarter
)
SELECT t.*, tt.data2
FROM t OUTER APPLY
(SELECT SUM(data1) as data2
FROM t t2
WHERE t2.quarter <= t.quarter
) tt;

How to get sum by MonthYear in sql?

I have a table like this
id Date Amount
1 2016-09-29 09:25:37.000 25.13
2 2016-08-01 17:20:39.000 598.00
3 2016-09-29 09:24:47.000 15.60
4 2016-07-28 17:50:11.000 61.80
5 2016-07-28 17:53:56.000 31.40
6 2016-07-22 10:40:27.000 74.16
I'm trying to get two columns like this,
MonthYear Total
Sep 2016 40.73
Aug 2016 598.00
Jul 2016 167.36
But, I want to get most recent year and month to top.
Try this sql,
SELECT CONVERT(CHAR(4), Date, 100) + CONVERT(CHAR(4), Date, 120)
AS MonthYear, SUM(Amount)
AS Total,
CAST(CONVERT(varchar(4), Date, 120) AS int)
AS Year, DATEPART(m, Date) As Month
FROM your_table
GROUP BY CONVERT(CHAR(4), Date, 100) + CONVERT(CHAR(4), Date,120),CAST(CONVERT(varchar(4), Date, 120) AS int), DATEPART(m, Date)
ORDER BY Year DESC, Month DESC
Just an other perspective.
Query
SELECT t.[MonthYear], SUM(t.[Amount]) AS [Total] FROM(
SELECT RIGHT((CONVERT(VARCHAR(11), [Date], 106)), 8) as [MonthYear], [Amount]
FROM [your_table_name]
)t
GROUP BY t.[MonthYear];
Try below
SELECT Datename(MONTH, [Date]) month_name,
Year([Date]) year,
Sum([Amount]),
Month([date]) month_no
FROM #Table1
GROUP BY Datename(MONTH, [Date]),
Year([Date]),
Month([date])
ORDER BY Month([date]) DESC,
Year([Date]),
Datename(MONTH, [Date])

Add sub-condition in the where clause

I am not sure what is the proper name to give to this condition in the where clause. I am using Microsoft SQL Server 2008.
Basically, I have an order table with the following columns.
MyOrderTable:
OrderAmount Paid OrderDate
100 Y 2016-08-10 21:15:52.000
120 N 2016-08-10 20:15:12.000
300 Y 2016-08-09 11:10:22.000
500 Y 2016-06-09 10:10:54.000
125 N 2016-06-09 09:15:13.000
325 N 2015-11-09 09:15:13.000
Currently, I have the following SQL query:
SELECT DATEPART(Year, OrderDate) TheYear, DATEPART(Month, OrderDate) TheMonth,
SUM(ISNULL(OrderAmount, 0)) as SumOrderAmount
FROM MyOrderTable
WHERE YEAR(OrderDate) = 2016
GROUP BY DATEPART(Year, OrderDate), DATEPART(Month, OrderDate)
ORDER BY TheYear, TheMonth
The result returned from the above SQL is like the following:
TheYear TheMonth SumOrderAmount
2016 6 625
2016 8 520
How can I query also the SumOrderAmount where the row is Paid='Y'? I want to return the following from the SQL:
TheYear TheMonth SumOrderAmount PaidSumOrderAmount
2016 6 625 500
2016 8 520 400
How can I do that? Any help is greatly appreciated. Thank you.
Use conditional aggregation:
SELECT DATEPART(Year, OrderDate) TheYear, DATEPART(Month, OrderDate) TheMonth,
SUM(ISNULL(OrderAmount, 0)) as SumOrderAmount,
SUM(CASE WHEN Paid = 'Y' THEN OrderAmount ELSE 0 END) as PaidOrderAmount
FROM MyOrderTable
WHERE YEAR(OrderDate) = 2016
GROUP BY DATEPART(Year, OrderDate), DATEPART(Month, OrderDate)
ORDER BY TheYear, TheMonth;
You probably don't need the ISNULL() in SUM(). In SQL, NULL values are ignored in the SUM() aggregation function.
Use a case expression to do a conditional SUM():
SELECT DATEPART(Year, OrderDate) TheYear, DATEPART(Month, OrderDate) TheMonth,
SUM(ISNULL(OrderAmount, 0)) as SumOrderAmount,
SUM(case when Paid='Y' then OrderAmount end)
FROM MyOrderTable
WHERE YEAR(OrderDate) = 2016
GROUP BY DATEPART(Year, OrderDate), DATEPART(Month, OrderDate)
ORDER BY TheYear, TheMonth
Add this clause on select:
SUM(CASE WHEN Paid='Y' THEN OrderAmount ELSE 0 END) AS PaidSumOrderAmount
SELECT DATEPART(Year, OrderDate) TheYear, DATEPART(Month, OrderDate) TheMonth,
SUM(ISNULL(OrderAmount, 0)) as SumOrderAmount,Paid
FROM MyOrderTable
WHERE (YEAR(OrderDate) = 2016) and (Paid = 'Y')
GROUP BY DATEPART(Year, OrderDate), DATEPART(Month, OrderDate)
ORDER BY TheYear, TheMonth