My table looks something like
deal_id test_value run_date
820117648 1.2 2014-03-31
820117648 1.33 2014-04-30
820117648 1.33 2014-05-30
820117648 1.26 2014-06-30
820117648 1.11 2014-07-31
820117648 0.58 2014-09-30
820117648 1.64 2014-10-31
820117648 0.64 2014-11-28
820117648 3.65 2014-12-31
820117648 3.8 2015-03-11
820117649 0.64 2014-09-31
820117649 0.23 2014-10-31
820117649 0.64 2014-11-28
820117649 3.65 2014-12-31
820117649 3.8 2015-03-11
SELECT deal_id,test_value,run_date FROM ems.cdotests
WHERE run_date >= Dateadd(month, -4 Getdate())
I am trying to grab the last 4 run_date record from the whole set and then
I need to find the difference between the testValue on the first rundate of my select criteria and the last rundate i.e for deal_id 820117649 3.8 - 0.23 = 3.57 and for deal_id 820117648 it should be 3.8 -1.64 = 2.16
This is the large table with multiple deal id and several run_date associated with each dealid and may have data for the last 15 years or so
Any suggestion would be really helpfull
Try this solution. It will not be bound to particular deal_id.
DECLARE #t TABLE ( ID INT, V MONEY, D DATE )
INSERT INTO #t
VALUES ( 1, 1, '20140101' ),
( 1, 4, '20140102' ),
( 1, 2, '20140103' ),
( 1, 7, '20140104' ),
( 2, 5, '20140104' ),
( 2, 8, '20140110' ),
( 2, 11, '20140105' );
WITH cte
AS ( SELECT ID ,
D ,
V ,
MIN(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Min ,
MAX(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Max
FROM #t
)
SELECT c1.ID ,
c2.V - c1.V AS V
FROM cte c1
JOIN cte c2 ON c1.ID = c2.ID AND c1.D < c2.D
WHERE ( c1.D = c1.MIN OR c1.D = c1.MAX ) AND ( c2.D = c2.MIN OR c2.D = c2.MAX )
Output:
ID V
1 6.00
2 3.00
You have changed you question. For latest 6 month, add filter CTE above main CTE:
;WITH filter
AS ( SELECT ID ,
D ,
V ,
ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY D DESC ) AS RN
FROM #t
),
cte
AS ( SELECT ID ,
D ,
V ,
MIN(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Min ,
MAX(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Max
FROM filter
WHERE RN <= 6
)
SELECT c1.ID ,
c2.V - c1.V AS V
FROM cte c1
JOIN cte c2 ON c1.ID = c2.ID AND c1.D < c2.D
WHERE ( c1.D = c1.MIN OR c1.D = c1.MAX )
AND ( c2.D = c2.MIN OR c2.D = c2.MAX )
AND (c2.V - c1.V > 5)
You can use window functions
WITH C AS(
SELECT ROW_NUMBER() OVER (PARTITION BY deal_Id ORDER BY deal_Id) Rn
,deal_Id
,test_value
,run_date
FROM ems.cdotests
)
SELECT id1 deal_id, t2 - t1 diff
FROM (SELECT c1.deal_id id1
,c1.test_value t1
,c2.deal_id id2
, c2.test_value t2
FROM C c1
INNER JOIN C c2 ON c1.deal_id = c2.deal_id
WHERE c1.Rn = 1
AND c2.Rn = (SELECT TOP 1 MAX(Rn) FROM C GROUP BY deal_id)
) t
So late to answer, but a way is this:
;with t as (
select *,
row_number() over (partition by deal_id order by run_date desc) seq
from ems.cdotests
)
select
t1.deal_id, t1.run_date run_date1, t1.test_value test_value1,
t2.run_date run_date4, t2.test_value test_value4,
t1.test_value - t2.test_value diff
from t t1
join t t2
on t1.deal_id = t2.deal_id
where t1.seq = 1
and t2.seq = 4
-- as an extra condition of those should have at least 5 records:
and exists (select 1 from t ti where ti.deal_id = t1.deal_id and ti.seq > 5)
[SQL Fiddle]
Related
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
With a table as:
Name Num Value
----------------------
Peter 10 10
Mary 10,15 5,10
John 5,20 10,20
How can I get a result as the follow table with a View ?
Name Num Value
------------------
Peter 10 10
Mary 10 5
Mary 15 10
John 5 10
John 20 20
Notice Mary and John have multiple data (comma-delimited).
I've a function to do the split of a cell and returns a table but just for a specific row and cell, hos to iterate from all table ?
Aditional Info:
SELECT NAM.NAME, Data AS NUM, VAL.VALUE
FROM dbo.Split((SELECT NUM FROM t1 WHERE LineNum = 2), ','))
CROSS APPLY (
SELECT Id As Id1, Data AS VALUE
FROM dbo.Split((SELECT VALUE FROM t1 WHERE LineNum = 2), ','))
) AS VAL
CROSS APPLY (
SELECT NAME FROM t1 WHERE LineNum = 2
) AS NAM
WHERE Id = Id1
Note: LineNum is row number from t1
From Second Row of t1 (Mary | 10,15 | 5,10)
Previous Function returns table as:
Name Num Value
------------------
Mary 10 5
Mary 15 10
Split Function from a cell (10,15) returns:
Id Data
-----------
1 10
2 15
===============================================
My Split Function:
CREATE FUNCTION [dbo].[Split]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
Declare #YourTable table (Name varchar(50), Num varchar(50), Value varchar(50))
Insert into #YourTable values
('Peter','10', '10'),
('Mary','10,15','5,10'),
('John','5,20', '10,20')
Select A.Name
,B.*
From #YourTable A
Cross Apply (
Select Num = A.RetVal
,Value = B.RetVal
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(A.Num,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) A
Join (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(A.Value,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B on A.RetSeq=B.RetSeq
) B
Returns
Name Num Value
Peter 10 10
Mary 10 5
Mary 15 10
John 5 10
John 20 20
Edit - Another option with a UDF
Select A.Name
,B.*
From #YourTable A
Cross Apply (
Select Num = A.RetVal
,Value = B.RetVal
From [dbo].[udf-Str-Parse-8K](A.Num,',') A
Join [dbo].[udf-Str-Parse-8K](A.Value,',') B
on A.RetSeq=B.RetSeq
) B
The fastest UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = Substring(#String, A.N, A.L)
From cte4 A
);
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')
Needs some help on the following:
Table #Data contains the Opening and Closing Stock for a product over 5 days
Table #BackData contains some post dated transactions
How can i Update the table #Data with a Running Total including a carry forward
CREATE TABLE #Data (
Prod VARCHAR(20)
,SDate DATE
,OStock INT
,CStock INT
)
CREATE TABLE #BackData (
Prod VARCHAR(20)
,SDate DATE
,CStock INT
)
INSERT INTO #Data
SELECT 'p1', '2016-06-06', 10, 10
UNION ALL
SELECT 'p1', '2016-06-07', 10, 14
UNION ALL
SELECT 'p1', '2016-06-08', 14, 13
UNION ALL
SELECT 'p1', '2016-06-09', 13, 13
UNION ALL
SELECT 'p1', '2016-06-10', 13, 11
INSERT INTO #BackData
SELECT 'p1', '2016-06-06', 2
UNION ALL
SELECT 'p1', '2016-06-07', 4
UNION ALL
SELECT 'p1', '2016-06-09', -1
UNION ALL
SELECT 'p1', '2016-06-10', -2
DROP TABLE #Data
DROP TABLE #BackData
Desired Output :
Prod| SDate |OStock |CStock|
p1 |2016-06-06 |10 |12 |
p1 |2016-06-07 |12 |16 |
p1 |2016-06-08 |16 |16 |
p1 |2016-06-09 |16 |15 |
p1 |2016-06-10 |15 |13 |
EDIT
This is what i had managed to write before i got the answer, using two updates because the actual table had too many columns to use in a single query.
UPDATE D
SET D.CStock = FL.NewCStock
FROM #Data D
INNER JOIN (
SELECT DT.Prod
,DT.SDate
,SUM(IIF(RwNm = 1, DT.CStock, 0) + ISNULL(BD.CStock, 0)) OVER (
PARTITION BY DT.Prod ORDER BY DT.SDate ROWS UNBOUNDED PRECEDING
) NewCStock
FROM (
SELECT Prod
,SDate
,CStock
,ROW_NUMBER() OVER (
PARTITION BY Prod ORDER BY SDate
) AS RwNm
FROM #Data
) DT
LEFT JOIN #BackData BD ON DT.Prod = DT.Prod
AND BD.SDate = DT.SDate
) FL ON D.Prod = FL.Prod
AND D.SDate = FL.SDate
UPDATE D
SET D.OStock = PV.NewOStock
FROM #Data D
INNER JOIN (
SELECT Prod
,SDate
,ISNULL(LAG(CStock) OVER (
PARTITION BY Prod ORDER BY SDate
), CStock) AS NewOStock
FROM #Data
) PV ON D.Prod = PV.Prod
AND D.SDate = PV.SDate
You can use the following query to UPDATE:
;WITH ToUpdate AS (
SELECT d1.OStock, d1.CStock,
COALESCE(LAG(d2.CStock2) OVER (PARTITION BY d2.Prod
ORDER BY d2.SDate),
d1.OStock) AS OStock2,
d2.CStock2
FROM #Data AS d1
JOIN (
SELECT d.Prod, d.SDate, d.OStock, d.CStock,
COALESCE(t.newCStock,
LAG(t.newCStock) OVER (PARTITION BY d.Prod
ORDER BY d.SDate)) AS CStock2
FROM #Data AS d
LEFT JOIN (
SELECT bd.Prod, bd.SDate,
drn.CStock + SUM(bd.CStock) OVER (PARTITION BY bd.Prod
ORDER BY bd.SDate) AS newCStock
FROM #BackData AS bd
INNER JOIN (
SELECT Prod, CStock,
ROW_NUMBER() OVER (PARTITION BY Prod ORDER BY SDate) AS rn
FROM #Data
) AS drn ON bd.Prod = drn.Prod AND drn.rn = 1
) AS t ON t.Prod = d.Prod AND t.SDate = d.SDate
) AS d2 ON d1.Prod = d2.Prod AND d1.SDate= d2.SDate
)
UPDATE ToUpdate
SET OStock = OStock2,
CStock = CStock2
This looks awfully convoluted, but I couldn't think of anything simpler.
Demo here
You can rebuild values in #Data table with the help of recursive CTE:
;WITH cte AS (
SELECT top 1 d.Prod,
d.SDate,
d.OStock,
d.OStock + b.CStock as CStock
FROM #Data d
LEFT JOIN #BackData b
ON b.Prod = d.Prod and b.SDate = d.SDate
ORDER BY d.SDate ASC
UNION ALL
SELECT c.Prod,
DATEADD(day,1,c.SDate),
c.CStock,
c.CStock + ISNULL(b.CStock,0)
FROM cte c
INNER JOIN #Data d
ON d.Prod = c.Prod AND d.SDate = DATEADD(day,1,c.SDate)
OUTER APPLY (SELECT CStock FROM #BackData b WHERE b.Prod = d.Prod and b.SDate = d.SDate) as b
)
SELECT *
FROM cte
Output:
Prod SDate OStock CStock
-------------------- ---------- ----------- -----------
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13
To update #Data:
UPDATE d
SET OStock = c.OStock, CStock = c.CStock
FROM #Data d
INNER JOIN cte c
ON c.Prod = d.Prod AND c.SDate = d.SDate
Shouldn't the result be like :
Prod SDate OStock CStock
p1 2016-06-06 10 12
p1 2016-06-07 12 20 (#Data CStock 14 + #BakData 2 + 4)
p1 2016-06-08 20 19 (#Data CStock 13 + #BakData 2 + 4)
p1 2016-06-09 19 18 (#Data CStock 13 + #BakData 2 + 4 - 1)
p1 2016-06-10 18 14 (#Data CStock 11 + #BakData 2 + 4 - 1 -2)
This query will produce the above result
update d
set OStock = d.OStock + a.OAdj,
CStock = d.CStock + a.CAdj
from #Data d
cross apply
(
select OAdj = sum(case when Oflag = 1 then x.CStock else 0 end),
CAdj = sum(x.CStock)
from
(
select *, Oflag = case when x.SDate = d.SDate then 0 else 1 end
from #BackData x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) x
) a
Based on your expected output, it seems that you are re-calculating the daily Opening / Closing balance based on the figure from 2016-06-06
Here is a solution that will gives you your expected output.
; with
cte as
(
select Prod, SDate, OStock, CStock,
rn = row_number() over (partition by Prod order by SDate)
from #Data
),
adj as
(
select Prod, SDate, CStock
from cte
where rn = 1
union all
select Prod, SDate, CStock
from #BackData
)
update d
set OStock = coalesce(o.OStock, d.OStock),
CStock = c.CStock
from #Data d
cross apply
(
select OStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate < d.SDate
) o
cross apply
(
select CStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) c
Result :
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13
I have a table [tbl] with money values
id mon
1 10.17
2 36.00
I need to split these values into rows by a set of specific ranges [1.00,10.00,25.00]. The sum of the new values grouped by id will equal the original value.
id mon sum
1 1.00 1.00
1 9.17 10.17
2 1.00 1.00
2 10.00 11.00
2 25.00 36.00
Is there any way to do this without using a cursor?
Here's one way to do it:
;with CTE as (select t2.value, t1.id, sum(t2.value)
over (partition by t1.id order by t2.value asc) as total
from table1 t1 join table2 t2 on t1.mon >= t2.limit
)
select id, value, total from CTE
union all
select t1.id, t1.mon - c.total, t1.mon
from table1 t1
outer apply (select top 1 id, total from CTE c
where c.id = t1.id order by c.value desc) c
where t1.mon > c.total
order by 1,3
This uses additional table that has the limits stored to join with the original data and then uses running total in a CTE and joins that to the original table to get the remaining amounts
You can test the example in SQL Fiddle
Here is my attempt using window functions and CROSS APPLY:
;WITH Cte(s) AS(
SELECT CAST(1 AS MONEY) UNION ALL
SELECT 10 UNION ALL
SELECT 25
)
,CteRange AS(
SELECT
s,
e = SUM(s) OVER(ORDER BY s)
FROM Cte
)
SELECT
t.id,
mon = CASE WHEN t.mon > x.e THEN x.s ELSE mon - LAG(x.e) OVER(PARTITION BY t.id ORDER BY x.s) END,
[sum] = CASE WHEN t.mon < x.e THEN t.mon ELSE x.e END
FROM tbl t
CROSS APPLY(
SELECT * FROM CteRange
)x
WHERE t.mon > x.s
UNION ALL
SELECT
t.id,
mon = t.mon - x.e,
[sum] = t.mon
FROM tbl t
CROSS APPLY(
SELECT TOP 1 e
FROM CteRange
ORDER BY e DESC
)x(e)
WHERE t.mon > e
ORDER BY t.id, mon
SQL Fiddle
This works for your given example data, you just need to predefine ranges all by yourself (I've used CROSS JOIN VALUES, but this can be done however you want/prefer). I think that's not an issue. I've used running SUM and analytic functions to achieve that.
DECLARE #tbl TABLE
(
id INT IDENTITY (1, 1)
, mon MONEY
);
INSERT INTO #tbl (mon)
VALUES (10.17), (36.00);
SELECT id
, [sum] - SUM(lagRange) OVER (PARTITION BY ID ORDER BY rangeId) AS mon
, [sum]
FROM (
SELECT id, rangeId
, LAG(rangeValue, 1, 0) OVER (PARTITION BY ID ORDER BY rangeId) AS lagRange
, CASE
WHEN SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId) > mon THEN mon
ELSE SUM(rangeValue) OVER (PARTITION BY ID ORDER BY rangeId)
END AS [sum]
FROM #tbl
CROSS JOIN (VALUES ((1), (1.00)), ((2), (10.00)), ((3), (25.00))) AS T(rangeId, rangeValue)
WHERE rangeValue <= mon
) AS T;
Results:
id mon sum
-----------------
1 1.00 1.00
1 9.17 10.17
2 1.00 1.00
2 10.00 11.00
2 25.00 36.00
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