How to use SQL to do a group by with different dates? - sql

Lets say I have a table with the following columns:
Qty, INTEGER
SaleDate, DATETIME
I would now like to see this result:
Sold in the last 7 days | Sold in last 14 days
-----------------------------------------------------
10 | 20
I can use a where clause to use between, however how would I get the qty sold for 7 and 14 days?

Filter in the WHERE clause to get days 0 to -14. Then aggregate on days 0 to -7 separately.
SELECT
...,
SUM(CASE WHEN SaleDate >= DATEADD(day, -7, GETDATE()) THEN 1 ELSE 0 END) AS 7days,
COUNT(*) AS 14days
FROM
MyTable
WHERE
SaleDate >= DATEADD(day, -14, GETDATE())
GROUP BY
...

Tested in MS T-SQL 2003
declare #whatever table(
qty int,
saledate datetime
)
insert into #whatever select 1, getdate()
insert into #whatever select 2, dateadd(dd, -1, getdate())
insert into #whatever select 2, dateadd(dd, -2, getdate())
insert into #whatever select 1, dateadd(dd, -3, getdate())
insert into #whatever select 1, dateadd(dd, -4, getdate())
insert into #whatever select 1, dateadd(dd, -5, getdate())
insert into #whatever select 1, dateadd(dd, -6, getdate())
insert into #whatever select 1, dateadd(dd, -7, getdate())
insert into #whatever select 1, dateadd(dd, -8, getdate())
insert into #whatever select 1, dateadd(dd, -9, getdate())
insert into #whatever select 2, dateadd(dd, -10, getdate())
insert into #whatever select 2, dateadd(dd, -11, getdate())
insert into #whatever select 1, dateadd(dd, -15, getdate())
insert into #whatever select 2, dateadd(dd, -16, getdate())
select
qty,
sum(
case
when datediff(dd, saledate, getdate()) between 0 and 7 then 1
else 0
end
) as [Sold in last 7 days],
sum(
case
when datediff(dd, saledate, getdate()) between 0 and 14 then 1
else 0
end
) as [Sold in last 14 days]
from
#whatever
group by
qty
;

select sum(Qty), datediff(w, getdate(), SaleDate) as Period
from table
group by datediff(ww, getdate(), SaleDate)

Related

Add numbers from three different queries together

I am getting average values for different past 3, 6 and 10 months.From following queries
select SUM(GrossAmount)/10 as Averageten
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -10, GETDATE())
select SUM(GrossAmount)/6 as Averagesix
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -6, GETDATE())
select SUM(GrossAmount)/3 as Averagethree
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -3, GETDATE())
I get three different values for example:
1200.22
2300.22
4500
I want to get the average of those three values like this
(1200.22 + 2300.22 + 4500) / 3
How can I add values from these separate queries.
Use union to merge three queries and then do average
select avg(Averageten)
from
(select SUM(GrossAmount)/10 as Averageten
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -10, GETDATE())
union
select SUM(GrossAmount)/6
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -6, GETDATE())
union
select SUM(GrossAmount)/3
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -3, GETDATE())
)a
OR you can use conditional aggregation to find the values as you query is from same table with different condition
select ((sum(case when CreatedDate >= DATEADD(MONTH, -10, GETDATE()) then GrossAmount end)/10)
+(sum(case when CreatedDate >= DATEADD(MONTH, -6, GETDATE()) then GrossAmount end)/6)
+(sum(case when CreatedDate >= DATEADD(MONTH, -3, GETDATE()) then GrossAmount end)/3))/3
from tablename
Use conditional aggregation.
SELECT
(
SUM(CASE WHEN CreatedDate >= DATEADD(MONTH, -10, GETDATE()) THEN GrossAmount END) / 10 +
SUM(CASE WHEN CreatedDate >= DATEADD(MONTH, -6, GETDATE()) THEN GrossAmount END) / 6 +
SUM(CASE WHEN CreatedDate >= DATEADD(MONTH, -3, GETDATE()) THEN GrossAmount END) / 3
) / 3 AS [10, 6, 3 Average]
FROM Table
WHERE CreatedDate >= DATEADD(MONTH, -10, GETDATE()) -- this covers all three cases

SQL Server grouped rows return with default values if no row available for a date period

I'm trying to write a stored procedure which groups up rows based on their month and return a sum of all items if they exist and 0 if they don't.
For the date part of the query, what I am trying to get is today's date - extract the month and go back 5 months to gather any data if it exists.
At this stage, the query runs fine as is but I'm wondering if there's any way to optimise this as it looks like I'm running the same set of data over and over again and also it's hard coded to an extent.
The dataset I am trying to achieve is as follows:
Month TotalAmount TotalCount
-----------------------------------
2017-11 0 0
2017-12 200.00 2
2018-01 300.00 3
2018-02 0 0
2018-03 300.00 3
2018-04 100.00 1
Using the following query below, I was able to achieve what I want but as you can see, it's hard coding back the past 5 months so if I wanted to go back 12 months, I'd have to add in more code.
DECLARE #5MonthAgo date = CAST(DATEADD(MONTH, -5, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -5, GETDATE())) AS DATE)
DECLARE #4MonthAgo date = CAST(DATEADD(MONTH, -4, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -4, GETDATE())) AS DATE)
DECLARE #3MonthAgo date = CAST(DATEADD(MONTH, -3, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -3, GETDATE())) AS DATE)
DECLARE #2MonthAgo date = CAST(DATEADD(MONTH, -2, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -2, GETDATE())) AS DATE)
DECLARE #1MonthAgo date = CAST(DATEADD(MONTH, -1, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -1, GETDATE())) AS DATE)
DECLARE #CurrentMonth date = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)
-- Table to return grouped and sum data
DECLARE #StatsTable TABLE ([Month] DATE,
[Total Amount] DECIMAL(18,2),
[Total Count] INT
)
-- Temporary table to hold onto data batch - so table isn't used later on
DECLARE #TempGenTable TABLE ([Id] INT,
[Date] DATETIME,
[Lines] INT NULL,
[Amount] DECIMAL(18, 2) NULL
)
INSERT INTO #TempGenTable
SELECT
Id, Date, Lines, Amount
FROM
TallyTable
WHERE
Date >= #5MonthAgo
INSERT INTO #StatsTable
SELECT
#5MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
UNION
SELECT
#4MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
...
Is there an easier way to be able to get the above data with more flexibility in the number of months?
Is it better to just have the query pass in a month variable and it checks just the current month and have a loop within the controller to go back x number of months?
I would generate the data using a recursive CTE and then use left join:
with months as (
select datefromparts(year(getdate()), month(getdate()), 1) as month_start, 5 as n
union all
select dateadd(month, -1, month_start), n - 1
from months
where n > 0
)
select m.month_start, count(s.id), sum(s.amount)
from months m left join
#StatsTable s
on m.month_start = s.month
group by m.month_start
order by m.month_start;
You haven't provided sample data, so I'm not sure what s.month looks like. You might want the join condition to be:
on s.month >= m.month_start and s.month < dateadd(month, 1, m.month_start)
Below is a set-based method to generate the needed monthly periods:
--sample data
CREATE TABLE dbo.TallyTable (
Id int
, Date datetime
, Lines int
, Amount decimal(18, 2)
);
INSERT INTO dbo.TallyTable
VALUES
(1, '2017-12-05', 1, 50.00)
,(2, '2017-12-06', 1, 150.00)
,(3, '2018-01-10', 1, 100.00)
,(4, '2018-01-11', 1, 100.00)
,(5, '2018-01-12', 1, 100.00)
,(6, '2018-03-15', 1, 225.00)
,(7, '2018-03-15', 1, 25.00)
,(8, '2018-03-15', 1, 50.00)
,(9, '2018-04-20', 1, 100.00);
GO
DECLARE #Months int = 5; --number of historical months
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t100 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b)
, periods AS (SELECT
CONVERT(varchar(7), DATEADD(month, DATEDIFF(month, '', GETDATE()) - num, ''),121) AS Month
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num, '') AS PeriodStart
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num + 1, '') AS NextPeriodStart
FROM t100
WHERE num <= #Months
)
SELECT periods.Month, COALESCE(SUM(Amount), 0) AS TotalAmount, COALESCE(COUNT(ID), 0) AS TotalCount
FROM periods
LEFT JOIN dbo.TallyTable ON
TallyTable.Date >= PeriodStart
AND TallyTable.Date < NextPeriodStart
GROUP BY periods.Month
ORDER BY periods.Month;

Show Dates that have no values for selected column

I have the following query which counts the number of items created on a particular date in the last 10 days
SELECT
CONVERT (DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688) AS 'Logged Date',
Count (*) AS 'Total'
FROM
MTV_System$WorkItem$Incident
WHERE
CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688 >= DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 10, 0)
GROUP BY
CONVERT(DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688)
How do I get this to show the dates which have no values present (i.e. get every date value for the last 10 days, return the count if there is data or 0 if none). Using SQL Server 2012.
You can write a recursive cte to get the date for the last 10 days into a table as follows:
WITH TableA (StartDate) AS (SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 10, 0)),
q as (
SELECT StartDate
, Number = 0
FROM TableA
UNION ALL
SELECT DATEADD(d,1,StartDate)
, Number = Number + 1
FROM q
WHERE 10 > Number )
Then join q with your original query, to get a row for every date.
select q.StartDate, yourtable.Total from q
left join (
SELECT
CONVERT (DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688) AS 'Logged Date',
Count (*) AS 'Total'
FROM
MTV_System$WorkItem$Incident
WHERE
CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688 >= DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 10, 0)
GROUP BY
CONVERT(DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688)
) as yourtable on [Logged Date] = q.StartDate
Similar to BeanFrog's answer but a little shorter
-- sample data for testing
declare #MTV_System$WorkItemIncident table (
[CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688] DATE,
[Total] INT
);
INSERT INTO #MTV_System$WorkItemIncident VALUES ('2015-11-23', 23);
INSERT INTO #MTV_System$WorkItemIncident VALUES ('2015-11-21', 21);
INSERT INTO #MTV_System$WorkItemIncident VALUES ('2015-11-30', 30);
-- now the query
WITH TableA (LoggedDate) AS (
SELECT TOP 10 CONVERT (DATE, DATEADD(DAY, number * -1, GETDATE())) AS 'LoggedDate'
FROM master.dbo.spt_values
WHERE name IS NULL
)
SELECT TableA.[LoggedDate],
SUM(ISNULL(Data.Total, 0)) AS 'LoggedCount'
FROM TableA
LEFT JOIN #MTV_System$WorkItemIncident AS Data ON CONVERT (DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688) = TableA.[LoggedDate]
GROUP BY TableA.[LoggedDate]
Not that there is anything wrong with BeanFrog's answer, but if you don't want to use a recursive cte you could do this:
CREATE TABLE MTV_System$WorkItem$Incident (id int PRIMARY KEY, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688 datetime)
INSERT INTO MTV_System$WorkItem$Incident VALUES (1, '20151201')
INSERT INTO MTV_System$WorkItem$Incident VALUES (2, '20151126')
INSERT INTO MTV_System$WorkItem$Incident VALUES (3, '20151127')
INSERT INTO MTV_System$WorkItem$Incident VALUES (4, '20151127')
SELECT
ReportDate AS 'Logged Date',
Count (CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688) AS 'Total'
FROM (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 0, 0) AS ReportDate
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 1, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 2, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 3, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 4, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 5, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 6, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 7, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 8, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 9, 0)
UNION SELECT DATEADD(DAY, DATEDIFF(DAY, 0, Getdate()) - 10, 0)
) AS Dates
LEFT JOIN MTV_System$WorkItem$Incident ON ReportDate = CONVERT(DATE, CreatedDate_6258638D_B885_AB3C_E316_D00782B8F688)
GROUP BY ReportDate
BeanFrog's answer has the advantage of being able to easily change the number of days though.

How to get list of 2nd and 4th Saturday dates in SQL Server?

I am almost a newbie to writing SQL queries.
In the context of SQL Server, how to get list of 2nd and 4th Saturday dates
in the year 2016?
Done as a derived table simply to show the logic but you can reduce if you prefer:
select *
from (
select d2016,
datename( weekday, d2016 ) as wkdy,
row_number( ) over ( partition by datepart( month, d2016 ), datename( weekday, d2016 ) order by d2016 ) as rn_dy_mth
from (
select dateadd( day, rn, cast( '2016-01-01' as date ) ) as d2016
from (
select row_number() over( order by object_id ) - 1 as rn
from sys.columns
) as rn
) as dy
) as dy_mth
where rn_dy_mth in ( 2, 4 )
and wkdy = 'Saturday'
order by d2016
--DEFINE LIMITS FOR DAY
DECLARE #TODATE DATETIME, #FROMDATE DATETIME
SET #FROMDATE ='2010-01-01'
SET #TODATE = '2017-12-31'
;WITH DATESEQUENCE( [DATE] ) AS
(
SELECT #FROMDATE AS [DATE]
UNION ALL
SELECT DATEADD(DAY, 1, [DATE])
FROM DATESEQUENCE
WHERE DATE < #TODATE
)
, DATESATURDAY AS
(SELECT CAST(CAST(YEAR([DATE]) AS VARCHAR)+
(CASE WHEN DATEPART(M,[DATE])<=9 THEN '0'+CAST(DATEPART(M,[DATE]) AS VARCHAR)
ELSE CAST(DATEPART(M,[DATE]) AS VARCHAR) END ) AS NUMERIC) AS MONTH_ID
,CONVERT(VARCHAR,[DATE],106) AS DAY_DESC
,UPPER(DATENAME(DW,[DATE]))AS DAY_NAME
FROM DATESEQUENCE )
,SECOND_FOURTH_SATURDAY AS
(SELECT *
,ROW_NUMBER() OVER (PARTITION BY MONTH_ID ORDER BY DAY_NAME) FALL_IN
FROM DATESATURDAY
WHERE DAY_NAME='SATURDAY')
SELECT * FROM SECOND_FOURTH_SATURDAY
WHERE FALL_IN IN(2,4)
OPTION (MAXRECURSION 10000)
You can get any Saturday of a month using the Following Query in SQL.
Here I'm Getting on Current Date, You can set your own selected date to get a Specific month Saturday
select DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0,getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0)) as FirstSaturday,
DATEADD(dd,7,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as SecondSaturday,
DATEADD(dd,14,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as ThirdSaturday,
DATEADD(dd,21,DATEADD(dd, (14 - ##DATEFIRST - DATEPART(dw, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) % 7, DATEADD(MONTH, DATEDIFF(mm, 0, getdate()), 0))) as LastSaturday

SQL Server correct way to account for the weekend

I have a few sql statements where I manually enter the date everyday then execute.
I would like to automate the date part. But weekends are giving me trouble. I use excel to run the reports as it is then saved and another member of staff does a mailshot.
This is my test example:
if (datename(dw, getdate()) = 'Friday') BEGIN
select convert(varchar, dateadd(day, 7, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103)
+ ',' + convert(varchar, dateadd(day, 9, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103) AS [Date] END
ELSE BEGIN select convert(varchar, dateadd(day, 7, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103) as [Date] END;
But how would I put this into a sql where statement?
for example
select * from table where (date = )
where Date equals 7 days in advance unless It's Friday when we have to account for the weekend so we want 7,8 and 9 days in advance?
select * from table where date = (case
when datename(dw, getdate()) = 'Friday' then
convert(varchar, dateadd(day, 7, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103)
+ ',' + convert(varchar, dateadd(day, 9, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103)
else
convert(varchar, dateadd(day, 7, DATEADD(dd, 0, DATEDIFF(dd, 0,getdate()))), 103)
end)