Using OVER PARTITION BY in higher levels - sql-server

I've been struggling with this problem. I have a table that looks like this:
Date
Location
SkU
Model
Type
Qty
2020-01-01
01A
AB1
One
A
10
2020-01-01
01A
AB1
One
B
3
2020-01-01
01A
AB2
One
A
5
2020-01-01
01A
AB2
One
B
0
2020-01-02
01A
AB1
One
A
8
2020-01-02
01A
AB1
One
B
2
2020-01-02
01A
AB2
One
A
1
2020-01-02
01A
AB2
One
B
0
2020-01-01
01A
CD1
Two
A
3
2020-01-01
01A
CD1
Two
B
9
2020-01-01
01A
CD2
Two
A
0
2020-01-01
01A
CD2
Two
B
1
2020-01-02
01A
CD1
Two
A
7
2020-01-02
01A
CD1
Two
B
4
2020-01-02
01A
CD2
Two
A
1
2020-01-02
01A
CD2
Two
B
5
As you can see, I have dates, locations, skus , models and types, and what I want to do is to calculate a running total between the current date and 3 days before by Location and Model. However, when I do this in my current code, since I have several rows for those combinations, it's giving me incorrect results.
This is my code:
SUM(Qty) OVER (PARTITION BY Location, Model ORDER BY date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
The expected output should look like this:
Date
Location
SkU
Model
Type
Qty
RunningTotal
2020-01-01
01A
AB1
One
A
10
18
2020-01-01
01A
AB1
One
B
3
18
2020-01-01
01A
AB2
One
A
5
18
2020-01-01
01A
AB2
One
B
0
18
2020-01-02
01A
AB1
One
A
8
29
2020-01-02
01A
AB1
One
B
2
29
2020-01-02
01A
AB2
One
A
1
29
2020-01-02
01A
AB2
One
B
0
29
2020-01-01
01A
CD1
Two
A
3
13
2020-01-01
01A
CD1
Two
B
9
13
2020-01-01
01A
CD2
Two
A
0
13
2020-01-01
01A
CD2
Two
B
1
30
2020-01-02
01A
CD1
Two
A
7
30
2020-01-02
01A
CD1
Two
B
4
30
2020-01-02
01A
CD2
Two
A
1
30
2020-01-02
01A
CD2
Two
B
5
30

This seems to do the trick. You may want to test with a larger data set.
I pulled out the 'higher level' of data into a CTE, apply the lag and then join this back to the original set of data.
What I don't think this solves - but you didn't expressly ask for - is the case where there are gaps in the dates within the data. You might not get your expected results in this case (e.g. it doesn't lag by three calendar days, but rather by three records).
declare #src table ([Date] DateTime, [Location] varchar(5), [SkU] varchar(5), Model varchar(10), [Type] varchar(1), Qty int)
insert into #src values
('2020-01-01', '01A', 'AB1', 'One', 'A', 10),
('2020-01-01', '01A', 'AB1', 'One', 'B', 3 ),
('2020-01-01', '01A', 'AB2', 'One', 'A', 5 ),
('2020-01-01', '01A', 'AB2', 'One', 'B', 0 ),
('2020-01-02', '01A', 'AB1', 'One', 'A', 8 ),
('2020-01-02', '01A', 'AB1', 'One', 'B', 2 ),
('2020-01-02', '01A', 'AB2', 'One', 'A', 1 ),
('2020-01-02', '01A', 'AB2', 'One', 'B', 0 ),
('2020-01-01', '01A', 'CD1', 'Two', 'A', 3 ),
('2020-01-01', '01A', 'CD1', 'Two', 'B', 9 ),
('2020-01-01', '01A', 'CD2', 'Two', 'A', 0 ),
('2020-01-01', '01A', 'CD2', 'Two', 'B', 1 ),
('2020-01-02', '01A', 'CD1', 'Two', 'A', 7 ),
('2020-01-02', '01A', 'CD1', 'Two', 'B', 4 ),
('2020-01-02', '01A', 'CD2', 'Two', 'A', 1 ),
('2020-01-02', '01A', 'CD2', 'Two', 'B', 5 )
-- Create a CTE with the quantity totals by Date/Location/Model
; with dailyTotals as
(
select [Date], [Location], [Model], sum(Qty) QtyForDay
from #src
group by [Date], [Location], [Model]
)
-- then generate the lag using the daily total
, dailyTotalsWithLag as
(
select [Date], [Location], [Model], RunningTotalForDay = SUM(QtyForDay) OVER (PARTITION BY Location, Model ORDER BY date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
from dailyTotals
)
-- and finally join the lagged totals back to the source records to get the desired result.
select s.*, t.RunningTotalForDay
from #src s
join dailyTotalsWithLag t
on s.[Date] = t.[Date] and s.Location = t.Location and s.Model = t.Model

Related

How to bring the last 5 register of each ID with SQL?

I have a table with the purchase registers. There are all the purchase registers of a Pet Shop, since 2010.
I need some help to bring only the last five purchase of each client.
I was trying, but it is not working. It brings me the last 5 registers of all the table, and not of each client.
SELECT TOP (5) [client_name],
[purchase_date],
[item]
FROM [Pet_Shop]
ORDER BY client_name
WHERE client_name in ('John', 'Mary', 'Austin')
I need this kind of return:
client_name | purchase_date | item
___________________________________
John | 2019-09-14 | food
John | 2019-09-13 | ball
John | 2019-09-12 | shampoo
John | 2019-09-11 | cookie
John | 2019-09-11 | food
Mary | 2019-09-14 | collar
Mary | 2019-07-14 | food
Mary | 2019-06-14 | toy
Mary | 2019-06-14 | hamster
Mary | 2019-05-14 | food
Austin | 2019-09-18 | food
Austin | 2019-09-11 | collar
Austin | 2019-09-10 | toy
Austin | 2019-09-09 | catnip
Austin | 2019-09-11 | food
Use ROW_NUMBER():
SELECT *
FROM (
SELECT
client_name,
purchase_date,
item,
ROW_NUMBER() OVER(PARTITION BY client_name ORDER BY purchase_date desc) rn
FROM Pet_Shop
WHERE client_name in ('John', 'Mary', 'Austin')
) x
WHERE rn <= 5
ORDER BY client_name
You can use CROSS APPLY like this:
DECLARE #Registry TABLE (client_name VARCHAR(100), purchase_date DATETIME, item INT)
INSERT INTO #Registry
(client_name, purchase_date, item)
VALUES
('Client1', '1/1/2019', 1),
('Client1', '2/1/2019', 2),
('Client1', '3/1/2019', 3),
('Client1', '4/1/2019', 4),
('Client1', '5/1/2019', 5),
('Client1', '6/1/2019', 6),
('Client1', '7/1/2019', 7),
('Client2', '1/1/2019', 1),
('Client2', '2/1/2019', 2),
('Client2', '3/1/2019', 3),
('Client2', '4/1/2019', 4),
('Client2', '5/1/2019', 5),
('Client2', '6/1/2019', 6),
('Client2', '7/1/2019', 7)
;WITH Clients AS (
SELECT client_name FROM #Registry GROUP BY client_name
)
SELECT C.*, P.purchase_date, P.item
FROM Clients AS C
CROSS APPLY
(
SELECT TOP 5 R.purchase_date, R.item
FROM #Registry R
WHERE R.client_name = C.client_name
ORDER BY R.purchase_date DESC
) P
ORDER BY C.client_name, P.purchase_date, P.item
Here is the result:
client_name purchase_date item
Client1 2019-03-01 00:00:00.000 3
Client1 2019-04-01 00:00:00.000 4
Client1 2019-05-01 00:00:00.000 5
Client1 2019-06-01 00:00:00.000 6
Client1 2019-07-01 00:00:00.000 7
Client2 2019-03-01 00:00:00.000 3
Client2 2019-04-01 00:00:00.000 4
Client2 2019-05-01 00:00:00.000 5
Client2 2019-06-01 00:00:00.000 6
Client2 2019-07-01 00:00:00.000 7

Sum Column as an Accumulation Based on a Time Period

I have a table in which records are inserted at different periods (each record contains a column called 'Amount').
I want to show the total amount acummulation, after each 5 seconds. I have tried with the following query without success:
SELECT Sum(totalamount) AS RealTimeTotalAmount,
Datepart(second, createstamp) / 5 AS dp
FROM [order]
WHERE
createstamp BETWEEN Dateadd(s, -5, Getdate()) AND Getdate()
GROUP BY Datepart(second, createstamp) / 5
The problem I am facing is, that it shows me the 'accumulative sum as per each second' and I want to see it like '(accumulative sum as per each second + total accumulative amount till that second)'
Here is how the source data looks like:
-----------------------------------------------------------
|OrderID | CreateStamp | TotalAmount |
-----------------------------------------------------------
|1 |2015-03-22 15:26:05.620 | 10 |
-----------------------------------------------------------
|2 |2015-03-22 15:26:05.653 | 20 |
-----------------------------------------------------------
|3 |2015-03-22 15:26:05.660 | 10 |
-----------------------------------------------------------
|4 |2015-03-22 15:26:06.663 | 10 |
-----------------------------------------------------------
|5 |2015-03-22 15:26:06.670 | 30 |
-----------------------------------------------------------
Essentially, I want the resulting query to return as follows:
----------------------------------------
|Period | Accumulative Amount |
----------------------------------------
|0 to 5 seconds | 30 |
----------------------------------------
|0 to 10 seconds | 80 |
----------------------------------------
This is basically an accumulation from 0 time to multiples of 5.for last 5 seconds basically i am calculating the amount for the whole day up to the time when i execute this query and for example the amount for whole day before this time was 50 so result table should look like
----------------------------------------
|0 to 5 seconds | 30 + 50 = 80 |
----------------------------------------
|0 to 10 seconds | 80 + 80 = 160 |
----------------------------------------
you can try something like this.
Input Data
DECLARE #Orders TABLE
(
OrderId INT,
CreateStamp DATETIME,
TotalAmount NUMERIC(9,2)
)
INSERT INTO #Orders
SELECT 1,'2015-03-22 15:26:05.620',400
UNION ALL SELECT 2,'2015-03-22 15:26:04.653',500
UNION ALL SELECT 3,'2015-03-22 15:26:05.660',600
UNION ALL SELECT 4,'2015-03-22 15:26:06.663',700
UNION ALL SELECT 5,'2015-03-22 15:26:06.670',900
UNION ALL SELECT 6,'2015-03-22 15:26:05.660',600
UNION ALL SELECT 7,'2015-03-22 15:26:09.663',700
UNION ALL SELECT 8,'2015-03-22 15:26:12.670',900
Query
;WITH CTE as
(
SELECT DATEDIFF(minute,0,CreateStamp)totalminutes,Datepart(second, CreateStamp ) / 5 sec,SUM(TotalAmount) TotalAmount
FROM #Orders
GROUP BY DATEDIFF(minute,0,CreateStamp),Datepart(second, CreateStamp) / 5
)
SELECT DATEADD(minute,totalminutes,0) dt,sec,(SELECT SUM(TotalAmount) FROM cte WHERE totalminutes <=c2.totalminutes and sec <=c2.sec)
FROM CTE c2
ORDER BY sec;
I have added a GROUP BY DATEDIFF(minute,0,CreateStamp) to separate seconds for different dates and minutes.
If I understand you correctly:
DECLARE #t TABLE
(
ID INT ,
D DATETIME ,
A MONEY
)
DECLARE #mind DATETIME ,
#maxd DATETIME
INSERT INTO #t
VALUES ( 1, '2015-04-07 13:49:15.000', 5 ),
( 2, '2015-04-07 13:49:17.000', 15 ),
( 3, '2015-04-07 13:49:35.000', 2 ),
( 4, '2015-04-07 13:49:45.000', 4 ),
( 5, '2015-04-07 13:49:49.000', 20 ),
( 6, '2015-04-07 13:50:05.000', 20 ),
( 7, '2015-04-07 13:50:09.000', 3 ),
( 8, '2015-04-07 13:50:09.000', 3 ),
( 9, '2015-04-07 13:50:10.000', 1 ),
( 10, '2015-04-07 13:50:15.000', 1 )
SELECT #mind = MIN(d) ,
#maxd = MAX(d)
FROM #t;
WITH cte
AS ( SELECT #mind AS d
UNION ALL
SELECT DATEADD(ss, 5, d)
FROM cte
WHERE cte.d <= #maxd
)
SELECT cte.d, SUM(A) AS A FROM cte
JOIN #t t ON t.D < cte.d
GROUP BY cte.d
Output:
d A
2015-04-07 13:49:20.000 20.00
2015-04-07 13:49:25.000 20.00
2015-04-07 13:49:30.000 20.00
2015-04-07 13:49:35.000 20.00
2015-04-07 13:49:40.000 22.00
2015-04-07 13:49:45.000 22.00
2015-04-07 13:49:50.000 46.00
2015-04-07 13:49:55.000 46.00
2015-04-07 13:50:00.000 46.00
2015-04-07 13:50:05.000 46.00
2015-04-07 13:50:10.000 72.00
2015-04-07 13:50:15.000 73.00
2015-04-07 13:50:20.000 74.00

Add computed column

This is my table:
with these columns:
ShiftId ShiftNum Date ShiftType StartTime EndTime
1 1 2014-08-07 A 0:00:00 6:00:00
2 2 2014-08-07 B 6:01:00 18:00:00
3 3 2014-08-07 A 18:00:01 23:59:00
4 1 2014-08-08 A 0:00:00 6:00:00
5 2 2014-08-08 C 6:01:00 18:00:00
6 3 2014-08-08 B 18:00:01 23:59:00
7 1 2014-08-09 B 0:00:00 6:00:00
8 2 2014-08-09 C 6:01:00 18:00:00
9 3 2014-08-09 B 18:00:01 23:59:00
10 1 2014-08-10 B 0:00:00 6:00:00
11 2 2014-08-10 D 6:01:00 18:00:00
12 3 2014-08-10 C 18:00:01 23:59:00
13 1 2014-08-11 C 0:00:00 6:00:00
14 2 2014-08-11 D 6:01:00 18:00:00
15 3 2014-08-11 C 18:00:01 23:59:00
16 1 2014-08-12 C 0:00:00 6:00:00
17 2 2014-08-12 A 6:01:00 18:00:00
18 3 2014-08-12 D 18:00:01 23:59:00
19 1 2014-08-13 D 0:00:00 6:00:00
20 2 2014-08-13 A 6:01:00 18:00:00
21 3 2014-08-13 D 18:00:01 23:59:00
22 1 2014-08-14 D 0:00:00 6:00:00
23 2 2014-08-14 B 6:01:00 18:00:00
I want to add "DailyShiftId" column as computrd column means when shiftType changed then DailyShiftId has been increased.
ShiftId ShiftNum Date ShiftType StartTime EndTime DailyShiftId
1 1 2014-08-07 A 0:00:00 6:00:00 1
2 2 2014-08-07 B 6:01:00 18:00:00 2
3 3 2014-08-07 A 18:00:01 23:59:00 3
4 1 2014-08-08 A 0:00:00 6:00:00 3
5 2 2014-08-08 C 6:01:00 18:00:00 4
6 3 2014-08-08 B 18:00:01 23:59:00 5
7 1 2014-08-09 B 0:00:00 6:00:00 5
8 2 2014-08-09 C 6:01:00 18:00:00 6
9 3 2014-08-09 B 18:00:01 23:59:00 7
10 1 2014-08-10 B 0:00:00 6:00:00 7
11 2 2014-08-10 D 6:01:00 18:00:00 8
12 3 2014-08-10 C 18:00:01 23:59:00 9
13 1 2014-08-11 C 0:00:00 6:00:00 9
14 2 2014-08-11 D 6:01:00 18:00:00 10
15 3 2014-08-11 C 18:00:01 23:59:00 11
16 1 2014-08-12 C 0:00:00 6:00:00 11
17 2 2014-08-12 A 6:01:00 18:00:00 12
18 3 2014-08-12 D 18:00:01 23:59:00 13
19 1 2014-08-13 D 0:00:00 6:00:00 13
20 2 2014-08-13 A 6:01:00 18:00:00 14
21 3 2014-08-13 D 18:00:01 23:59:00 15
22 1 2014-08-14 D 0:00:00 6:00:00 15
23 2 2014-08-14 B 6:01:00 18:00:00 16
how can I do it?
Create On Update Trigger
CREATE TRIGGER ViewEmployeeTrigger ON Employee
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON
UPDATE Employee
SET DailyShiftId=DailyShiftId+1
-- what ever your updation logic
FROM INSERTED I JOIN Employee C ON I.ShiftId= C.ShiftId
END
GO
You can add DailyShiftId as a normal column and add an AFTER UPDATE Trigger on your table:
CREATE TRIGGER TAU_MyTable
ON MyTable
AFTER UPDATE
AS
BEGIN
UPDATE MyTable SET DailyShiftId = DailyShiftId + 1
WHERE [INSERTED].ShiftType <> [DELETED].ShiftType
END
OR
You can add a function to the database to calculate value of the new column and use Computed Column Specification as done here:
http://www.c-sharpcorner.com/UploadFile/rohatash/formula-for-computed-column-specification-property-in-sql-se/
This Query will be helpful to you to get the expected result without using trigger.
DECLARE #Test Table
(
ShiftId Integer,
ShiftNum Int,
Date DATETIME,
ShiftType VARCHAR(1)
)
INSERT INTO #Test (ShiftId, ShiftNum, Date, ShiftType)
SELECT 1, 1, '2014-08-07', 'A' UNION ALL
SELECT 2, 2, '2014-08-07', 'B' UNION ALL
SELECT 3, 3, '2014-08-07', 'A' UNION ALL
SELECT 4, 1, '2014-08-08', 'A' UNION ALL
SELECT 5, 2, '2014-08-08', 'C' UNION ALL
SELECT 6, 3, '2014-08-08', 'B' UNION ALL
SELECT 7, 1, '2014-08-09', 'B'
SELECT C.ShiftId, C.ShiftNum, C.Date, C.ShiftType ,
DENSE_RANK() OVER( order by C.DailyShift ASC) AS DailyShift
FROM
(
SELECT A.*,
CASE WHEN A.ShiftType = B.ShiftType
Then ROW_NUMBER() OVER(ORDER BY A.ShiftId ASC) - 1
Else ROW_NUMBER() OVER(ORDER BY A.ShiftId ASC)
END AS DailyShift
FROM #Test AS A LEFT JOIN #Test AS B ON A.ShiftId = B.ShiftId + 1
) AS C
Using Computed Column:
Add the column to the table specifing the function (Calculate).
ALTER TABLE dbo.TableName
ADD DailyShiftID AS dbo.Calculate(ShiftId, ShiftType)
CREATE FUNCTION dbo.Calculate(#nShiftId INT, #sShiftType VARCHAR(1))
RETURNS INT
AS
BEGIN
DECLARE #sPrevShiftType AS VARCHAR(1)
DECLARE #nDailyShift AS INT
SELECT #sPrevShiftType = ShiftType, #nDailyShift = ISNULL(DailyShiftID, 0)
FROM TableName WHERE ShiftId = #nShiftId - 1
IF (#sPrevShiftType <> #sShiftType)
SET #nDailyShift = #nDailyShift + 1
RETURN #nDailyShift
END

T-SQL query update null values

I have a very specific problem in T-SQL.
If I can solve this example case I give you I think I will be able to solve my original case.
Having this data in a table:
DECLARE #Test TABLE
(
Value INT
,Date DATETIME2(7)
);
INSERT INTO #Test
VALUES
(NULL, '2011-01-01 10:00'),
(NULL, '2011-01-01 11:00'),
(2, '2011-01-01 12:00'),
(NULL, '2011-01-01 13:00'),
(3, '2011-01-01 14:00'),
(NULL, '2011-01-01 15:00'),
(NULL, '2011-01-01 16:00'),
(4, '2011-01-01 17:00'),
(NULL, '2011-01-01 18:00'),
(5, '2011-01-01 19:00'),
(6, '2011-01-01 20:00')
I need to select this output:
Value Date
2 2011-01-01 10:00
2 2011-01-01 11:00
2 2011-01-01 12:00
2 2011-01-01 13:00
3 2011-01-01 14:00
3 2011-01-01 15:00
3 2011-01-01 16:00
4 2011-01-01 17:00
4 2011-01-01 18:00
5 2011-01-01 19:00
6 2011-01-01 20:00
To give some explanation. If value is NULL somewhere I need to update with the value from the previous hour. If there are several null values in a row the closest earlier hour with a non null value propagates and fills all these null values. Also if the first hour of the day is null then the earliest hour on the day with a non null value propagates downwards like 2 in this case. In your case you can assume that at least one value is non null value.
My ambition is to solve this with Common table expressions or something similar. With the cursor way I think I would have the solution in short bit of time if I try but my attempts with CTEs and recursive CTEs have failed so far.
Since your condition is not always the same this is a little bit more difficult. In your example, the first two rows need to get their values from the first value with a later date, in the other cases they need to get the values from previous dates. If you would always need to look previous dates, you could simple do this query:
SELECT B.Value,
A.[Date]
FROM #Test A
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE [Date] <= A.[Date] AND Value IS NOT NULL
ORDER BY [Date] DESC) B
But in your case, I think that you need this instead:
SELECT ISNULL(B.Value,C.Value) Value,
A.[Date]
FROM #Test A
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE [Date] <= A.[Date] AND Value IS NOT NULL
ORDER BY [Date] DESC) B
OUTER APPLY (SELECT TOP 1 *
FROM #Test
WHERE Value IS NOT NULL
ORDER BY [Date]) C
try this:
select
t.value, t.date
,COALESCE(t.value
,(select MAX(tt.value) from #Test tt WHERE t.Date>tt.Date)
,(SELECT MIN(ttt.Value) FROM #Test ttt Where ttt.Date IS NOT NULL)
) AS UseValue
from #Test t
OUTPUT:
value date UseValue
----------- ----------------------- -----------
NULL 2011-01-01 10:00:00.000 2
NULL 2011-01-01 11:00:00.000 2
2 2011-01-01 12:00:00.000 2
NULL 2011-01-01 13:00:00.000 2
3 2011-01-01 14:00:00.000 3
NULL 2011-01-01 15:00:00.000 3
NULL 2011-01-01 16:00:00.000 3
4 2011-01-01 17:00:00.000 4
NULL 2011-01-01 18:00:00.000 4
5 2011-01-01 19:00:00.000 5
6 2011-01-01 20:00:00.000 6

tsql grouping consecutive numbers in range

Is there any way to group these temperature measurement in a range with consecutive group?
I want to get group, time difference and count in between 0-7 and 8-12 and more than 12
Date Heat
01/01/2012 12:00 8
01/01/2012 12:03 9
01/01/2012 12:06 5
01/01/2012 12:09 3
01/01/2012 12:12 6
01/01/2012 12:15 7
01/01/2012 12:18 1
01/01/2012 12:21 12
01/01/2012 12:24 28
01/01/2012 12:27 25
01/01/2012 12:30 20
01/01/2012 12:33 20
01/01/2012 12:36 20
01/01/2012 12:39 12
01/01/2012 12:42 6
01/01/2012 12:45 3
01/01/2012 12:48 5
01/01/2012 12:51 7
01/01/2012 12:54 11
01/01/2012 12:57 12
01/01/2012 13:00 6
The result should be:
0-7 (01/01/2012 12:06-01/01/2012 12:18) 5
/* Rows of dataset:
01/01/2012 12:06 5
01/01/2012 12:09 3
01/01/2012 12:12 6
01/01/2012 12:15 7
01/01/2012 12:18 1
*/
0-7 (01/01/2012 12:42-01/01/2012 12:51) 5
/* Rows of dataset:
01/01/2012 12:42 6
01/01/2012 12:45 3
01/01/2012 12:48 5
01/01/2012 12:51 7
*/
8-12 (01/01/2012 12:00-01/01/2012 12:03) 2
/* Rows of dataset:
01/01/2012 12:00 8
01/01/2012 12:03 9
*/
more then 12 (01/01/2012 12:24-01/01/2012 12:36) 5
/* Rows of dataset:
01/01/2012 12:24 28
01/01/2012 12:27 25
01/01/2012 12:30 20
01/01/2012 12:33 20
01/01/2012 12:36 20
*/
8-12 (01/01/2012 12:21) 1
/* Rows of dataset:
01/01/2012 12:21 12 */
Note: because the processing order for RANK/DENSE_RANK is PARTITION BY and then ORDER BY, these functions are not useful in this case. Maybe, at some point in time, MS will introduce a supplementary syntax thus:
[DENSE_]RANK() OVER(ORDER BY fields PARTITION BY fields) so ORDER BY will be processed first and then PARTITION BY.
1) First solution (SQL2005+)
DECLARE #TestData TABLE
(
Dt SMALLDATETIME PRIMARY KEY,
Heat TINYINT NOT NULL
);
INSERT #TestData(Dt, Heat)
VALUES
SELECT '2012-01-01T12:00:00', 8 UNION ALL SELECT '2012-01-01T12:03:00', 9 UNION ALL SELECT '2012-01-01T12:06:00', 5
UNION ALL SELECT '2012-01-01T12:09:00', 3 UNION ALL SELECT '2012-01-01T12:12:00', 6 UNION ALL SELECT '2012-01-01T12:15:00', 7
UNION ALL SELECT '2012-01-01T12:18:00', 1 UNION ALL SELECT '2012-01-01T12:21:00', 12 UNION ALL SELECT '2012-01-01T12:24:00', 28
UNION ALL SELECT '2012-01-01T12:27:00', 25 UNION ALL SELECT '2012-01-01T12:30:00', 20 UNION ALL SELECT '2012-01-01T12:33:00', 20
UNION ALL SELECT '2012-01-01T12:36:00', 20 UNION ALL SELECT '2012-01-01T12:39:00', 12 UNION ALL SELECT '2012-01-01T12:42:00', 6
UNION ALL SELECT '2012-01-01T12:45:00', 3 UNION ALL SELECT '2012-01-01T12:48:00', 5 UNION ALL SELECT '2012-01-01T12:51:00', 7
UNION ALL SELECT '2012-01-01T12:54:00', 11 UNION ALL SELECT '2012-01-01T12:57:00', 12 UNION ALL SELECT '2012-01-01 13:00:00', 6;
SET STATISTICS IO ON;
WITH CteSource
AS
(
SELECT a.*,
CASE
WHEN a.Heat >= 0 AND a.Heat <= 7 THEN 1
WHEN a.Heat >= 8 AND a.Heat <= 12 THEN 2
WHEN a.Heat > 12 THEN 3
END AS Grp,
ROW_NUMBER() OVER(ORDER BY a.Dt) AS RowNum
FROM #TestData a
), CteRecursive
AS
(
SELECT s.RowNum,
s.Dt,
s.Heat,
s.Grp,
1 AS DENSE_RANK_OVER_ORDERBY_PARTITIONBY
FROM CteSource s
WHERE s.RowNum = 1
UNION ALL
SELECT crt.RowNum,
crt.Dt,
crt.Heat,
crt.Grp,
CASE
WHEN crt.Grp = prev.Grp THEN prev.DENSE_RANK_OVER_ORDERBY_PARTITIONBY
ELSE prev.DENSE_RANK_OVER_ORDERBY_PARTITIONBY + 1
END
FROM CteSource crt
INNER JOIN CteRecursive prev ON crt.RowNum = prev.RowNum + 1
)
SELECT r.DENSE_RANK_OVER_ORDERBY_PARTITIONBY,
MAX(r.Grp) AS Grp,
COUNT(*) AS Cnt,
MIN(r.Dt) AS MinDt,
MAX(r.Dt) AS MaxDt
FROM CteRecursive r
GROUP BY r.DENSE_RANK_OVER_ORDERBY_PARTITIONBY;
Results:
DENSE_RANK_OVER_ORDERBY_PARTITIONBY Grp Cnt MinDt MaxDt
----------------------------------- ----------- ----------- ----------------------- -----------------------
1 2 2 2012-01-01 12:00:00 2012-01-01 12:03:00
2 1 5 2012-01-01 12:06:00 2012-01-01 12:18:00
3 2 1 2012-01-01 12:21:00 2012-01-01 12:21:00
4 3 5 2012-01-01 12:24:00 2012-01-01 12:36:00
5 2 1 2012-01-01 12:39:00 2012-01-01 12:39:00
6 1 4 2012-01-01 12:42:00 2012-01-01 12:51:00
7 2 2 2012-01-01 12:54:00 2012-01-01 12:57:00
8 1 1 2012-01-01 13:00:00 2012-01-01 13:00:00
2) Second solution (SQL2012; better performance)
SELECT d.DENSE_RANK_OVER_ORDERBY_PARTITIONBY,
MAX(d.Grp) AS Grp,
MIN(d.Dt) AS MinDt,
MAX(d.Dt) AS MaxDt
FROM
(
SELECT c.*,
1+SUM(c.IsNewGroup) OVER(ORDER BY c.Dt) AS DENSE_RANK_OVER_ORDERBY_PARTITIONBY
FROM
(
SELECT b.*,
CASE
WHEN LAG(b.Grp) OVER(ORDER BY b.Dt) <> b.Grp THEN 1
ELSE 0
END
AS IsNewGroup
FROM
(
SELECT a.*,
CASE
WHEN a.Heat >= 0 AND a.Heat <= 7 THEN 1
WHEN a.Heat >= 8 AND a.Heat <= 12 THEN 2
WHEN a.Heat > 12 THEN 3
END AS Grp
FROM #TestData a
) b
) c
) d
GROUP BY d.DENSE_RANK_OVER_ORDERBY_PARTITIONBY;
Here's an alternative solution for SQL Server 2005 or newer version:
WITH auxiliary (HeatID, MinHeat, MaxHeat, HeatDescr) AS (
SELECT 1, 0 , 7 , '0-7' UNION ALL
SELECT 2, 8 , 12 , '8-12' UNION ALL
SELECT 3, 13, NULL, 'more than 12'
),
datagrouped AS (
SELECT
d.*,
a.HeatDescr,
grp = ROW_NUMBER() OVER ( ORDER BY d.Date)
- ROW_NUMBER() OVER (PARTITION BY a.HeatID ORDER BY d.Date)
FROM data d
INNER JOIN auxiliary a
ON d.Heat BETWEEN a.MinHeat AND ISNULL(a.MaxHeat, 0x7fffffff)
)
SELECT
HeatDescr,
DateFrom = MIN(Date),
DateTo = MAX(Date),
ItemCount = COUNT(*)
FROM datagrouped
GROUP BY
HeatDescr, grp
ORDER BY
MIN(Date)
Where data is defined as follows:
CREATE TABLE data (Date datetime, Heat int);
INSERT INTO data (Date, Heat)
SELECT '01/01/2012 12:00', 8 UNION ALL
SELECT '01/01/2012 12:03', 9 UNION ALL
SELECT '01/01/2012 12:06', 5 UNION ALL
SELECT '01/01/2012 12:09', 3 UNION ALL
SELECT '01/01/2012 12:12', 6 UNION ALL
SELECT '01/01/2012 12:15', 7 UNION ALL
SELECT '01/01/2012 12:18', 1 UNION ALL
SELECT '01/01/2012 12:21', 12 UNION ALL
SELECT '01/01/2012 12:24', 28 UNION ALL
SELECT '01/01/2012 12:27', 25 UNION ALL
SELECT '01/01/2012 12:30', 20 UNION ALL
SELECT '01/01/2012 12:33', 20 UNION ALL
SELECT '01/01/2012 12:36', 20 UNION ALL
SELECT '01/01/2012 12:39', 12 UNION ALL
SELECT '01/01/2012 12:42', 6 UNION ALL
SELECT '01/01/2012 12:45', 3 UNION ALL
SELECT '01/01/2012 12:48', 5 UNION ALL
SELECT '01/01/2012 12:51', 7 UNION ALL
SELECT '01/01/2012 12:54', 11 UNION ALL
SELECT '01/01/2012 12:57', 12 UNION ALL
SELECT '01/01/2012 13:00', 6;
For the above sample, the query gives the following output:
HeatDescr DateFrom DateTo ItemCount
------------ ------------------- ------------------- ---------
8-12 2012-01-01 12:00:00 2012-01-01 12:03:00 2
0-7 2012-01-01 12:06:00 2012-01-01 12:18:00 5
8-12 2012-01-01 12:21:00 2012-01-01 12:21:00 1
more than 12 2012-01-01 12:24:00 2012-01-01 12:36:00 5
8-12 2012-01-01 12:39:00 2012-01-01 12:39:00 1
0-7 2012-01-01 12:42:00 2012-01-01 12:51:00 4
8-12 2012-01-01 12:54:00 2012-01-01 12:57:00 2
0-7 2012-01-01 13:00:00 2012-01-01 13:00:00 1
You should reach your goal using RANK()
http://msdn.microsoft.com/en-us/library/ms176102.aspx
Something like
SELECT date, heat, RANK() OVER (PARTITION BY heat ORDER BY date DESC) AS Rank
FROM tbl
Then you can GROUP it after, or make more sub selects and unions them, depending what you have as result.

Resources