Max consecutive time_periods by date and hour - sql-server

For a particular exercise I am required to find and locate the maximum consecutive/continuous date and time range. The time_period is given in a format YYYY_MM_DD_hr HH that I converted into YYYY-MM-DD-HH:00:00:000 to enable addition and subtraction operations. The data is a forecast that runs from 2014-2039 and I believe I need to basically 'flag' beginning and end of consecutive periods, ie. 2014-01-01-01:00:00:000 to 2014-01-02:00:00:000 would return a value of '1'. I am using Microsoft SQL Server Management Studio 2012.
So far I have something like:
SELECT r1.Report_Day ,
r1.Report_Hour ,
r1.report_month ,
r1.report_year ,
DATEADD(HOUR, r1.report_hour, CAST(CAST(r1.report_year AS VARCHAR) + '-' + CAST(r1.report_month AS VARCHAR) + '-' + CAST(r1.report_day AS VARCHAR) AS DATETIME)) AS 'NEWDATETIME'

Lets make up some test date ranges:
DECLARE #DateRangeTable TABLE
(
StartDate DATETIME,
EndDate DATETIME
);
INSERT INTO #DateRangeTable
VALUES
( '01-01-2010', '01-31-2010 23:59:00' ),
( '02-01-2010', '02-28-2010 23:59:00' ),
( '03-01-2010', '03-31-2010 23:59:00' ),
( '05-01-2010', '05-31-2010 23:59:00' ),
( '06-01-2010', '06-30-2010 23:59:00' ),
( '01-01-2011', '01-31-2011 23:59:00' ),
( '02-01-2011', '02-28-2011 23:59:00' ),
( '03-01-2011', '03-31-2011 23:59:00' ),
( '04-01-2011', '04-30-2011 23:59:00' ),
( '05-01-2011', '05-31-2011 23:59:00' ),
( '06-01-2011', '06-30-2011 23:59:00' ),
( '01-01-2012', '01-31-2012 23:59:00' ),
( '02-01-2012', '02-28-2012 23:59:00' ),
( '03-01-2012', '03-31-2012 23:59:00' ),
( '04-01-2012', '04-30-2012 23:59:00' ),
( '05-01-2012', '05-31-2012 23:59:00' );
Setup to start searching for our periods
DECLARE #StartDate DATETIME;
SELECT #StartDate = MIN(StartDate) FROM #DateRangeTable;
This part creates a numbers table for all the hours of our range
DECLARE #number_of_numbers INT = 100000;
;WITH
a AS (SELECT 1 AS i UNION ALL SELECT 1),
b AS (SELECT 1 AS i FROM a AS x, a AS y),
c AS (SELECT 1 AS i FROM b AS x, b AS y),
d AS (SELECT 1 AS i FROM c AS x, c AS y),
e AS (SELECT 1 AS i FROM d AS x, d AS y),
f AS (SELECT 1 AS i FROM e AS x, e AS y),
numbers AS
(
SELECT TOP(#number_of_numbers)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS number
FROM f
),
dt as
(
SELECT dt.CheckHour
FROM
(
SELECT
DATEADD(HOUR, n.number, #StartDate) CheckHour
FROM numbers n
) dt
INNER JOIN #DateRangeTable drt
ON dt.CheckHour >= drt.StartDate AND dt.CheckHour <= drt.EndDate
),
t as
(
SELECT
dt.CheckHour,
ROW_NUMBER() OVER(ORDER BY dt.CheckHour) i
FROM dt
)
We have a couple of CTE expressions above, numbers is numbers table, dt is a list of all the hours based on the date range table, t is just the hours with a rownumber to check against.
SELECT
d.PeriodStart,
d.PeriodEnd,
DATEDIFF(HOUR, d.PeriodStart, d.PeriodEnd) NumOfHours
FROM
(
SELECT
MIN(t.CheckHour) PeriodStart,
MAX(t.CheckHour) PeriodEnd
FROM t
GROUP BY DATEDIFF(HOUR, DATEADD(HOUR, i, 0), t.CheckHour)
) d
So the real code is I first get the difference between the hours - the rownumber to group my records into contiguous ranges and then I get the min and max to get the beginning and end and then just diff the hours to get the longest range based on hours, here is the output:
PeriodStart PeriodEnd NumOfHours
2010-01-01 01:00:00.000 2010-03-31 23:00:00.000 2158
2010-05-01 00:00:00.000 2010-06-30 23:00:00.000 1463
2011-01-01 00:00:00.000 2011-06-30 23:00:00.000 4343
2012-01-01 00:00:00.000 2012-02-28 23:00:00.000 1415
2012-03-01 00:00:00.000 2012-05-31 00:00:00.000 2184

The requirement is not very clear but it seems that something on this lines will work:
select newdatetime, newdatetime -(select max(newdatetime)
from yourtable b where b.newdatetime<a.newdatetime) As Period
from yourtable a
order by newdatetime

Ok. Assuming that you created the NEWDATETIME column and has the complete datetime value as mentioned in the question, try this:
SELECT CONVERT(float, r1_1.NEWDATETIME - MAX(r1_2.NEWDATETIME)) * 24
FROM r1 r1_1
INNER JOIN r1 r1_2 ON r1_2.NEWDATETIME < r1_1.NEWDATETIME
GROUP BY r1_1.NEWDATETIME
This gives you all the differences in Hours. To find the maximum value of all:
SELECT MAX(hoursDiff) FROM (
SELECT CONVERT(float, r1_1.NEWDATETIME - MAX(r1_2.NEWDATETIME)) * 24 hoursDiff
FROM r1 r1_1
INNER JOIN r1 r1_2 ON r1_2.NEWDATETIME < r1_1.NEWDATETIME
GROUP BY r1_1.NEWDATETIME) dt

Related

SQL Server: Use OVER / Partition to get records by the minute ( or a time interval)

I am trying to get the records grouped by the minute they were running in. In example below, I have 2 events a01 and a02.
I would like to get the following
min 10:34 - a01
min 10:35 - a01
min 10:36 - a01
min 10:36 - a02
...
min 10:38 - a01
min 10:38 - a02
min 10:39 - a02
So, I am currently using a minute as the time interval. Can you please point me to some examples for this.
Create SQL below:
CREATE TABLE test_t1 (
t1 VARCHAR(150)
,StartTime DATETIME NULL
,EndTime DATETIME NULL
);
INSERT INTO test_t1 (
t1
,StartTime
,EndTime
)
VALUES (
'a01'
,convert(DATETIME, '20180101 10:34:09.630')
,convert(DATETIME, '20180101 10:38:09.630')
);
INSERT INTO test_t1 (
t1
,StartTime
,EndTime
)
VALUES (
'a02'
,convert(DATETIME, '20180101 10:36:09.630')
,convert(DATETIME, '20180101 10:39:09.630')
);
Recursive CTE can be used to solve this kind of problems
with cte as (
select
t1, StartTime = DATEADD(MINUTE, DATEDIFF(MINUTE, 0, StartTime), 0)
, EndTime = DATEADD(MINUTE, DATEDIFF(MINUTE, 0, EndTime), 0)
from
test_t1
)
, rcte as (
select
t1, StartTime, EndTime, convert(char(5), StartTime, 108) res
from
cte
union all
select
t1, dateadd(mi, 1, StartTime), EndTime, convert(char(5), dateadd(mi, 1, StartTime), 108)
from
rcte
where
StartTime < EndTime
)
select
t1, res
from
rcte
order by t1
option (maxrecursion 0)
You need a Tally Table for this.
DECLARE
#minDateTime AS DATETIME,
#maxDateTime AS DATETIME;
SELECT
#minDateTime = MIN(StartTime),
#maxDateTime = MAX(EndTime)
FROM test_t1;
DECLARE #Range AS INT = DATEDIFF(MINUTE, #minDateTime, #maxDateTime);
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
CteTally(N) AS(
SELECT TOP(#Range) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E8
)
SELECT
tt.t1,
MinInterval = DATEADD(MINUTE, ct.N - 1, tt.StartTime)
FROM test_t1 tt
INNER JOIN CteTally ct
ON DATEADD(MINUTE, ct.N - 1, tt.StartTime) <= tt.EndTime
ORDER BY
tt.t1, MinInterval;
Brief explanation of the Tally Table query taken from the article:
Selecting N rows in SQL Server
ONLINE DEMO

Group and count by 16 hours time interval not working

I am trying to get the number of records for a 16 hour time interval. Below is the code that I am using now.
;With Cte_hours as ( --hours generation
Select top(6) hr = (Row_number() over (order by (Select NULL))-1)*4 from master..spt_values
), cte2 as ( --getting range
Select DateAdd(HH, c.hr, Convert(datetime,d.dts) ) as Dts_Start, DateAdd(MS, -2, DateAdd(HH, c.hr+ 4, Convert(datetime,d.dts) ) ) Dts_end
from (select distinct convert(date, dt) as dts from TEST2 ) d
cross apply Cte_hours c
) --actual query
Select c2.Dts_Start as DT, Sum(case when t.Dt is not null then 1 else 0 end) No_of_records,LD_VOY_N,LD_VSL_M
from cte2 c2
Left Join TEST2 t
on t.Dt between c2.Dts_Start and c2.Dts_end
group by c2.Dts_Start,LD_VOY_N,LD_VSL_M
order by LD_VOY_N, LD_VSL_M, Dts_Start ASC
This code is able to count the number of records I have based on a 4,6, and 12 hour interval. However, if I try to count based on a 16 hour interval, it somehow does not work. Below is my code and output that I used for the 16 hour interval.
;With Cte_hours as ( --hours generation
Select top(6) hr = (Row_number() over (order by (Select NULL))-1)*16 from master..spt_values
), cte2 as ( --getting range
Select DateAdd(HH, c.hr, Convert(datetime,d.dts) ) as Dts_Start, DateAdd(MS, -2, DateAdd(HH, c.hr+ 16, Convert(datetime,d.dts) ) ) Dts_end
from (select distinct convert(date, dt) as dts from TEST2 ) d
cross apply Cte_hours c
) --actual query
Select c2.Dts_Start as DT, Sum(case when t.Dt is not null then 1 else 0 end) No_of_records,LD_VOY_N,LD_VSL_M
from cte2 c2
Left Join TEST2 t
on t.Dt between c2.Dts_Start and c2.Dts_end
group by c2.Dts_Start,LD_VOY_N,LD_VSL_M
order by LD_VOY_N, LD_VSL_M, Dts_Start ASC
Result:
DT No_of_records LD_VOY_N LD_VSL_M
2017-05-05 16:00:00.000 14 0002W pqo emzmnwp
2017-05-06 00:00:00.000 14 0002W pqo emzmnwp
2017-05-06 08:00:00.000 12 0002W pqo emzmnwp
2017-05-06 16:00:00.000 12 0002W pqo emzmnwp
2017-05-01 16:00:00.000 1 0007E omq ynzmeoyn
2017-05-02 00:00:00.000 1 0007E omq ynzmeoyn
It is taking the 8 hour timing as well. Do any of you have any idea why?

TSQL - Groups and Islands dates

I need a help on writing an optimal query for the below problem. Have attached the query I have with me but it is highly utilizing resources.
Below is the code to achieve above said logic. Please suggest some optimal way to achieve the same
-- drop table #me
create table #ME (memid int , EffectiveDate datetime , termdate datetime)
Insert into #ME values ('123','3-Dec-16','10-Jan-17')
Insert into #ME values ('123','11-Jan-17','6-Feb-17')
Insert into #ME values ('123','7-Feb-17','5-Mar-17')
Insert into #ME values ('123','8-Mar-17','15-Apr-17')
Insert into #ME values ('123','16-Apr-17','24-May-17')
--drop table #dim
select * from #ME
declare #StartDate datetime , #CutoffDate datetime
select #StartDate= min(effectivedate),#CutoffDate = max(termdate) From #me where termdate<>'9999-12-31 00:00:00.000'
SELECT d
into #dim
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
-- on my system this would support > 5 million days
ORDER BY s1.[object_id]
) AS x
) AS y;
--drop table #MemEligibilityDateSpread
select MemID, D As DateSpread Into #MemEligibilityDateSpread From #Dim dim JOIN #me ME on dim.d between ME.effectivedate and me.termdate
--drop table #DateClasified
WITH CTE AS
(
SELECT MEmID,
UniqueDate = DateSpread,
DateGroup = DATEADD(dd, - ROW_NUMBER() OVER (PARTITION BY Memid ORDER BY Memid,DateSpread), DateSpread)
FROM #MemEligibilityDateSpread
GROUP BY Memid,DateSpread
)
--===== Now, if we find the MIN and MAX date for each DateGroup, we'll have the
-- Start and End dates of each group of contiguous daes. While we're at it,
-- we can also figure out how many days are in each range of days.
SELECT Memid,
StartDate = MIN(UniqueDate),
EndDate = MAX(UniqueDate)
INTO #DateClasified
FROM cte
GROUP BY Memid,DateGroup
ORDER BY Memid,StartDate
select ME.MemID,ME.EffectiveDate,ME.TermDate,DC.StartDate,DC.EndDate from #DateClasified dc join #me ME ON Me.MemID = dc.MemID
and (ME.EffectiveDate BETWEEN DC.StartDate AND DC.EndDate
OR ME.TermDate BETWEEN DC.StartDate AND DC.EndDate)
In cte0 and cte1, we create an ad-hoc tally/calendar table. Once we have that, it is a small matter to calculate and group by Island.
Currently, the tally is has a max of 10,000 days (27 years), but you can easily expand the tally table by adding , cte0 N5
;with cte0(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
,cte1(R,D) as (Select Row_Number() over (Order By (Select Null))
,DateAdd(DD,-1+Row_Number() over (Order By (Select Null)),(Select MinDate=min(EffectiveDate) From #ME))
From cte0 N1, cte0 N2, cte0 N3, cte0 N4)
Select MemID
,EffectiveDate
,TermDate
,SinceFrom = Min(EffectiveDate) over (Partition By Island)
,Tildate = Max(TermDate) over (Partition By Island)
From (
Select *,Island = R - Row_Number() over (Partition By MemID Order by TermDate)
From #ME A
Join cte1 B on D Between EffectiveDate and TermDate
) A
Group By MemID,Island,EffectiveDate,TermDate
Order By 1,2
Returns
MemID EffectiveDate TermDate SinceFrom Tildate
123 2016-12-03 2017-01-10 2016-12-03 2017-03-05
123 2017-01-11 2017-02-06 2016-12-03 2017-03-05
123 2017-02-07 2017-03-05 2016-12-03 2017-03-05
123 2017-03-08 2017-04-15 2017-03-08 2017-05-24
123 2017-04-16 2017-05-24 2017-03-08 2017-05-24
Edit - Now if you want a compressed dataset
Select MemID
,EffectiveDate = Min(EffectiveDate)
,TermDate = Max(TermDate)
From (
Select *,Island = R - Row_Number() over (Partition By MemID Order by TermDate)
From #ME A
Join cte1 B on D Between EffectiveDate and TermDate
) A
Group By MemID,Island
Order By 1,2
Returns
MemID EffectiveDate TermDate
123 2016-12-03 2017-03-05
123 2017-03-08 2017-05-24

TSQL loop months in sequence

I have an query that I'm feeling out-of-my depth with.
I need to loop through months between two dates and return a subset of data for each month with a blank row for months with no data.
For example:
TransactionID | Date | Value
1 | 01/01/2015 | £10
2 | 16/01/2015 | £15
3 | 21/01/2015 | £5
4 | 15/03/2015 | £20
5 | 12/03/2015 | £15
6 | 23/04/2015 | £10
Needs to return:
Month | Amount
January | £30
February | £0
March | £35
April | £10
My query will rely on specifying a date range so I can set the first and last date of the query.
I feel like I maybe over thinking this, but have gotten to that stage where you start to feel like you tying yourself in knots.
The key is having access to a list of integers to represent the months in the range. If you don't have a Numbers Table, then spt_values will do in a pinch.
SqlFiddle Demo
SELECT
[Year] = YEAR(DATEADD(month,[i],#range_start))
,[Month] = DATENAME(month,DATEADD(month,[i],#range_start))
,[Amount] = ISNULL(SUM([Value]),0)
FROM (
SELECT TOP (DATEDIFF(month,#range_start,#range_end)+1)
ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1 [i]
FROM master.dbo.spt_values
) t1
LEFT JOIN #MyTable t2
ON (t1.[i] = DATEDIFF(month,#range_start,t2.[Date]) )
GROUP BY [i]
ORDER BY [i]
SQL is a tricky language at first. You actually do not want a loop. In fact, you pretty much never want to loop in SQL except in very few cases. Try this out:
DECLARE #StartDate DATE,
#EndDate DATE;
SET #StartDate = '01 January 2015';
SET #EndDate = '30 April 2015';
WITH CTE_Months
AS
(
SELECT #StartDate dates
UNION ALL
SELECT DATEADD(MONTH,1,dates)
FROM CTE_Months
WHERE DATEADD(MONTH,1,dates) < #EndDate
)
SELECT YEAR(B.[date]) AS yr,
DATENAME(MONTH,B.[Date]) AS month_name,
SUM(ISNULL(B.Value,0)) AS Amount
FROM CTE_Months A
LEFT JOIN yourTable B
ON YEAR(A.[date]) = YEAR(B.[date])
AND MONTH(A.[date]) = MONTH(B.[date])
GROUP BY YEAR(B.[date]),DATENAME(MONTH,B.[Date])
One way: create a table called months with a monthnum int field and 12 rows of [1..12]
declare #start date = '01 jan 2015',
#end date = '30 apr 2015'
select
datename(month, dateadd(month, monthnum, 0) - 1),
isnull(Amount, 0)
from months
left join (
select
month(date) Month,
sum(Value) Amount
from tbl
where date between #start and #end
group by month(date)
) T on (T.Month = months.monthnum)
where months.monthnum between month(#start) and month(#end)
order by monthnum
The following code will generate one output row for each month between the first and last transaction dates. Spanning a year boundary, or multiple years, is handled correctly.
-- Some sample data.
declare #Transactions as Table
( TransactionId Int Identity, TransactionDate Date, Value Int );
insert into #Transactions ( TransactionDate, Value ) values
( '20141125', 10 ), ( '20150311', 20 ), ( '20150315', 5 ), ( '20150509', 42 );
select * from #Transactions;
with
-- Determine the first and last dates involved.
Range as (
select Min( TransactionDate ) as FirstDate, Max( TransactionDate ) as LastDate
from #Transactions ),
-- Generate a set of all of the months in the range.
Months as (
select DateAdd( month, DateDiff( month, 0, FirstDate ), 0 ) as Month,
DateAdd( month, DateDiff( month, 0, LastDate ), 0 ) as LastMonth
from Range
union all
select DateAdd( month, 1, Month ), LastMonth
from Months
where Month < LastMonth )
-- Summarize the transactions.
select M.Month, Coalesce( Sum( T.Value ), 0 ) as Total
from Months as M left outer join
#Transactions as T on DateAdd( month, DateDiff( month, 0, T.TransactionDate ), 0 ) = M.Month
group by M.Month
order by M.Month
option ( MaxRecursion 1000 );

sql query to find the Items with the highest difference

I have my database table ABC as shown below :
ItemId Month Year Sales
1 1 2013 333
1 2 2013 454
2 1 2013 434
and so on .
I would like to write a query to find the top 3 items that have had the highest increase in sales from last month to this month , so that I see somethinglike this in the output.
Output :
ItemId IncreaseInSales
1 +121
9 +33
6 +16
I came up to here :
select
(select Sum(Sales) from ABC where [MONTH] = 11 )
-
(select Sum(Sales) from ABC where [MONTH] = 10)
I cannot use a group by as it is giving an error . Can anyone point me how I can
proceed further ?
Assuming that you want the increase for a given month, you can also do this with an aggregation query:
select top 3 a.ItemId,
((sum(case when year = #YEAR and month = #MONTH then 1.0*sales end) /
sum(case when year = #YEAR and month = #MONTH - 1 or
year = #YEAR - 1 and #Month = 1 and month = 12
then sales end)
) - 1
) * 100 as pct_increase
from ABC a
group by a.ItemId
order by pct_increase desc;
You would put the year/month combination you care about in the variables #YEAR and #MONTH.
EDIT:
If you just want the increase, then do a difference:
select top 3 a.ItemId,
(sum(case when year = #YEAR and month = #MONTH then 1.0*sales end) -
sum(case when year = #YEAR and month = #MONTH - 1 or
year = #YEAR - 1 and #Month = 1 and month = 12
then sales
end)
) as difference
from ABC a
group by a.ItemId
order by difference desc;
Here is the SQL Fiddle that demonstrates the below query:
SELECT TOP(3) NewMonth.ItemId,
NewMonth.Month11Sales - OldMonth.Month10Sales AS IncreaseInSales
FROM
(
SELECT s1.ItemId, Sum(s1.Sales) AS Month11Sales
FROM ABC AS s1
WHERE s1.MONTH = 11
AND s1.YEAR = 2013
GROUP BY s1.ItemId
) AS NewMonth
INNER JOIN
(
SELECT s2.ItemId, Sum(s2.Sales) AS Month10Sales
FROM ABC AS s2
WHERE s2.MONTH = 10
AND s2.YEAR = 2013
GROUP BY s2.ItemId
) AS OldMonth
ON NewMonth.ItemId = OldMonth.ItemId
ORDER BY NewMonth.Month11Sales - OldMonth.Month10Sales DESC
You never mentioned if you could have more than one record for an ItemId with the same Month, so I made the query to handle it either way. Obviously you were lacking the year = 2013 in your query. Once you get past this year you will need that.
Another option could be something on these lines:
SELECT top 3 a.itemid, asales-bsales increase FROM
(
(select itemid, month, sum(sales) over(partition by itemid) asales from ABC where month=2
and year=2013) a
INNER JOIN
(select itemid, month, sum(sales) over(partition by itemid) bsales from ABC where month=1
and year=2013) b
ON a.itemid=b.itemid
)
ORDER BY increase desc
if you need to cater for months without sales then you can do a FULL JOIN and calculate increase as isnull(asales,0) - isnull(bsales,0)
You could adapt this solution based on PIVOT operator:
SET NOCOUNT ON;
DECLARE #Sales TABLE
(
ItemID INT NOT NULL,
SalesDate DATE NOT NULL,
Amount MONEY NOT NULL
);
INSERT #Sales (ItemID, SalesDate, Amount)
VALUES
(1, '2013-01-15', 333), (1, '2013-01-14', 111), (1, '2012-12-13', 100), (1, '2012-11-12', 150),
(2, '2013-01-11', 200), (2, '2012-12-10', 150), (3, '2013-01-09', 900);
-- Parameters (current year & month)
DECLARE #pYear SMALLINT = 2013,
#pMonth TINYINT = 1;
DECLARE #FirstDayOfCurrentMonth DATE = CONVERT(DATE, CONVERT(CHAR(4), #pYear) + '-' + CONVERT(CHAR(2), #pMonth) + '-01');
DECLARE #StartDate DATE = DATEADD(MONTH, -1, #FirstDayOfCurrentMonth), -- Begining of the previous month
#EndDate DATE = DATEADD(DAY, -1, DATEADD(MONTH, 1, #FirstDayOfCurrentMonth)) -- End of the current month
SELECT TOP(3) t.ItemID,
t.[2]-t.[1] AS IncreaseAmount
FROM
(
SELECT y.ItemID, y.Amount,
DENSE_RANK() OVER(ORDER BY y.FirstDayOfSalesMonth ASC) AS MonthNum -- 1=Previous Month, 2=Current Month
FROM
(
SELECT x.ItemID, x.Amount,
DATEADD(MONTH, DATEDIFF(MONTH, 0, x.SalesDate), 0) AS FirstDayOfSalesMonth
FROM #Sales x
WHERE x.SalesDate BETWEEN #StartDate AND #EndDate
) y
) z
PIVOT( SUM(z.Amount) FOR z.MonthNum IN ([1], [2]) ) t
ORDER BY IncreaseAmount DESC;
SQLFiddle demo
Your sample data seems to be incomplete, however, here is my try. I assume that you want to know the three items with the greatest sales-difference from one month to the next:
WITH Increases AS
(
SELECT a1.itemid,
a1.sales - (SELECT a2.sales
FROM dbo.abc a2
WHERE a1.itemid = a2.itemid
AND ( ( a1.year = a2.year
AND a1.month > 1
AND a1.month = a2.month + 1 )
OR ( a1.year = a2.year + 1
AND a1.month = 1
AND a2.month = 12 ) ))AS IncreaseInSales
FROM dbo.abc a1
)
SELECT TOP 3 ItemID, MAX(IncreaseInSales) AS IncreaseInSales
FROM Increases
GROUP BY ItemID
ORDER BY MAX(IncreaseInSales) DESC
Demo
SELECT
cur.[ItemId]
MAX(nxt.[Sales] - cur.[Sales]) AS [IncreaseInSales]
FROM ABC cur
INNER JOIN ABC nxt ON (
nxt.[Year] = cur.[Year] + cur.[month]/12 AND
nxt.[Month] = cur.[Month]%12 + 1
)
GROUP BY cur.[ItemId]
I'd do this this way. It should work in all the tagged versions of SQL Server:
SELECT TOP 3 [ItemId],
MAX(CASE WHEN [Month] = 2 THEN [Sales] END) -
MAX(CASE WHEN [Month] = 1 THEN [Sales] END) [Diff]
FROM t
WHERE [Month] IN (1, 2) AND [Year] = 2013
GROUP BY [ItemId]
HAVING COUNT(*) = 2
ORDER BY [Diff] DESC
Fiddle here.
The reason why I'm adding the HAVING clause is that if any item is added in only one of the months then the numbers will be all wrong. So I'm only comparing items that are only present in both months.
The reason of the WHERE clause would be to filter in advance only the needed months and improve the efficiency of the query.
An SQL Server 2012 solution could also be:
SELECT TOP 3 [ItemId], [Diff] FROM (
SELECT [ItemId],
LEAD([Sales]) OVER (PARTITION BY [ItemId] ORDER BY [Month]) - [Sales] Diff
FROM t
WHERE [Month] IN (1, 2) AND [Year] = 2013
) s
WHERE [Diff] IS NOT NULL
ORDER BY [Diff] DESC

Resources