How to sum values in column, grouped by date in SQL Server - sql-server

I have the following:
Name |Dur |DateTime
Bill |10 |8/12/2018 07:00:00
Sue |4 |8/10/2018 09:00:00
Joe |23 |8/13/2018 07:15:00
Bill |13 |8/13/2018 09:15:00
Sue |4 |8/10/2018 19:00:00
Sue |23 |8/13/2018 07:25:00
Bill |10 |8/12/2018 15:18:00
Sue |4 |8/11/2018 13:00:00
Joe |23 |8/14/2018 07:15:00
I want to end up with:
Name |Tot|Date
Bill |20 |8/12/2018
Bill |13 |8/13/2018
Sue | 8 |8/10/2018
Sue | 4 |8/11/2018
Joe |23 |8/13/2018
Joe |23 |8/14/2018
So basically, sum the Dur each day, for each Name.
I tried:
SELECT
Name,
SUM([Dur]) AS Tot,
(CONVERT(VARCHAR, [DateTime], 101)) AS sDateTime
FROM
DailyTransactions
GROUP BY
DATEADD(day, DATEDIFF(day, 0, DateTime), 0), Name

You were close... just group by the same logic you used on your column conversion.
SELECT
Name,
SUM([Dur]) AS Tot,
(CONVERT(VARCHAR, [DateTime], 101))AS sDateTime
FROM
DailyTransactions
GROUP BY
(CONVERT(VARCHAR, [DateTime], 101))

This should do your work
SELECT
Name,
SUM([Dur]) AS Tot,
cast(DateTime as Date) AS Date
FROM
DailyTransactions
GROUP BY
cast(DateTime as Date), Name

Related

SQL Server min max with intermediate record

I am sorry if this question has been answered before, but I cannot find it. Perhaps of my bad keywords.
I have this table:
CREATE TABLE test1
(
Employee VARCHAR(10),
Band VARCHAR(10),
StartDate DATE,
EndDate DATE
)
INSERT INTO test1
VALUES ('Emp1', 'Band1', '2009-01-01', '2010-12-31'),
('Emp1', 'Band1', '2011-01-01', '2012-12-31'),
('Emp1', 'Band1', '2013-01-01', '2013-08-31'),
('Emp1', 'Band2', '2013-09-01', '2013-12-31'),
('Emp1', 'Band2', '2014-01-01', '2014-06-30'),
('Emp1', 'Band1', '2014-07-01', '2014-12-31'),
('Emp1', 'Band1', '2015-01-01', '2018-08-31'),
('Emp2', 'Band1', '2012-01-01', '2014-12-31'),
('Emp2', 'Band1', '2015-01-01', '2018-03-31')
Results in this table:
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2010-12-31
Emp1 Band1 2011-01-01 2012-12-31
Emp1 Band1 2013-01-01 2013-08-31
Emp1 Band2 2013-09-01 2013-12-31
Emp1 Band2 2014-01-01 2014-06-30
Emp1 Band1 2014-07-01 2014-12-31
Emp1 Band1 2015-01-01 2018-08-31
Emp2 Band1 2012-01-01 2014-12-31
Emp2 Band1 2015-01-01 2018-03-31
What I want to create is a result table grouping each employee with the band and the minimum start date and maximum end date, but when an intermediate record (band) exist in the middle between the similar band, the end date should be capped and the start date of the next group in similar band should reset again.
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2013-08-31
Emp1 Band2 2013-09-01 2014-06-30
Emp1 Band1 2014-07-01 2018-08-31
Emp2 Band1 2012-01-01 2018-03-31
I have tried CTE to get the max and min of each band and compare it with the original table, but I still fail. I also tried using lead and lag but still fail.
Nice to have
Supposed the second record's EndDate is 2012-02-01, I prefer the result is still one record for the first Band1 group.
Employee Band StartDate EndDate
----------------------------------------
Emp1 Band1 2009-01-01 2013-08-31
With each row of the result only differing in Band, I would able to figure out how long an employee has stayed in a certain band before moving to other band (different band).
But it is something nice to have.
Apparently you want to group rows whenever an employee changes band. This is straight forward with window functions. The following solution adds a "change" flag to rows whenever the band changes. Be advised that it ignores gaps. Add DATEDIFF check to the case statement to find the actual amount of time the person was associated with a band:
DECLARE #test1 TABLE(
Employee VARCHAR(10),
Band VARCHAR(10),
StartDate DATE,
EndDate DATE
);
INSERT INTO #test1 VALUES
('Emp1', 'Band1', '2009-01-01', '2010-12-31'),
('Emp1', 'Band1', '2011-01-01', '2012-12-31'),
('Emp1', 'Band1', '2013-01-01', '2013-08-31'),
('Emp1', 'Band2', '2013-09-01', '2013-12-31'),
('Emp1', 'Band2', '2014-01-01', '2014-06-30'),
('Emp1', 'Band1', '2014-07-01', '2014-12-31'),
('Emp1', 'Band1', '2015-01-01', '2018-08-31'),
('Emp2', 'Band1', '2012-01-01', '2014-12-31'),
('Emp2', 'Band1', '2015-01-01', '2018-03-31');
WITH cte1 AS (
SELECT *,
CASE WHEN LAG(Band) OVER (PARTITION BY Employee ORDER BY StartDate) = Band /* AND DATEDIFF(...) */ THEN 0 ELSE 1 END AS Chg
FROM #test1
), cte2 AS (
SELECT *,
SUM(Chg) OVER (PARTITION BY Employee ORDER BY StartDate) AS Grp
FROM cte1
)
SELECT Employee, Band, MIN(StartDate), Max(EndDate)
FROM cte2
GROUP BY Employee, Band, Grp
DB Fiddle
This is the intermediate result in case you want to see how it works:
| Employee | Band | StartDate | EndDate | Chg | Grp |
|----------|-------|---------------------|---------------------|-----|-----|
| Emp1 | Band1 | 01/01/2009 00:00:00 | 31/12/2010 00:00:00 | 1 | 1 |
| Emp1 | Band1 | 01/01/2011 00:00:00 | 31/12/2012 00:00:00 | 0 | 1 |
| Emp1 | Band1 | 01/01/2013 00:00:00 | 31/08/2013 00:00:00 | 0 | 1 |
| Emp1 | Band2 | 01/09/2013 00:00:00 | 31/12/2013 00:00:00 | 1 | 2 |
| Emp1 | Band2 | 01/01/2014 00:00:00 | 30/06/2014 00:00:00 | 0 | 2 |
| Emp1 | Band1 | 01/07/2014 00:00:00 | 31/12/2014 00:00:00 | 1 | 3 |
| Emp1 | Band1 | 01/01/2015 00:00:00 | 31/08/2018 00:00:00 | 0 | 3 |
| Emp2 | Band1 | 01/01/2012 00:00:00 | 31/12/2014 00:00:00 | 1 | 1 |
| Emp2 | Band1 | 01/01/2015 00:00:00 | 31/03/2018 00:00:00 | 0 | 1 |
This is commonly known as Gaps and Islands.
One approach
Example
Declare #YourTable Table ([Employee] varchar(50),[Band] varchar(50),[Start] date,[End] date)
Insert Into #YourTable Values
('Emp1','Band1','2009-01-01','2010-12-31')
,('Emp1','Band1','2011-01-01','2012-12-31')
,('Emp1','Band1','2013-01-01','2013-08-31')
,('Emp1','Band2','2013-09-01','2013-12-31')
,('Emp1','Band2','2014-01-01','2014-06-30')
,('Emp1','Band1','2014-07-01','2014-12-31')
,('Emp1','Band1','2015-01-01','2018-08-31')
,('Emp2','Band3','2012-01-01','2014-12-31')
,('Emp2','Band3','2015-01-01','2018-03-31')
;with cte as (
Select *,Grp = sum(Flg) over (Partition By Employee Order by [End])
From (
Select *,Flg = IsNull(datediff(DAY,Lag([End],1) over (Partition By Employee,Band Order by [End]) ,[Start]) - 1,1)
From #YourTable
) A
)
Select Employee
,Band
,[Start] = min([Start])
,[End] = max([End])
From cte
Group By Employee,Band,Grp
Order by Employee,max([End])
Returns
Employee Band Start End
Emp1 Band1 2009-01-01 2013-08-31
Emp1 Band2 2013-09-01 2014-06-30
Emp1 Band1 2014-07-01 2018-08-31
Emp2 Band3 2012-01-01 2018-03-31
If it helps with the visualization, the CTE produces the following
Notice the Flag and Group Columns

Splitting data into time intervals according to changes (T-SQL)

Let's say I have two tables:
Salary:
PersonId, StartDate, EndDate, Salary
Title:
PersonId, StartDate, EndDate, Title
The salary of a person is independent of his title, either may change at any time.
How can I best get all the consecutive StartDate/EndDate time intervals during which salary and title don't change?
So this...
Salary
Me | 2017-01-01 | 2017-01-31 | 2000
Me | 2017-02-01 | 2017-05-31 | 2100
Me | 2017-06-01 | 2017-07-31 | 2300
Title
Me | 2017-01-01 | 2017-03-31 | Junior
Me | 2017-04-01 | 2017-07-31 | Senior
would return:
SalaryAndTitle
Me | 2017-01-01 | 2017-01-31 | 2000 | Junior
Me | 2017-02-01 | 2017-03-31 | 2100 | Junior
Me | 2017-04-01 | 2017-05-31 | 2100 | Senior
Me | 2017-06-01 | 2017-07-31 | 2300 | Senior
This is a simplified example. In my real case there would be many changing columns, and the resulting data set should still contain time intervals where those columns have unchanged values with respect to that time period.
I'm thinking about over( partition by...) but I can't get it to work. Any help appreciated.
Cheers,
Kim
I dont know the performance requirements you have and i am sure there will be better ways of doing this, however....
one sort of catch all solution to these problems is to break it down day by day and then use standard aggregate functions, for example below I am assuming you have a table called dates that has all the dates you are interested in:
select
p.personid
,min(ds.dt) as from
,max(ds.dt) as to
,s.salary
,t.title
from
dates as ds
cross join
(select distinct personid from salary) as p
left outer join salary as s
on ds.dt >= s.startdate
and ds.dt <= s.enddate
and p.personid = s.personid
left outer join title as t
on ds.dt >= t.startdate
and ds.dt <= t.enddate
and p.personid = t.personid
group by
p.personid
,s.salary
,t.title
I am using left outer joins here as i would start with that and do some analysis of the data.
I use this type of thing for analysis, reporting and data migration a lot. I have used it for billing calculations too - however I have done absolutely no performance testing on this type of approach. the focus has been writing queries that are easy to maintain and have all the power you could ever want (analysis tends to be easyier with highly de-normalised data like a day by day breakdown)
I added some records to the sample data to address the issue raised surrounding the possibility of a PersonID having more than one time range where the PersonID has the same Title and Salary.
Answer:
create table dbo.Salary
(
PersonID varchar(3)
, StartDate date
, EndDate date
, Salary int
)
create table dbo.Title
(
PersonID varchar(3)
, StartDate date
, EndDate date
, Title varchar(10)
)
insert into dbo.Salary
values ('Me', '2017-01-01', '2017-01-31', 2000)
, ('Me', '2017-02-01', '2017-05-31', 2100)
, ('Me', '2017-06-01', '2017-07-31', 2300)
, ('You', '2017-01-01', '2017-03-31', 2400)
, ('You', '2017-04-01', '2017-08-31', 2500)
, ('You', '2017-09-01', '2017-12-31', 2400)
insert into dbo.Title
values ('Me', '2017-01-01', '2017-03-31', 'Junior')
, ('Me', '2017-04-01', '2017-07-31', 'Senior')
, ('You', '2017-01-01', '2017-02-28', 'Junior')
, ('You', '2017-03-01', '2017-05-31', 'Senior')
, ('You', '2017-06-01', '2017-12-31', 'Junior')
select a.PersonID
, a.StartDate
, a.EndDate
, a.Salary
, a.Title
from (
select s.PersonID
, iif(s.StartDate < t.StartDate, t.StartDate, s.StartDate) as StartDate
, iif(s.EndDate < t.EndDate, s.EndDate, t.EndDate) as EndDate
, s.Salary
, t.Title
from dbo.Salary as s
inner join dbo.Title as t on s.PersonID = t.PersonID
) as a
where 1=1
and datediff(d, a.StartDate, a.EndDate) >= 0 --is it a valid time range?
The sub-query duplicates all possible StartDate / EndDate combinations for a PersonID, and the outer query determines if that time range is valid.
Output:
PersonID StartDate EndDate Salary Title
Me 2017-01-01 2017-01-31 2000 Junior
Me 2017-02-01 2017-03-31 2100 Junior
Me 2017-04-01 2017-05-31 2100 Senior
Me 2017-06-01 2017-07-31 2300 Senior
You 2017-01-01 2017-02-28 2400 Junior
You 2017-03-01 2017-03-31 2400 Senior
You 2017-04-01 2017-05-31 2500 Senior
You 2017-06-01 2017-08-31 2500 Junior
You 2017-09-01 2017-12-31 2400 Junior

SQL Server time interval to multiple rows

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;

Stored Procedure - Start and end date, looking at today's date

I have a table with this entries:
StartDate | EndDate
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-06 | 2016-01-16
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-07 | 2016-01-17
2016-01-08 | 2016-01-18
2016-01-08 | 2016-01-18
2017-01-01 | 2017-01-10
2026-01-06 | 2026-01-16
2026-01-07 | 2026-01-17
And my query in stored procedure is:
SELECT ROW_NUMBER() OVER ( ORDER BY [StartDate] ASC) as ROWNUM, [Oid]
From [dbo].[Ument]
Where (
(#StartDate is null or StartDate >= #StartDate)
or (#EndDate is null or EndDate <= #EndDate)
)
I want to search by #StartDate written by user or, if is null/empty, by #StartDate >= today's date.
But, at the same time, I want to search by #endDate, written by user or, if is null/empty, EndDate >= today's.
If today's is between two dates, i need to return it.
Like this:
#startDate isn't null?
YES: use the #startDate written by user to filter startDate >= #StartDate.
NO: use today's date to put startDate >= today.
#endDate isn't null?
YES: use the #endDate written by user to filter endDate <= #endDate
NO: use max-date (e.g.: 2050/12/31) to filter endDate <= max-Date
Is this what you want?
WHERE startDate >= COALESCE(#StartDate, GETDATE()) AND
endDate <= COALESCE(#EndDate, GETDATE())
You may want this logic, but with the current date with no time:
WHERE startDate >= COALESCE(#StartDate, CAST(GETDATE() AS DATE)) AND
endDate <= COALESCE(#EndDate, CAST(GETDATE() AS DATE))
After the comment, diabolickman's question and Gordon's answer can be mixed like:
SELECT ROW_NUMBER() OVER ( ORDER BY [StartDate] ASC) as ROWNUM, [Oid]
From [dbo].[Ument]
Where (
StartDate >= ISNULL(#StartDate, CAST(GETDATE() AS DATE))
AND (#EndDate is null or EndDate <= #EndDate)
)
This way, End date is ignored if it's null, so you dont need to compare it against max date.
Changed middle OR to AND, assuming user wants to filter a date range.
I used ISNULL instead of COALESCE, just to show another alternative.
I think my problem is solved! :)
I change the last solution to this and the query returns what I want!
SELECT ROW_NUMBER() OVER ( ORDER BY [StartDate] ASC) as ROWNUM, [Oid] From [dbo].[Ument]
Where (
StartDate >= ISNULL(#StartDate, CAST(GETDATE() AS DATE))
and EndDate <= ISNULL(#EndDate, CAST('20501231' AS DATE))
)
Thanks for the help ramazan's and Gordon's. :)

How to compare time slot in SQL Server?

I have a table [pricelist]
startHour
endHour
Price
and another table that contains the [actualuse]
startDate
endDate
user
My data
pricelist:
startHour | endHour | price
----------------------------
00:00 | 07:59 | 10
08:00 | 15:59 | 20
16:00 | 23:59 | 5
actualUse:
startDate | endDate | jobId
-------------------------------------------
12/10/2014 08:30 | 12/10/2014 15:20| 1
12/10/2014 07:30 | 12/10/2014 18:20| 2
12/10/2014 07:30 | 13/10/2014 16:20| 3
12/10/2014 09:30 | 13/10/2014 00:20| 4
I try to get for every gob all rows in pricelist the are belong to. For example for jobId 1 I will get
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 08:30 | 12/10/2014 15:20| 1 |20
for jobId 2
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 07:30 | 12/10/2014 07:59| 2 |10
12/10/2014 08:00 | 12/10/2014 15:59| 2 |20
12/10/2014 16:00 | 12/10/2014 18:20| 2 |5
for jobId 3
startDate | endDate | jobId |price
---------------------------------------------------
12/10/2014 07:30 | 12/10/2014 07:59| 3 |10
12/10/2014 08:00 | 12/10/2014 15:59| 3 |20
12/10/2014 16:00 | 12/10/2014 23:59| 3 |5
13/10/2014 00:00 | 13/10/2014 07:59| 3 |10
13/10/2014 08:00 | 13/10/2014 15:59| 3 |20
13/10/2014 16:00 | 13/10/2014 16:20| 3 |5
Here is a possible working solution. I know the cursor is a bit ugly but I had to create another table with every possibles dates between the date ranges. You might be able to refactor some date computation too (I assumed you were working with TIME datatype)
/*
create table pricelist
(
startHour time,
endHour time,
price decimal(18,2)
)
create table actualuse
(
startDate datetime,
endDate datetime,
jobId int
)
insert pricelist values
('00:00','07:59',10),
('08:00','15:59',20),
('16:00','23:59',5)
set dateformat dmy
insert actualuse values
('12/10/2014 08:30','12/10/2014 15:20',1),
('12/10/2014 07:30','12/10/2014 18:20',2),
('12/10/2014 07:30','13/10/2014 16:20',3),
('12/10/2014 09:30','13/10/2014 00:20',4)
*/
BEGIN TRY DROP TABLE #actualUseDays END TRY
BEGIN CATCH END CATCH
CREATE TABLE #actualUseDays (
startDate DATETIME
,endDate DATETIME
,jobId INT
)
DECLARE #startDate DATETIME
,#endDate DATETIME
,#jobId INT;
DECLARE cur CURSOR FORWARD_ONLY FOR SELECT * FROM actualuse
OPEN cur;
FETCH NEXT FROM cur INTO #startDate ,#endDate ,#jobId
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT #actualUseDays
SELECT #startDate
,iif(CAST(#endDate AS DATE) <> CAST(#startDate AS DATE), DATEADD(day, DATEDIFF(day, '19000101', cast(#startDate AS DATE)), CAST(CAST('23:59:59' AS TIME) AS
DATETIME2(7))), #endDate)
,#jobId
UNION
SELECT CAST(DATEADD(DAY, number + 1, #startDate) AS DATE) [Date]
,iif(CAST(#endDate AS DATE) <> CAST(DATEADD(DAY, number + 1, #startDate) AS DATE), DATEADD(day, DATEDIFF(day, '19000101', CAST(DATEADD(DAY, number + 1,
#startDate) AS DATE)), CAST(CAST('23:59:59' AS TIME) AS DATETIME2(7))), #endDate)
,#jobId
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number + 1, CAST(#startDate AS DATE)) < #endDate
FETCH NEXT FROM cur INTO #startDate ,#endDate ,#jobId
END
CLOSE cur;
DEALLOCATE cur;
/*
#actualUseDays now contains :
startDate endDate jobId
----------------------- ----------------------- -----------
2014-10-12 08:30:00.000 2014-10-12 15:20:00.000 1
2014-10-12 07:30:00.000 2014-10-12 18:20:00.000 2
2014-10-12 07:30:00.000 2014-10-12 23:59:59.000 3
2014-10-13 00:00:00.000 2014-10-13 16:20:00.000 3
2014-10-12 09:30:00.000 2014-10-12 23:59:59.000 4
2014-10-13 00:00:00.000 2014-10-13 00:20:00.000 4
*/
SELECT iif(CAST(a.startDate AS TIME) > p.startHour, startDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(startDate AS DATE)), CAST(startHour AS DATETIME2(7)))) AS
startDate
,iif(CAST(a.endDate AS TIME) < p.endHour, endDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(endDate AS DATE)), CAST(endHour AS DATETIME2(7)))) AS endDate
,jobId
,price
FROM #actualUseDays a
INNER JOIN pricelist p
ON CAST(a.startDate AS TIME) <= p.endHour
AND CAST(a.endDate AS TIME) >= p.startHour
ORDER BY jobId
,iif(CAST(a.startDate AS TIME) > p.startHour, startDate, DATEADD(day, DATEDIFF(day, '19000101', CAST(startDate AS DATE)), CAST(startHour AS DATETIME2(7))))
Results :
startDate endDate jobId price
--------------------------- --------------------------- ----------- ---------------------------------------
2014-10-12 08:30:00.0000000 2014-10-12 15:20:00.0000000 1 20.00
2014-10-12 07:30:00.0000000 2014-10-12 07:59:00.0000000 2 10.00
2014-10-12 08:00:00.0000000 2014-10-12 15:59:00.0000000 2 20.00
2014-10-12 16:00:00.0000000 2014-10-12 18:20:00.0000000 2 5.00
2014-10-12 07:30:00.0000000 2014-10-12 07:59:00.0000000 3 10.00
2014-10-12 08:00:00.0000000 2014-10-12 15:59:00.0000000 3 20.00
2014-10-12 16:00:00.0000000 2014-10-12 23:59:00.0000000 3 5.00
2014-10-13 00:00:00.0000000 2014-10-13 07:59:00.0000000 3 10.00
2014-10-13 08:00:00.0000000 2014-10-13 15:59:00.0000000 3 20.00
2014-10-13 16:00:00.0000000 2014-10-13 16:20:00.0000000 3 5.00
2014-10-12 09:30:00.0000000 2014-10-12 15:59:00.0000000 4 20.00
2014-10-12 16:00:00.0000000 2014-10-12 23:59:00.0000000 4 5.00
2014-10-13 00:00:00.0000000 2014-10-13 00:20:00.0000000 4 10.00

Resources