SQL Server time interval to multiple rows - sql-server

I have this record with StartDateTime and EndDateTime and I want the result to be in rows with 15 minutes interval:
Sample data:
FName | StartDateTime | EndDateTime
:----- | -----------: | :--------------:
Juan | 08/01/2017 1:00| 08/01/2017 8:00
Result:
FName | Interval
:----- | -----------:
Juan | 08/01/2017 1:00
Juan | 08/01/2017 1:15
Juan | 08/01/2017 1:30
Juan | 08/01/2017 1:45
Juan | 08/01/2017 2:00
Until it reaches to its EndDateTime, how can I achieve this in SQL. I'm thinking using looping statement or cte.

If you are open to a TVF (Table-Valued Function)
I'll often use this udf to create dynamic date/time ranges. It is faster than a recursive CTE and parameter driven.
The parameters are Date1, Date2, DatePart, and Increment
Example
Declare #YourTable Table ([FName] varchar(50),[StartDateTime] datetime,[EndDateTime] datetime)
Insert Into #YourTable Values
('Juan','08/01/2017 1:00','08/01/2017 8:00')
Select A.FName
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Range-Date](A.StartDateTime,A.EndDateTime,'MI',15) B
Returns
FName RetSeq RetVal
Juan 1 2017-08-01 01:00:00.000
Juan 2 2017-08-01 01:15:00.000
Juan 3 2017-08-01 01:30:00.000
Juan 4 2017-08-01 01:45:00.000
Juan 5 2017-08-01 02:00:00.000
...
Juan 29 2017-08-01 08:00:00.000
The UDF if Interested
CREATE FUNCTION [dbo].[udf-Range-Date] (#R1 datetime,#R2 datetime,#Part varchar(10),#Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case #Part When 'YY' then DateDiff(YY,#R1,#R2)/#Incr When 'QQ' then DateDiff(QQ,#R1,#R2)/#Incr When 'MM' then DateDiff(MM,#R1,#R2)/#Incr When 'WK' then DateDiff(WK,#R1,#R2)/#Incr When 'DD' then DateDiff(DD,#R1,#R2)/#Incr When 'HH' then DateDiff(HH,#R1,#R2)/#Incr When 'MI' then DateDiff(MI,#R1,#R2)/#Incr When 'SS' then DateDiff(SS,#R1,#R2)/#Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,#R1 Union All Select N,Case #Part When 'YY' then DateAdd(YY, N*#Incr, #R1) When 'QQ' then DateAdd(QQ, N*#Incr, #R1) When 'MM' then DateAdd(MM, N*#Incr, #R1) When 'WK' then DateAdd(WK, N*#Incr, #R1) When 'DD' then DateAdd(DD, N*#Incr, #R1) When 'HH' then DateAdd(HH, N*#Incr, #R1) When 'MI' then DateAdd(MI, N*#Incr, #R1) When 'SS' then DateAdd(SS, N*#Incr, #R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=#R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/

You could use a recursive query like this
DECLARE #SampleData AS TABLE
(
FName varchar(20),
StartDatetime datetime,
EndDatetime datetime
)
INSERT INTO #SampleData
(
FName,
StartDatetime,
EndDatetime
)
VALUES
('Juan', '2017-08-01 1:00', '2017-08-01 8:00')
;WITH temp AS
(
SELECT sd.FName, sd.StartDatetime AS Interval, sd.EndDatetime
FROM #SampleData sd
UNION ALL
SELECT t.FName, dateadd(minute, 15, t.Interval) AS Interval, t.EndDatetime
FROM temp t
WHERE dateadd(minute, 15, t.Interval) <= t.EndDatetime
)
SELECT t.FName,
t.Interval
FROM temp t
OPTION (MAXRECURSION 0)
Demo link: http://rextester.com/CELP88345

It is better to use date table but you can generate as below:
Select Fname, RowN as Interval from #dates
cross apply (
Select top(datediff(hh,startdatetime, enddatetime)*4+1) RowN =dateadd(mi, (row_number() over(order by (Select NULL)) -1)*15 ,startdatetime)
from master..spt_values s1, master..spt_values s2 ) a
Output as below:
+-------+-------------------------+
| Fname | Interval |
+-------+-------------------------+
| Juan | 2017-08-01 01:00:00.000 |
| Juan | 2017-08-01 01:15:00.000 |
| Juan | 2017-08-01 01:30:00.000 |
| Juan | 2017-08-01 01:45:00.000 |
| Juan | 2017-08-01 02:00:00.000 |
| Juan | 2017-08-01 02:15:00.000 |
| Juan | 2017-08-01 02:30:00.000 |
| Juan | 2017-08-01 02:45:00.000 |
| Juan | 2017-08-01 03:00:00.000 |
| Juan | 2017-08-01 03:15:00.000 |
| Juan | 2017-08-01 03:30:00.000 |
| Juan | 2017-08-01 03:45:00.000 |
| Juan | 2017-08-01 04:00:00.000 |
| Juan | 2017-08-01 04:15:00.000 |
| Juan | 2017-08-01 04:30:00.000 |
| Juan | 2017-08-01 04:45:00.000 |
| Juan | 2017-08-01 05:00:00.000 |
| Juan | 2017-08-01 05:15:00.000 |
| Juan | 2017-08-01 05:30:00.000 |
| Juan | 2017-08-01 05:45:00.000 |
| Juan | 2017-08-01 06:00:00.000 |
| Juan | 2017-08-01 06:15:00.000 |
| Juan | 2017-08-01 06:30:00.000 |
| Juan | 2017-08-01 06:45:00.000 |
| Juan | 2017-08-01 07:00:00.000 |
| Juan | 2017-08-01 07:15:00.000 |
| Juan | 2017-08-01 07:30:00.000 |
| Juan | 2017-08-01 07:45:00.000 |
| Juan | 2017-08-01 08:00:00.000 |
+-------+-------------------------+

You can use Recursive CTE to achieve the expected results.
Demo - http://sqlfiddle.com/#!6/821a5/11
with cte as
(
select Fname,StartDateTime
from Table1
union all
select Fname,DATEADD(mi,15,StartDateTime) as StartDateTime
from cte
where DATEADD(mi,15,StartDateTime) <= '2017-01-08 08:00:00:000'
)
select Fname, StartDateTime from cte;

Related

Calculate Median of a column (Days on Shelf) for different ranges of another column (Price)

I am trying to find the Median number of Carats of Gold for each day of the year. And I need to segregate these as per the price range of Gold in different columns.
The data:
Create table Daily_Gold_Rate
( S_No int, Dt datetime, Carat int, Gold_Rate numeric(18,2))
Insert into Daily_Gold_Rate values(1,'2013-01-03',18,155.00)
Insert into Daily_Gold_Rate values(2,'2013-01-03',22,190.50)
Insert into Daily_Gold_Rate values(3,'2013-01-03',24,202.23)
Insert into Daily_Gold_Rate values(4,'2013-01-03',18,400.00)
Insert into Daily_Gold_Rate values(5,'2013-01-03',22,480.50)
Insert into Daily_Gold_Rate values(6,'2013-01-03',24,671.23)
Insert into Daily_Gold_Rate values(7,'2013-01-04',18,153.00)
Insert into Daily_Gold_Rate values(8,'2013-01-04',22,191.00)
Insert into Daily_Gold_Rate values(9,'2013-01-04',24,202.25)
Insert into Daily_Gold_Rate values(10,'2013-01-04',18,351.00)
Insert into Daily_Gold_Rate values(11,'2013-01-04',22,892.00)
Insert into Daily_Gold_Rate values(12,'2013-01-04',24,1003.25)
Insert into Daily_Gold_Rate values(13,'2013-01-05',18,150.00)
Insert into Daily_Gold_Rate values(14,'2013-01-05',22,190.00)
Insert into Daily_Gold_Rate values(15,'2013-01-05',24,203.25)
Insert into Daily_Gold_Rate values(16,'2013-01-05',18,773.00)
Insert into Daily_Gold_Rate values(17,'2013-01-05',22,542.00)
Insert into Daily_Gold_Rate values(18,'2013-01-05',24,182.25)
Insert into Daily_Gold_Rate values(19,'2013-01-06',18,158.00)
Insert into Daily_Gold_Rate values(20,'2013-01-06',22,189.50)
Insert into Daily_Gold_Rate values(21,'2013-01-06',24,201.50)
Insert into Daily_Gold_Rate values(22,'2013-01-06',18,624.00)
Insert into Daily_Gold_Rate values(23,'2013-01-06',
Insert into Daily_Gold_Rate values(24,'2013-01-06',24,411.50)
Simple PERCENTILE_CONT shown below:
Select Dt, Carat, Gold_Rate, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY
Carat) OVER (PARTITION BY Carat) AS MedianCont
from Daily_Gold_Rate
order by Dt;Expected output is like this:
Returns this Data:
Dt | Carat | Gold_Rate | MedianCont
2013-01-03 00:00:00.000 | 18 | 155.00 | 18
2013-01-03 00:00:00.000 | 18 | 400.00 | 18
2013-01-03 00:00:00.000 | 22 | 480.50 | 22
2013-01-03 00:00:00.000 | 22 | 190.50 | 22
2013-01-03 00:00:00.000 | 24 | 202.23 | 24
2013-01-03 00:00:00.000 | 24 | 671.23 | 24
2013-01-04 00:00:00.000 | 24 | 202.25 | 24
2013-01-04 00:00:00.000 | 24 | 1003.25 | 24
2013-01-04 00:00:00.000 | 18 | 153.00 | 18
2013-01-04 00:00:00.000 | 18 | 351.00 | 18
2013-01-04 00:00:00.000 | 22 | 892.00 | 22
2013-01-04 00:00:00.000 | 22 | 191.00 | 22
2013-01-05 00:00:00.000 | 22 | 542.00 | 22
2013-01-05 00:00:00.000 | 22 | 190.00 | 22
2013-01-05 00:00:00.000 | 18 | 150.00 | 18
2013-01-05 00:00:00.000 | 18 | 773.00 | 18
2013-01-05 00:00:00.000 | 24 | 203.25 | 24
2013-01-05 00:00:00.000 | 24 | 182.25 | 24
2013-01-06 00:00:00.000 | 24 | 201.50 | 24
2013-01-06 00:00:00.000 | 24 | 411.50 | 24
2013-01-06 00:00:00.000 | 18 | 158.00 | 18
2013-01-06 00:00:00.000 | 18 | 624.00 | 18
2013-01-06 00:00:00.000 | 22 | 735.50 | 22
2013-01-06 00:00:00.000 | 22 | 189.50 | 22
I need to pivot on Gold Price Range. So, I am trying through this query:
with a as (
select year(Dt) as "year", month(Dt) as "month", day(Dt) as "day",
case when Gold_Rate / 200 < 5 then Gold_Rate / 200 else 5 end as
price_bucket,
PERCENTILE_CONT(.50) WITHIN GROUP (ORDER BY Carat) over (PARTITION BY
day(Dt), month(Dt)) as MED_days
from Daily_Gold_Rate
group by year(Dt), month(Dt), day(Dt), Gold_Rate / 200, Carat)
select "year", "month", "day",
[1] as "less than 400", [2] as "400-599", [3] as "600-799", [4] as "800-
999", [5] as "Over 1000"
from a pivot (max(MED_days) for price_bucket in ([1], [2], [3], [4], [5])) p
order by "year", "month", "day";
But, this is what I am getting, instead. :(
Year | Month | Day | 1-399 | 400-599 | 600-799| 800-999| Over 1000
2013 | 1 | 3 | NULL | 22 | NULL | NULL | NULL
2013 | 1 | 4 | NULL | NULL | NULL | NULL | 22
2013 | 1 | 5 | NULL | NULL | NULL | NULL | NULL
2013 | 1 | 6 | NULL | NULL | NULL | NULL | NULL
Pivoting before the percentile calculation:
select distinct
dt
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratelt400) OVER (PARTITION BY dt) ratelt400
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratebtwn400599) OVER (PARTITION BY dt) ratebtwn400599
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratebtwn600799) OVER (PARTITION BY dt) ratebtwn600799
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ratebtwn800999) OVER (PARTITION BY dt) ratebtwn800999
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY rateovr1000) OVER (PARTITION BY dt) rateovr1000
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY caratlt400) OVER (PARTITION BY dt) caratlt400
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY caratbtwn400599) OVER (PARTITION BY dt) caratbtwn400599
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY caratbtwn600799) OVER (PARTITION BY dt) caratbtwn600799
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY caratbtwn800999) OVER (PARTITION BY dt) caratbtwn800999
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY caratovr1000) OVER (PARTITION BY dt) caratovr1000
from (
SELECT
DATEADD(day, DATEDIFF(day, 0, dt), 0) dt
, CASE WHEN Gold_Rate < 400 THEN Gold_Rate END ratelt400
, CASE WHEN Gold_Rate BETWEEN 400 AND 599 THEN Gold_Rate END ratebtwn400599
, CASE WHEN Gold_Rate BETWEEN 600 AND 799 THEN Gold_Rate END ratebtwn600799
, CASE WHEN Gold_Rate BETWEEN 800 AND 999 THEN Gold_Rate END ratebtwn800999
, CASE WHEN Gold_Rate >= 1000 THEN Gold_Rate END rateovr1000
, CASE WHEN Gold_Rate < 400 THEN carat END caratlt400
, CASE WHEN Gold_Rate BETWEEN 400 AND 599 THEN carat END caratbtwn400599
, CASE WHEN Gold_Rate BETWEEN 600 AND 799 THEN carat END caratbtwn600799
, CASE WHEN Gold_Rate BETWEEN 800 AND 999 THEN carat END caratbtwn800999
, CASE WHEN Gold_Rate >= 1000 THEN carat END caratovr1000
FROM Daily_Gold_Rate
) d
GO
dt | ratelt400 | ratebtwn400599 | ratebtwn600799 | ratebtwn800999 | rateovr1000 | caratlt400 | caratbtwn400599 | caratbtwn600799 | caratbtwn800999 | caratovr1000
:------------------ | --------: | -------------: | -------------: | -------------: | ----------: | ---------: | --------------: | --------------: | --------------: | -----------:
03/01/2013 00:00:00 | 190.5 | 440.25 | 671.23 | null | null | 22 | 20 | 24 | null | null
04/01/2013 00:00:00 | 196.625 | null | null | 892 | 1003.25 | 20 | null | null | 22 | 24
05/01/2013 00:00:00 | 186.125 | 542 | 773 | null | null | 23 | 22 | 18 | null | null
06/01/2013 00:00:00 | 189.5 | 411.5 | 624 | null | null | 22 | 24 | 18 | null | null
dbfiddle here
Below are some working queries for values by either carats or rates:
revised answer:
Rates by Day
SELECT DISTINCT
DATEADD(day, DATEDIFF(day, 0, dt), 0) dt
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate < 400 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) lt400
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 400 AND 599 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn400599
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 600 AND 799 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn600799
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 800 AND 999 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn800999
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate >= 1000 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) ovr1000
FROM Daily_Gold_Rate
GO
dt | lt400 | btwn400599 | btwn600799 | btwn800999 | ovr1000
:------------------ | ------: | ---------: | ---------: | ---------: | ------:
03/01/2013 00:00:00 | 190.5 | 440.25 | 671.23 | null | null
04/01/2013 00:00:00 | 196.625 | null | null | 892 | 1003.25
05/01/2013 00:00:00 | 186.125 | 542 | 773 | null | null
06/01/2013 00:00:00 | 189.5 | 411.5 | 624 | null | null
Carats by Day
SELECT DISTINCT
DATEADD(day, DATEDIFF(day, 0, dt), 0) dt
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate < 400 THEN carat END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) lt400
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 400 AND 599 THEN carat END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn400599
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 600 AND 799 THEN carat END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn600799
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 800 AND 999 THEN carat END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) btwn800999
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate >= 1000 THEN carat END) OVER (PARTITION BY DATEADD(day, DATEDIFF(day, 0, dt), 0)) ovr1000
FROM Daily_Gold_Rate
GO
dt | lt400 | btwn400599 | btwn600799 | btwn800999 | ovr1000
:------------------ | ----: | ---------: | ---------: | ---------: | ------:
03/01/2013 00:00:00 | 22 | 20 | 24 | null | null
04/01/2013 00:00:00 | 20 | null | null | 22 | 24
05/01/2013 00:00:00 | 23 | 22 | 18 | null | null
06/01/2013 00:00:00 | 22 | 24 | 18 | null | null
dbfiddle here
By Month: FYI only
SELECT DISTINCT
DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0) dt
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate < 400 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0)) lt400
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 400 AND 599 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0)) btwn400599
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 600 AND 799 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0)) btwn600799
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate BETWEEN 800 AND 999 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0)) btwn800999
, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN Gold_Rate >= 1000 THEN Gold_Rate END) OVER (PARTITION BY DATEADD(MONTH, DATEDIFF(MONTH, 0, dt), 0)) ovr1000
FROM Daily_Gold_Rate
GO
dt | lt400 | btwn400599 | btwn600799 | btwn800999 | ovr1000
:------------------ | -----: | ---------: | ---------: | ---------: | ------:
01/01/2013 00:00:00 | 190.25 | 446 | 671.23 | 892 | 1003.25
dbfiddle here

How to add biweekly periods in a year to a table? (SQL Server)

This SQL Statement is not enough to add the right biweekly periods.
What i want is something like this:
Column 1 (period) / Column 2 (start period) / Column 3 (end period)
20160115 / 2016-01-01 / 2016-01-15
20160131 / 2016-01-15 / 2016-01-31
20160215 / 2016-02-01 / 2016-02-15
20160229 / 2016-02-16 / 2016-02-29
and so on...
This is what I have now, which is wrong / incomplete.
the value is being inserted correctly in the table columns but the gap is wrong since months don't have the same amount of days
Can someone help me? Thank you very much!
DECLARE #d date= '20020101'
WHILE #d<'20030101'
BEGIN
INSERT INTO [TABLE]
VALUES ((SELECT CONVERT(VARCHAR(8), #d, 112) AS [YYYYMMDD]), #d, #d, 'Fechado', '1')
SET #d=DATEADD(DAY,15,#d)
END
using a common table expression for an adhoc numbers table, and union all for two queries with some date math using dateadd():
declare #startdate date = '2016-01-01'
declare #enddate date = '2016-12-31'
;with cte as (
select top (datediff(month,#startdate,#enddate)+1)
Month = convert(date,dateadd(month,row_number() over (order by (select 1))-1,#startdate))
from master..spt_values
order by month
)
select
Period = convert(char(8), dateadd(day,14,month), 112)
, PeriodStart = month
, PeriodEnd = dateadd(day,14,month)
from cte
union all
select
Period = convert(char(8), eomonth(month), 112)
, PeriodStart = dateadd(day,14,month)
, PeriodEnd = eomonth(month)
from cte
order by period
rextester demo: http://rextester.com/BSYIXW7864
returns:
+----------+-------------+------------+
| Period | PeriodStart | PeriodEnd |
+----------+-------------+------------+
| 20160115 | 2016-01-01 | 2016-01-15 |
| 20160131 | 2016-01-15 | 2016-01-31 |
| 20160215 | 2016-02-01 | 2016-02-15 |
| 20160229 | 2016-02-15 | 2016-02-29 |
| 20160315 | 2016-03-01 | 2016-03-15 |
| 20160331 | 2016-03-15 | 2016-03-31 |
| 20160415 | 2016-04-01 | 2016-04-15 |
| 20160430 | 2016-04-15 | 2016-04-30 |
| 20160515 | 2016-05-01 | 2016-05-15 |
| 20160531 | 2016-05-15 | 2016-05-31 |
| 20160615 | 2016-06-01 | 2016-06-15 |
| 20160630 | 2016-06-15 | 2016-06-30 |
| 20160715 | 2016-07-01 | 2016-07-15 |
| 20160731 | 2016-07-15 | 2016-07-31 |
| 20160815 | 2016-08-01 | 2016-08-15 |
| 20160831 | 2016-08-15 | 2016-08-31 |
| 20160915 | 2016-09-01 | 2016-09-15 |
| 20160930 | 2016-09-15 | 2016-09-30 |
| 20161015 | 2016-10-01 | 2016-10-15 |
| 20161031 | 2016-10-15 | 2016-10-31 |
| 20161115 | 2016-11-01 | 2016-11-15 |
| 20161130 | 2016-11-15 | 2016-11-30 |
| 20161215 | 2016-12-01 | 2016-12-15 |
| 20161231 | 2016-12-15 | 2016-12-31 |
+----------+-------------+------------+
Create a function, then call in your query:
Function:
create FUNCTION advance_biweekly
(
#current date
)
RETURNS date
AS
BEGIN
--if it's the first day of month, advance two weeks
if (DAY(#current) = 1)
return DATEADD(WEEK, 2, #current)
else
--if its last day of month, advance one day
if (DAY(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #current) + 1, 0))) = DAY(#current))
return DATEADD(DAY, 1, #current)
else
--else, it's the middle of the month, go to end
return dateadd(month,((YEAR(#current)-1900)*12)+MONTH(#current)-1,DAY(DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #current) + 1, 0)))-1)
return null
END
GO
code:
DECLARE #d date= '20020101'
WHILE #d<'20030101'
begin
set #d = dbo.advance_biweekly(#d)
print #d
end
result:
2002-01-15
2002-01-31
2002-02-01
2002-02-15
2002-02-28
2002-03-01
2002-03-15
2002-03-31
2002-04-01
2002-04-15
2002-04-30
2002-05-01

sql server set consumption value based on first older record

i have a table containing sensor data in Sql server 2014, with multiple sensors each with its own sensor id. I want to calculate and set / update the consumption field by calculating the consumption between the current value and subtracting the previous value based on the logdate for a single sensorid, for example sensorid = 8555.
Current table structure for sensorid 8555:
+----------+-------------------------+---------+-------------+
| SensorId | LogDate | Value | Consumption |
+----------+-------------------------+---------+-------------+
| 8555 | 2016-10-03 13:00:00.000 | 0.00000 | Null |
| 2478 | 2016-11-09 09:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 10:00:00.000 | 0.00000 | 0.00000 |
| 8555 | 2016-10-03 14:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 15:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 16:00:00.000 | 1.00000 | Null |
| 8555 | 2016-10-03 17:00:00.000 | 2.00000 | Null |
| 8555 | 2016-10-03 18:00:00.000 | 2.00000 | Null |
| 8555 | 2016-10-03 19:00:00.000 | 4.00000 | Null |
+----------+-------------------------+---------+-------------+
The end result should be this:
+----------+-------------------------+---------+-------------+
| SensorId | LogDate | Value | Consumption |
+----------+-------------------------+---------+-------------+
| 8555 | 2016-10-03 13:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 09:00:00.000 | 0.00000 | 0.00000 |
| 2478 | 2016-11-09 10:00:00.000 | 0.00000 | 0.00000 |
| 8555 | 2016-10-03 14:00:00.000 | 1.00000 | 1.00000 |
| 8555 | 2016-10-03 15:00:00.000 | 1.00000 | 0.00000 |
| 8555 | 2016-10-03 16:00:00.000 | 1.00000 | 0.00000 |
| 8555 | 2016-10-03 17:00:00.000 | 2.00000 | 1.00000 |
| 8555 | 2016-10-03 18:00:00.000 | 2.00000 | 0.00000 |
| 8555 | 2016-10-03 19:00:00.000 | 4.00000 | 2.00000 |
+----------+-------------------------+---------+-------------+
I have scoured google for a solution, and i could only find a version of the answer for Mysql, which i do not know how to convert to sql server.
I know that the update statement should contain LAG and probably use CTEs but i haven't found an easy to understand explanation on how to use those on the web.
Any help would be appreciated.
BEGIN TRAN
CREATE TABLE #T (SensorId INT , LogDate DATETIME, Value DECIMAL(15,4), Consumption DECIMAL(15,4))
INSERT INTO #T
SELECT 8555 , '2016-10-03 13:00:00.000' ,0.00000 ,Null UNION ALL
SELECT 2478 , '2016-11-09 09:00:00.000' ,0.00000 ,0.0000 UNION ALL
SELECT 2478 , '2016-11-09 10:00:00.000' ,0.00000 ,0.0000 UNION ALL
SELECT 8555 , '2016-10-03 14:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 15:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 16:00:00.000' ,1.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 17:00:00.000' ,2.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 18:00:00.000' ,2.00000 ,Null UNION ALL
SELECT 8555 , '2016-10-03 19:00:00.000' ,4.00000 ,Null
GO
SELECT * FROM #T
;with CTE as (
select SensorId,
ROW_NUMBER() OVER (Order By SensorId,LogDate) Rownum,
LogDate, Value
FROM #T
)
UPDATE #T SET Consumption= ISNULL(curr.Value - prev.Value,0)
from #T INNER JOIN
CTE curr ON #T.LogDate= curr.LogDate
left join CTE prev on curr.Rownum = (prev.Rownum + 1)
SELECT * FROM #T
ROLLBACK TRAN

How to prevent clustered index scan?

I am operating in an SAP B1 database, so modifying the structure of the database is not allowed.
I have a table with 4 columns.
Table name: HLD1
Column Name Type
1 HldCode nvarchar
2 StrDate datetime
3 EndDate datetime
4 Rmrks nvarchar
Some of the data looks like this:
HldCode StrDate EndDate Rmrks
2016 Holidays 2016-09-05 00:00:00.000 2016-09-05 00:00:00.000 Labor Day
2016 Holidays 2016-11-24 00:00:00.000 2016-11-25 00:00:00.000 Thankgiving
2016 Holidays 2016-12-26 00:00:00.000 2016-12-26 00:00:00.000 Christmas
2017 Holidays 2017-01-02 00:00:00.000 2017-01-02 00:00:00.000 New Years Day
2017 Holidays 2017-05-29 00:00:00.000 2017-05-29 00:00:00.000 Memorial Day
2017 Holidays 2017-07-04 00:00:00.000 2017-07-04 00:00:00.000 Indepenance Day
Notice that there is no primary key in this table.
I have a function that I have created to find the number of days between two dates, excluding holidays (as provided by the HLD1 table above) and weekends. While the function works as expected, it also takes ~.75 seconds per row that it's used in, and we're attempting to return 50000 rows to later summarize in Crystal Reports.
The piece of the function that is referencing the HLD1 table (and is causing the Clustered Index in the execution plan) looks like this:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare #START DATE
declare #END DATE
)
RETURNS INT
AS
BEGIN
SELECT #AddDays =
(select sum(datediff(dd,strdate,enddate) + 1) from hld1
where strdate between #START and #END)
+
(SELECT
(DATEDIFF(wk, #Start, #End) * 2)
+(CASE WHEN DATENAME(dw, #Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, #End) = 'Saturday' THEN 1 ELSE 0 END))
RETURN #AddDays
END
GO
Specifically, the first part. #START and #END are the parameters passed to the function.
When I check the execution plan for the function, everything looks speedy quick, except for this piece. It gives me the following info:
All of the sources that I've found on the web about how to prevent or fix this kind of slowdown suggest adding indices, not referencing certain columns, etc, but since I can't modify the database, I've been unable to find any methodology on how to help in my situation.
Any suggestions?
EDIT 1:
Added Table Schema info from SQL Management
EDIT 2:
Added full text of function, just in case:
CREATE FUNCTION [dbo].[dateDiffHolidays] (
#startdaytime DATETIME,
#enddaytime DATETIME
)
RETURNS INT
AS
BEGIN
DECLARE #answer INT, #START Date, #END Date, #AddDays int
SET #answer = 0
-- Strip Times
SELECT #START = dateadd(dd,0, datediff(dd,0,#StartDayTime)), #END =
dateadd(dd,0, datediff(dd,0,#EndDayTime))
SELECT #AddDays = (select sum(datediff(dd,strdate,enddatE) + 1) from hld1
where strdate between #START and #END
order by HldCode, StrDate, EndDate) + (
SELECT
(DATEDIFF(wk, #Start, #End) * 2)
+(CASE WHEN DATENAME(dw, #Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, #End) = 'Saturday' THEN 1 ELSE 0 END))
-- handle end conditions
DECLARE #firstWeekDayInRange datetime, #lastWeekDayInRange datetime;
SELECT #firstWeekDayInRange = #START, #lastWeekDayInRange = #END
WHILE #firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate)
as date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#firstWeekDayInRange) in (1,7)
BEGIN
SELECT #firstWeekDayInRange =
CASE
WHEN #firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,#firstWeekDayInRange) in (1,7)
THEN dateadd(DAY,1,#firstWeekDayInRange)
ELSE #firstWeekDayInRange
END
END
WHILE #lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) as ResultDate
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,#lastWeekDayInRange) in (1,7)
BEGIN
SELECT #lastWeekDayInRange =
CASE
WHEN #lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as
date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate)
+ 1)
or datepart(dw,#lastWeekDayInRange) in (1,7)
THEN dateadd(DAY,-1,#lastWeekDayInRange)
ELSE #lastWeekDayInRange
END
END
-- add one day to answer (to count Friday) if enddate was on a weekend
SELECT #answer = #answer +
CASE
-- triggered if start and end date are on same weekend
WHEN dateDiff(DAY,#firstWeekDayInRange,#lastWeekDayInRange) < 0 THEN
(#answer * -1)
-- otherwise count the days and substract 2 days per weekend in between dates
ELSE (DateDiff(DAY, #firstWeekDayInRange, #lastWeekDayInRange) - #AddDays)
END
RETURN #answer
END
GO
You can try by add an ORDER BY clause.
CREATE TABLE HLD1
(
HldCode nvarchar(20),
StrDate datetime,
EndDate datetime,
Rmrks nvarchar(50)
)
create unique index id_hld1 on HLD1 (HldCode, StrDate, EndDate);
GO
INSERT INTO HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
GO
6 rows affected
DECLARE #StrDate datetime = '2017-01-01';
DECLARE #EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= #StrDate
AND EndDate < #EndDate;
set statistics profile off;
GO
Output:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
> :--- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
> 3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= #StrDate<br>AND EndDate < #EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.0032886 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
> 3 | 1 | |--Table Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[#StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[#EndDate])) | 1 | 2 | 1 | Table Scan | Table Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[#StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[#EndDate]) | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001636 | 99 | 0.0032886 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
DECLARE #StrDate datetime = '2017-01-01';
DECLARE #EndDate datetime = '2018-01-01'
set statistics profile on;
SELECT HldCode, StrDate, EndDate, Rmrks
FROM HLD1
WHERE StrDate >= #StrDate
AND EndDate < #EndDate
ORDER BY HldCode, StrDate, EndDate;
set statistics profile off;
GO
Output:
HldCode | StrDate | EndDate | Rmrks
:------------ | :------------------ | :------------------ | :--------------
2017 Holidays | 02/01/2017 00:00:00 | 02/01/2017 00:00:00 | New Years Day
2017 Holidays | 29/05/2017 00:00:00 | 29/05/2017 00:00:00 | Memorial Day
2017 Holidays | 04/07/2017 00:00:00 | 04/07/2017 00:00:00 | Indepenance Day
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
:--- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :----------- | :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
3 | 1 | SELECT HldCode, StrDate, EndDate, Rmrks<br>from HLD1<br>WHERE StrDate >= #StrDate<br>AND EndDate < #EndDate<br>ORDER BY HldCode, StrDate, EndDate | 1 | 1 | 0 | <em>null</em> | <em>null</em> | <em>null</em> | <em>null</em> | 1 | <em>null</em> | <em>null</em> | <em>null</em> | 0.00658116 | <em>null</em> | <em>null</em> | SELECT | False | <em>null</em>
3 | 1 | |--Nested Loops(Inner Join, OUTER REFERENCES:([Bmk1000])) | 1 | 2 | 1 | Nested Loops | Inner Join | OUTER REFERENCES:([Bmk1000]) | <em>null</em> | 1 | 0 | 4.18E-06 | 99 | 0.00658116 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
3 | 1 | |--Index Scan(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[#StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[#EndDate]) ORDERED FORWARD) | 1 | 3 | 2 | Index Scan | Index Scan | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[id_hld1]), WHERE:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate]>=[#StrDate] AND [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate]<[#EndDate]) ORDERED FORWARD | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | 1 | 0.003125 | 0.0001636 | 55 | 0.0032886 | [Bmk1000], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[HldCode], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[StrDate], [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[EndDate] | <em>null</em> | PLAN_ROW | False | 1
3 | 3 | |--RID Lookup(OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD) | 1 | 5 | 2 | RID Lookup | RID Lookup | OBJECT:([fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1]), SEEK:([Bmk1000]=[Bmk1000]) LOOKUP ORDERED FORWARD | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | 1 | 0.003125 | 0.0001581 | 61 | 0.0032831 | [fiddle_9f66021924d842d39e112d909afc0794].[dbo].[HLD1].[Rmrks] | <em>null</em> | PLAN_ROW | False | 1
dbfiddle here
UPDATE
As far as you need a stored procedure, you can try;
WITH (INDEX(IndexName))
CREATE FUNCTION [dbo].[dateDiffHolidays] (#START DATE, #END DATE)
RETURNS INT
AS
BEGIN
DECLARE #AddDays int;
SELECT #AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1)
FROM hld1 WITH (INDEX(HLD1_PRIMARY))
WHERE StrDate BETWEEN #START AND #END)
+
(SELECT (DATEDIFF(wk, #Start, #End) * 2)
+ (CASE WHEN DATENAME(dw, #Start) = 'Sunday' THEN 1 ELSE 0 END)
+ (CASE WHEN DATENAME(dw, #End) = 'Saturday' THEN 1 ELSE 0 END))
RETURN #AddDays
END
DECLARE #StrDate datetime = '2017-01-01';
DECLARE #EndDate datetime = '2018-01-01';
DECLARE #NumDays int = 0;
set statistics profile on;
EXEC #NumDays = [dbo].[dateDiffHolidays] #StrDate, #EndDate;
set statistics profile off;
SELECT #NumDays;
Rows | Executes | StmtText | StmtId | NodeId | Parent | PhysicalOp | LogicalOp | Argument | DefinedValues | EstimateRows | EstimateIO | EstimateCPU | AvgRowSize | TotalSubtreeCost | OutputList | Warnings | Type | Parallel | EstimateExecutions
:--- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -----: | -----: | -----: | :--------------- | :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------- | :--------- | :---------- | ---------: | :--------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :------- | :------- | :------- | :-----------------
1 | 1 | SELECT #AddDays = (SELECT sum(datediff(dd, StrDate, EndDate) + 1) <br> FROM hld1 WITH (INDEX(HLD1_PRIMARY))<br> WHERE StrDate BETWEEN #START AND #END) <br> + <br> (SELECT (DATEDIFF(wk, #Start, #End) * 2)<br> + (CASE WHEN DATENAME(dw, #Start) = 'Sunday' THEN 1 ELSE 0 END)<br> + (CASE WHEN DATENAME(dw, #End) = 'Saturday' THEN 1 ELSE 0 END)) | 1 | 1 | 0 | null | null | null | null | 1 | null | null | null | 0.00329658 | null | null | SELECT | False | null
0 | 0 | |--Compute Scalar(DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[#START],0),CONVERT_IMPLICIT(datetimeoffset(7),[#END],0))*(2)+CASE WHEN datename(weekday,[#START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[#END])=N'Saturday' THEN (1) ELSE (0) END))) | 1 | 2 | 1 | Compute Scalar | Compute Scalar | DEFINE:([Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[#START],0),CONVERT_IMPLICIT(datetimeoffset(7),[#END],0))*(2)+CASE WHEN datename(weekday,[#START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[#END])=N'Saturday' THEN (1) ELSE (0) END)) | [Expr1006]=[Expr1003]+(datediff(week,CONVERT_IMPLICIT(datetimeoffset(7),[#START],0),CONVERT_IMPLICIT(datetimeoffset(7),[#END],0))*(2)+CASE WHEN datename(weekday,[#START])=N'Sunday' THEN (1) ELSE (0) END+CASE WHEN datename(weekday,[#END])=N'Saturday' THEN (1) ELSE (0) END) | 1 | 0 | 1E-07 | 11 | 0.00329658 | [Expr1006] | null | PLAN_ROW | False | 1
0 | 0 | |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END)) | 1 | 3 | 2 | Compute Scalar | Compute Scalar | DEFINE:([Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END) | [Expr1003]=CASE WHEN [Expr1012]=(0) THEN NULL ELSE [Expr1013] END | 1 | 0 | 0 | 11 | 0.00329648 | [Expr1003] | null | PLAN_ROW | False | 1
1 | 1 | |--Stream Aggregate(DEFINE:([Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]))) | 1 | 4 | 3 | Stream Aggregate | Aggregate | null | [Expr1012]=COUNT_BIG([Expr1007]), [Expr1013]=SUM([Expr1007]) | 1 | 0 | 2.3E-06 | 11 | 0.00329648 | [Expr1012], [Expr1013] | null | PLAN_ROW | False | 1
0 | 0 | |--Compute Scalar(DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1))) | 1 | 5 | 4 | Compute Scalar | Compute Scalar | DEFINE:([Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1)) | [Expr1007]=datediff(day,[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate],[fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate])+(1) | 3 | 0 | 3E-07 | 11 | 0.00329418 | [Expr1007] | null | PLAN_ROW | False | 1
3 | 1 | |--Index Scan(OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[#START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[#END])) | 1 | 6 | 5 | Index Scan | Index Scan | OBJECT:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[HLD1_PRIMARY]), WHERE:([fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]>=[#START] AND [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate]<=[#END]), FORCEDINDEX | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | 3 | 0.003125 | 0.0001636 | 23 | 0.0032886 | [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[StrDate], [fiddle_c7abc2eb9b3f49599be6803069c6aa56].[dbo].[HLD1].[EndDate] | null | PLAN_ROW | False | 1
| (No column name) |
| ---------------: |
| 108 |
dbfiddle here
Since the HLD1_PRIMARY index includes the following columns: HldCode, StrDate, EndDate try adding a condition that filters by HldCode to your aggregate select.
For example if you only want to include rows with HldCode = '2016 Holidays' and HldCode = '2017 Holidays':
CREATE FUNCTION [dbo].[dateDiffHolidays] (
declare #START DATE
declare #END DATE
)
RETURNS INT
AS
BEGIN
SELECT #AddDays = (
select
sum(datediff(dd,strdate,enddate) + 1)
from
hld1
where
HldCode in ('2016 Holidays', '2017 Holidays')
and strdate between #START and #END
)
+
(
SELECT
(DATEDIFF(wk, #Start, #End) * 2)
+(CASE WHEN DATENAME(dw, #Start) = 'Sunday' THEN 1 ELSE 0 END)
+(CASE WHEN DATENAME(dw, #End) = 'Saturday' THEN 1 ELSE 0 END)
)
RETURN #AddDays
END
UPDATE
You won't get any better than an Index Seek for that particular query.
Then, after looking at the full code, I suspect the issue is not on that query but on the while loops used to calculate #firstWeekDayInRange and #lastWeekDayInRange:
WHILE #firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date) as ResultDate
from
HLD1 s
join cteTally t
on t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw, #firstWeekDayInRange) in (1,7)
BEGIN
SELECT #firstWeekDayInRange =
CASE
WHEN #firstWeekDayInRange in (
select
cast(DATEADD(day, t.N - 1, StrDate) as date)
from
HLD1 s
join cteTally t on
t.N <= DATEDIFF(day, StrDate, EndDate) + 1
)
or datepart(dw,#firstWeekDayInRange) in (1,7) THEN
dateadd(DAY,1,#firstWeekDayInRange)
ELSE
#firstWeekDayInRange
END
END
At best these queries result in the Clustered Index Scan and at worst on a Table Scan on the HLD1 table for each iteration of the loop, and we don't know much about cteTally, so it could be worse.
Do you must use a function? how about create a date calendar kind of table?
CREATE TABLE #Datecalendar
(DateId DATE PRIMARY KEY
, IsWeekend BIT DEFAULT 0
, IsHoliday BIT DEFAULT 0
);
CREATE TABLE #HLD1
(HldCode nvarchar(20)
, StrDate datetime
, EndDate datetime
, Rmrks nvarchar(50)
);
INSERT INTO #HLD1
VALUES ('2016 Holidays', '2016-09-05 00:00:00.000', '2016-09-05 00:00:00.000', 'Labor Day'),
('2016 Holidays', '2016-11-24 00:00:00.000', '2016-11-25 00:00:00.000', 'Thanksgiving'),
('2016 Holidays', '2016-12-26 00:00:00.000', '2016-12-26 00:00:00.000', 'Christmas'),
('2017 Holidays', '2017-01-02 00:00:00.000', '2017-01-02 00:00:00.000', 'New Years Day'),
('2017 Holidays', '2017-05-29 00:00:00.000', '2017-05-29 00:00:00.000', 'Memorial Day'),
('2017 Holidays', '2017-07-04 00:00:00.000', '2017-07-04 00:00:00.000', 'Independence Day');
WITH cte AS (SELECT CAST('2016-01-01' AS DATE) AS DateId
UNION ALL
SELECT DATEADD(dd, 1, DateId)
FROM cte
WHERE DATEADD(dd, 1, DateId) <= '2019-01-01'
)
INSERT INTO #Datecalendar
(DateId
, IsWeekend
, IsHoliday
)
SELECT DateId
,CASE WHEN DATEPART(WEEKDAY,DateId) =1 OR DATEPART(WEEKDAY,DateId) = 7 THEN 1 ELSE 0 END
,CASE WHEN h.HldCode IS NOT NULL THEN 1 ELSE 0 END
FROM cte cte
LEFT JOIN #HLD1 h
ON cte.DateId BETWEEN h.StrDate AND h.EndDate
OPTION (MAXRECURSION 0);
SELECT COUNT(*)
FROM #Datecalendar
WHERE DateId BETWEEN '2016-05-06' AND '2017-02-24'
AND IsWeekend = 0
AND IsHoliday = 0;
DROP TABLE #HLD1;
DROP TABLE #Datecalendar;

SQL query - Find daily Min, Max and times when the Min and Max values occurred

I have a table which looks like this one:
_Datetime | Value
2015-05-01 06:00:00 | 12.3
2015-05-01 06:20:00 | 12.5
2015-05-01 06:40:00 | 12.3
2015-05-01 07:00:00 | 13.5
2015-05-01 07:20:00 | 14.5
2015-05-01 07:40:00 | 14.3
2015-05-01 08:00:00 | 18.2
2015-05-01 08:20:00 | 15.0
2015-05-01 08:40:00 | 15.0
2015-05-02 06:00:00 | 19.2
2015-05-02 06:20:00 | 7.3
2015-05-02 06:40:00 | 11.4
2015-05-02 07:00:00 | 9.5
2015-05-02 07:20:00 | 7.6
2015-05-02 07:40:00 | 6.6
2015-05-02 08:00:00 | 10.4
2015-05-02 08:20:00 | 19.3
2015-05-02 08:40:00 | 15.4
2015-05-03 06:00:00 | 8.7
2015-05-03 06:20:00 | 8.6
2015-05-03 06:40:00 | 8.6
2015-05-03 07:00:00 | 21.5
2015-05-03 07:20:00 | 12.4
2015-05-03 07:40:00 | 7.3
2015-05-03 08:00:00 | 10.8
2015-05-03 08:20:00 | 12.5
2015-05-03 08:40:00 | 10.6
I would like to:
-select min, max and avg for each day
-select time for which the min and max occurred (I want to select only one row - first that occurred)
_Date | _Min | _MinTime | _Max | _MaxTime | Avg |
2015-05-01 | 12.3 | 06:00:00 | 18.2 | 08:00:00 | 14.18 |
2015-05-02 | 6.6 | 07:40:00 | 19.3 | 08:20:00 | 11.86 |
2015-05-03 | 7.3 | 07:40:00 | 21.5 | 07:00:00 | 11.22 |
I can easily get the min, max and avg but I got stuck with the time.
Try this - should return only one set of values per day. (Edited - added average, rounded it as suggested in comments).
; with cte as (
select *
, cast (_datetime as date) as [DateFormat]
, cast (_datetime as time) as [TimeFormat]
, row_number() over (partition by cast (_datetime as date) order by Value, _datetime) RNmin
, row_number() over (partition by cast (_datetime as date) order by Value desc, _datetime) RNmax
, Avg(value) over (partition by cast (_datetime as date)) as AvgVal
from MyTable
)
select a.DateFormat, a.value as MinValue, a.TimeFormat as MinTime
, b.value as MaxValue, b.TimeFormat as MaxTime
, cast(a.AvgVal as decimal(5,2)) as AverageValue
from Cte a
join cte b
on a.DateFormat = B.Dateformat and a.RNmin = 1 and b.RNmax = 1
SELECT x.*
, min.datetime min_time
, max.datetime max_time
FROM
( SELECT DATE(datetime) date
, MIN(value) min_value
, MAX(value) max_value
, AVG(value)
FROM my_table
GROUP
BY DATE(datetime)
) x
JOIN my_table min
ON DATE(min.datetime) = x.date
AND min.value = x.min_value
JOIN my_table max
ON DATE(max.datetime) = x.date
AND max.value = x.max_value;
--
this will get you the mins.....
create table table1
(_date datetime,
value float);
insert into table1 values (
'2015-05-01 06:00:00', 12.3)
,('2015-05-01 06:20:00',12.5)
<('2015-05-02 06:00:00', 19.2)
,('2015-05-02 06:20:00', 7.3)
select * from
( select min(value) as min1 ,cast(_date as date) as date1 from table1 group by cast(_date as date) )
as i1
inner join
(select cast(_date as date) _date1,value, cast(_date as time) _time1 from table1) as i2
on i1.min1= i2.value and
cast(i2._date1 as date)= i1.date1

Resources