Group by month in 6 month range sql server - sql-server

I have a query that groups data by month, but there are some months that do not display simply because there is no data to display/group by. Is it possible to return the months and 0s for those months?
Here is my query
DECLARE #IMPORT_DATE AS DATETIME
SET #IMPORT_DATE = GETDATE()
SELECT COALESCE(COUNT(*),0), RIGHT(YEAR_MONTH_VALUE,2)
FROM VW_CALLS
WHERE CLIENT_ID = 2
AND START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE
ORDER BY YEAR_MONTH_VALUE
And it returns this:
(No column name) (No column name)
740 11
1929 12
3864 01
But I would like this:
(No column name) (No column name)
0 08
0 09
0 10
740 11
1929 12
3864 01

You can use a recursive CTE like this:
DECLARE #MonthStart CHAR(2) = '08'
;WITH MonthsRange AS (
SELECT m = #MonthStart, rn = 1
UNION ALL
SELECT m = (CASE WHEN m = '12' THEN CAST('01' AS CHAR(2))
ELSE CAST(REPLICATE('0', 2 - LEN(CAST((CAST(m AS INT) + 1) AS CHAR(2)))) +
CAST((CAST(m AS INT) + 1) AS CHAR(2)) AS CHAR(2))
END),
rn = rn + 1
FROM MonthsRange
WHERE rn < 6
)
SELECT *
FROM MonthsRange
in order to get all months you want to include in your final result set:
m rn
=======
08 1
09 2
10 3
11 4
12 5
01 6
Now you can left join the above CTE against you query to get the result set you want:
;WITH MonthsRange AS (
... cte statements here
)
SELECT COALESCE(vw.cnt, 0) AS cnt, mr.m AS [Month]
FROM MonthsRange AS mr
LEFT JOIN (
SELECT COALESCE(COUNT(*),0) AS cnt, RIGHT(YEAR_MONTH_VALUE,2) AS m,
YEAR_MONTH_VALUE AS ym
FROM VW_CALLS
WHERE CLIENT_ID = 2 AND
START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE) AS vw ON mr.m = vw.m
ORDER BY vw.ym

Related

Comparing SQL date ranges across multiple nullable date columns

I have a calendar type check I'm trying to do on SQL Server. For each month of the year, I need to check if the employee was hired or not. There can be an original hire date, a rehire date, a termination date, and the last termination date; other than the original hire date, which will always have a value, all of these date fields can be null.
Given the following data:
EmpID OrigHireDate TermDate LastTermDate RehireDate
42 2017-09-25 NULL 2019-03-26 2019-10-30
What I am trying to achieve is the following result for each month for last year (i.e. 2019) and having no luck in coming up with the right statement. Assume I already have a table containing each month's number along with the start/end date of the month that I can use to compare the date ranges.
EmpID Month EmployeeDuring
42 1 True
42 2 True
42 3 True
42 4 False
42 5 False
42 6 False
42 7 False
42 8 False
42 9 False
42 10 True
42 11 True
42 12 True
The following works. May need some minor adjustments to handle all possible combinations of EmpID, OrigHireDate, TermDate, LastTermDate, RehireDate
I apologize for posting mostly code. Will add more explanation and or comments tomorrow.
DECLARE #EmpID int, #OrigHireDate date, #TermDate date, #LastTermDate date, #RehireDate date
DECLARE #year int
SET #year = 2019
SET #EmpID = 42
SET #OrigHireDate = '2017-09-25'
SET #TermDate = NULL
SET #LastTermDate = '2019-03-26'
SET #RehireDate = '2019-10-30'
SET #OrigHireDate = DATEADD(day,-DAY(#OrigHireDate)+1, #OrigHireDate)
SET #LastTermDate = DATEADD(day,-DAY(ISNULL(#LastTermDate,GETDATE()))+1, #LastTermDate)
SET #RehireDate = DATEADD(day,-DAY(#RehireDate)+1, #RehireDate)
SET #TermDate = DATEADD(day,-DAY(ISNULL(#TermDate,GETDATE()))+1, #TermDate)
;WITH CTE_DATES_ORIGINAL([Date],[Level])
AS
(
SELECT #OrigHireDate AS [DATE],
1 AS [Level]
UNION ALL
SELECT
DATEADD(MONTH,1, [DATE] ) , [Level] + 1
FROM CTE_DATES_ORIGINAL
WHERE [DATE] < ISNULL(#LastTermDate,GETDATE())
),
CTE_DATES_REHIRE([Date],[Level])
AS
(
SELECT #RehireDate AS [DATE],
1 AS [Level]
UNION ALL
SELECT
DATEADD(MONTH,1, [DATE] ) , [Level] + 1
FROM CTE_DATES_REHIRE
WHERE [DATE] < ISNULL(#TermDate,GETDATE())
),
CTE_DATES_YEAR(m) AS
(
SELECT 1
UNION ALL
SELECT m+1
FROM CTE_DATES_YEAR
WHERE m < 12
)
SELECT #EmpID AS EmpID, m AS [Month], ISNULL(EmployeeDuring.EmployeeDuring,0) AS EmployeeDuring
FROM CTE_DATES_YEAR y
LEFT OUTER JOIN
(
SELECT
[Date], 1 AS EmployeeDuring
FROM
CTE_DATES_ORIGINAL
UNION
SELECT
[Date] , 1 AS EmployeeDuring
FROM
CTE_DATES_REHIRE
) employeeDuring
ON DATEADD(month,m-1, CAST(CAST(#year AS CHAR(4)) + '-1-1' AS DATE)) = employeeDuring.[Date]
ORDER BY m
OPTION (MAXRECURSION 5000)

How to insert "empty" row extracting a month list?

I've this sp, which return a list of data, for each "month" (i.e. each row is a month). Somethings like that:
SELECT
*,
(CAST(t1.NumActivities AS DECIMAL) / t1.NumVisits) * 100 AS PercAccepted,
(CAST(t1.Accepted AS DECIMAL) / t1.Estimated) * 100 AS PercValue
FROM
(SELECT
MONTH(DateVisit) AS Month,
COUNT(*) AS NumVisits,
SUM(CASE WHEN DateActivity is not null THEN 1 ELSE 0 END) AS NumActivities,
SUM(Estimate) AS Estimated,
SUM(CASE WHEN DateActivity is not null THEN Estimate ELSE 0 END) AS Accepted
FROM [dbo].[Activities]
WHERE
DateVisit IS NOT NULL
AND (#year IS NULL OR YEAR(DateVisit) = #year)
AND (#clinicID IS NULL OR ClinicID = #clinicID)
GROUP BY MONTH(DateVisit)) t1
This is a result:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
8 1 1 3000.00 3000.00 100.00000000000 100.00000000000000
But as you can see, I could "miss" some Month (for example, here April "4" is missed).
Is it possible to insert, for the missing month/row, an empty (0) record? Such as:
Month NumVisits NumActivities Estimated Accepted PercAccepted PercValue
1 5 1 13770.00 2520.00 20.00000000000 18.30065359477124
2 2 2 7900.00 7900.00 100.00000000000 100.00000000000000
3 1 0 2730.00 0.00 0.00000000000 0.00000000000000
4 0 0 0 0 0 0
...
Here is a example with sample data:
CREATE TABLE #Report
(
Id INT,
Name nvarchar(max),
Percentage float
)
INSERT INTO #Report VALUES (1,'ONE',2.01)
INSERT INTO #Report VALUES (2,'TWO',3.01)
INSERT INTO #Report VALUES (5,'Five',5.01)
;WITH months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month+1
FROM months
WHERE Month < 12
)
SELECT *
INTO #AllMonthsNumber
from months;
Your select query:
The left join will gives you the NULL for other months so just use ISNULL('ColumnName','String_to_replace')
\/\/\/\/
SELECT Month, ISNULL(Name,0), ISNULL(Percentage,0)
FROM AllMonthsNumber A
LEFT JOIN #Report B
ON A.Month = B.Id
EDIT:
Yes you can do it without creating AllMonthNumber Table:
You can use master..spt_values (found here) system table which contains the numbers so just with some where condition.
SELECT Number as Month, ISNULL(B.Name,0), ISNULL(Percentage,0)
FROM master..spt_values A
LEFT JOIN #Report B ON A.Number = B.Id
WHERE Type = 'P' AND number BETWEEN 1 AND 12

Accumulative Update for previous records

I have table that shows these information
Month NewClients OnHoldClients
5-2017 10 2
6-2017 16 4
7-2017 11 1
8-2017 15 6
9-2017 18 7
I am trying to find the accumulative total for each month
which is
(NewClients - OnHoldClients) + Previous Month Total
Something like this
Month NewClients OnHoldClients Total
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50
the query i tried to build was something like this but I think should be an easier way to do that
UPDATE MyTable
SET Total = (SELECT TOP 1 Total FROM MyTable B WHERE B.Month < A.Month) + NewClients - OnHoldClients
FROM MyTable A
Before we begin, note the mere fact that you're facing such calculative problem is a symptom that maybe you don't have the best possible design. Normally for this purpose calculated values are being stored along the way as the records are inserted. So i'd say you'd better have a total field to begin with and calculate it as records amass.
Now let's get down to the problem at hand. i composed a query which does that nicely but it's a bit verbose due to recursive nature of the problem. However, it yields the exact expected result:
DECLARE #dmin AS date = (SELECT min(mt.[Month]) from dbo.MyTable mt);
;WITH cte(_Month, _Total) AS (
SELECT mt.[Month] AS _Month, (mt.NewClients - mt.OnHoldClients) AS _Total
FROM dbo.MyTable mt
WHERE mt.[Month] = #dmin
UNION ALL
SELECT mt.[Month] AS _Month, ((mt.NewClients - mt.OnHoldClients) + ccc._Total) AS _Total
FROM dbo.MyTable mt
CROSS APPLY (SELECT cc._Total FROM (SELECT c._Total,
CAST((row_number() OVER (ORDER BY c._Month DESC)) AS int) as _Rank
FROM cte c WHERE c._Month < mt.[Month]) as cc
WHERE cc._Rank = 1) AS ccc
WHERE mt.[Month] > #dmin
)
SELECT c._Month, max(c._Total) AS Total
FROM cte c
GROUP BY c._Month
It is a recursive CTE structure that goes about each record all along the way to the initial month and adds up to the final Total value. This query only includes Month and Total fields but you can easily add the other 2 to the list of projection.
Try this
;WITH CTE([Month],NewClients,OnHoldClients)
AS
(
SELECT '5-2017',10,2 UNION ALL
SELECT '6-2017',16,4 UNION ALL
SELECT '7-2017',11,1 UNION ALL
SELECT '8-2017',15,6 UNION ALL
SELECT '9-2017',18,7
)
SELECT [Month],
NewClients,
OnHoldClients,
SUM(MonthTotal)OVER( ORDER BY [Month]) AS Total
FROM
(
SELECT [Month],
NewClients,
OnHoldClients,
SUM(NewClients-OnHoldClients)OVER(PArtition by [Month] Order by [Month]) AS MonthTotal
FROM CTE
)dt
Result,Demo:http://rextester.com/DKLG54359
Month NewClients OnHoldClients Total
--------------------------------------------
5-2017 10 2 8
6-2017 16 4 20
7-2017 11 1 30
8-2017 15 6 39
9-2017 18 7 50

SQL, using Group by until specific trend (increment, decrement, same)

I would like to know how can i modify my code for considering all the same values of suppose 10 as UP till the time it is incrementing and then down for decrement and SAME if there is no change till the time there is no variation in the value (increment, decrement, same).
Here is my code :
;with etape1 as
(
select ROW_NUMBER() OVER(ORDER BY mnth) AS id,* from [InsideTSQL2008].[alioune].[Sales]
)
,
etape2 as
(
select
a.id, b.mnth AS START , a.mnth AS FINISH ,
a.qty - b.qty AS TREND
FROM
etape1 a
LEFT JOIN etape1 b
on a.id = b.id+1
)
select * from etape2;
My Result is :
id START FINISH TREND
1 NULL 2007-12-01 NULL
2 2007-12-01 2008-01-01 10
3 2008-01-01 2008-02-01 10
4 2008-02-01 2008-03-01 10
5 2008-03-01 2008-04-01 10
6 2008-04-01 2008-05-01 0
7 2008-05-01 2008-06-01 -10
8 2008-06-01 2008-07-01 -10
9 2008-07-01 2008-08-01 -10
10 2008-08-01 2008-09-01 -10
11 2008-09-01 2008-10-01 10
12 2008-10-01 2008-11-01 -10
13 2008-11-01 2008-12-01 20
14 2008-12-01 2009-01-01 10
15 2009-01-01 2009-02-01 10
16 2009-02-01 2009-03-01 -40
My final result as required should be like :
Start End Trend
200712 200712 unknown
200801 200804 UP
200805 200805 SAME
200806 200809 DOWN
200810 200810 UP
200811 200811 DOWN
200812 200812 UP
200903 200903 DOWN
200904 200905 SAME
200906 200907 UP
Any help would be really helpful; Thanks
Took me a few goes (and a few hours), but I think I have what you want:
DECLARE #Sales AS TABLE (mnth datetime, qty int)
INSERT INTO #Sales
SELECT '2016-01-01', 10 UNION ALL
SELECT '2016-02-01', 20 UNION ALL
SELECT '2016-03-01', 30 UNION ALL
SELECT '2016-04-01', 40 UNION ALL
SELECT '2016-05-01', 40 UNION ALL
SELECT '2016-06-01', 30 UNION ALL
SELECT '2016-07-01', 20 UNION ALL
SELECT '2016-08-01', 30 UNION ALL
SELECT '2016-09-01', 40 UNION ALL
SELECT '2016-10-01', 45 UNION ALL
SELECT '2016-11-01', 50
;WITH etape1 AS (
SELECT ROW_NUMBER() OVER(ORDER BY mnth) AS id, * FROM #Sales
)
, etape2 AS (
SELECT id, lag(mnth) OVER (ORDER BY id) AS START, mnth AS FINISH, CASE WHEN qty - LAG(qty) OVER (ORDER BY id) < 0 THEN -1 WHEN qty - LAG(qty) OVER (ORDER BY id) > 0 THEN 1 ELSE 0 END AS TREND
FROM etape1
)
, etape3 AS (
SELECT id, START, FINISH, TREND, lag(TREND) OVER (ORDER BY id) AS PrevTrend
FROM etape2
)
, etape4 AS (
SELECT id, START, FINISH, TREND, SUM(CASE WHEN TREND = PREVTREND THEN 0 ELSE 1 END) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS Change
FROM etape3
)
SELECT MIN(START) AS START, MAX(FINISH) AS FINISH, CASE WHEN MIN(TREND) IS NULL THEN 'Unknown' WHEN MIN(TREND) < 0 THEN 'Down' WHEN MIN(TREND) > 0 THEN 'Up' WHEN MIN(Start) is NULL THEN 'Unknown' ELSE 'Same' END AS TREND
FROM etape4
GROUP BY Change
ORDER BY START
Results are:
START FINISH TREND
NULL 2016-01-01 Unknown
2016-01-01 2016-04-01 Up
2016-04-01 2016-05-01 Same
2016-05-01 2016-07-01 Down
2016-07-01 2016-11-01 Up

Find the n highest consecutive values in a set of rows

I have some data in a table as follows:
FileDate SumAmount
20150401 90.99
20150401 313
20150403 481.2
20150404 321.27
20150405 103
20150406 25
20150407 180.5
20150408 319.91
20150409 688
20150411 69
20150412 65
20150413 322
20150414 100
20150415 111.97
20150416 979.15
20150417 655.4
20150418 124
20150419 30
20150420 457
20150421 192.6
20150422 191.96
20150423 220
20150424 252.5
20150425 109.1
20150426 135.25
20150427 648.08
20150428 692
20150429 410.99
20150430 170
20150501 166.19
20150502 92
20150503 100
20150504 59
20150505 124.01
20150506 44.5
20150507 331.64
20150508 299.8
I am trying to devise a query that will find the highest 4 consecutive days values in the data.
Essentially, I think I need to partition by date and perform a row numbering over it but I can't seem to get the syntax right to evaluate the values.
So I use -3 in the join conditions since the day itself counts as one. Let me know what you think. Also I use day of year(DY) to ensure that it's only consecutive days and so I don't have to rank the dates manually. Hope this helps!
DECLARE #yourTable TABLE(FileDate DATE ,SumAmount FLOAT);
INSERT INTO #yourTable
VALUES ('20150401',90.99),
('20150402',313),
('20150403',481.2),
('20150404',321.27),
('20150405',103),
('20150406',25),
('20150407',180.5),
('20150408',319.91),
('20150409',688),
('20150411',69),
('20150412',65),
('20150413',322),
('20150414',100),
('20150415',111.97),
('20150416',979.15),
('20150417',655.4),
('20150418',124),
('20150419',30),
('20150420',457),
('20150421',192.6),
('20150422',191.96),
('20150423',220),
('20150424',252.5),
('20150425',109.1),
('20150426',135.25),
('20150427',648.08),
('20150428',692),
('20150429',410.99),
('20150430',170),
('20150501',166.19),
('20150502',92),
('20150503',100),
('20150504',59),
('20150505',124.01),
('20150506',44.5),
('20150507',331.64),
('20150508',299.8);
WITH CTE
AS
(
SELECT YEAR(FileDate) yr,DATEPART(DY,FileDate) dy,fileDate,SumAmount
FROM #yourTable
),
CTE_Max_Sum
AS
(
SELECT TOP 1 A.yr,A.dy,A.FileDate,SUM(B.SumAmount) consec4DaySum
FROM CTE A
INNER JOIN CTE B
ON B.dy BETWEEN A.dy - 3 AND A.dy
AND A.yr = B.yr
GROUP BY A.yr,A.dy,A.FileDate
ORDER BY SUM(B.SumAmount) DESC
)
SELECT A.*,B.consec4DaySum
FROM CTE A
INNER JOIN CTE_Max_Sum B
ON A.dy BETWEEN B.dy - 3 AND B.dy
AND A.yr = B.yr
Results:
yr dy fileDate SumAmount consec4DaySum
----------- ----------- ---------- ---------------------- ----------------------
2015 117 2015-04-27 648.08 1921.07
2015 118 2015-04-28 692 1921.07
2015 119 2015-04-29 410.99 1921.07
2015 120 2015-04-30 170 1921.07
You can use a CTE for that, joining every row with its three following rows (day-wise) and summing up. This Fiddle sadly does not work for me, it runs on my sql server and work for you. Watch out for recursion depth, without WHERE cte.Consecutive < 4 you quickly run into an error.
WITH cte (StartDate, EndDate, Consecutive, SumAmount)
AS (
SELECT t.FileDate, t.FileDate, 1, t.SumAmount FROM dbo.table30194903 t
UNION ALL
SELECT cte.StartDate, t.FileDate, cte.Consecutive + 1, cte.SumAmount + t.SumAmount
FROM dbo.table30194903 t INNER JOIN cte ON DATEADD(DAY, 1, cte.EndDate) = t.FileDate
WHERE cte.Consecutive < 5
)
SELECT *
FROM cte
WHERE cte.Consecutive = 4
ORDER BY cte.SumAmount DESC
EDIT: Had two errors in my query, it summed up wrong rows and showd the last day in the series.
I would like to add an answer using a subquery, however it does take more time compared to my cte...
SELECT t.FileDate, SUM(s.SumAmount)
FROM dbo.table30194903 t
LEFT JOIN dbo.table30194903 s ON t.FileDate <= s.FileDate AND DATEDIFF(DAY, t.FileDate, s.FileDate) < 4
GROUP BY t.FileDate
HAVING COUNT(s.SumAmount) = 4
ORDER BY SUM(s.SumAmount) DESC
I think the simplest way to get this is to use an APPLY to get the number of records in the n days following each row, and then limit this to where there are n dates, this ensures you have consecutive days. You can then just order by the sum and select the top 1:
DECLARE #n INT = 4;
SELECT TOP 1
FirstDate = t.FileDate,
FourDaySum = t2.Amount
FROM dbo.T
CROSS APPLY
( SELECT Amount = SUM(t2.SumAmount),
Dates = COUNT(DISTINCT t2.FileDate)
FROM dbo.T AS t2
WHERE t2.FileDate >= t.FileDate
AND t2.FileDate < DATEADD(DAY, #n, t.FileDate)
) AS t2
WHERE t2.Dates = #n
ORDER BY t2.Amount DESC;
Example on SQL Fiddle
How about a simply while block and sum the values of a range of dates?
DECLARE #startingDate DATETIME, #searchDate DATETIME;
DECLARE #maxSoFar INT, #sum INT, #daysRange INT;
SET #startingDate = convert(datetime, '20150401', 110)
SET #searchDate = #startingDate;
SET #daysRange = 3;
SET #maxSoFar = 0;
WHILE GETDATE()> #searchDate
BEGIN
--PRINT #searchDate
--PRINT DATEADD(DAY,#daysRange,#searchDate)
SELECT #sum = SUM(SumAmount) FROM MyTable WHERE FileDate >= #searchDate AND FileDate <= DATEADD(DAY,#daysRange,#searchDate)
IF #sum > #maxSoFar
BEGIN
SET #maxSoFar = #sum;
END
SET #searchDate = DATEADD(DAY,1,#searchDate)
END

Resources