How can I sum durations grouped by overlapping times in SQL Server - sql-server

I am trying to create a stored proc in SQL Server 2008.
I have a "Timings" Table (which could have thousands of records):
StaffID | MachineID | StartTime | FinishTime
1 | 1 | 01/01/2018 12:00 | 01/01/18 14:30
2 | 1 | 01/01/2018 12:00 | 01/01/18 13:00
3 | 2 | 01/01/2018 12:00 | 01/01/18 13:00
3 | 2 | 01/01/2018 13:00 | 01/01/18 14:00
4 | 3 | 01/01/2018 12:00 | 01/01/18 12:30
5 | 3 | 01/01/2018 11:00 | 01/01/18 13:30
This shows how long each staff member was working on each machine.
I would like to produce a results table as below:
MachineID | StaffQty | TotalMins
1 | 1 | 90
1 | 2 | 60
2 | 1 | 120
3 | 1 | 120
3 | 2 | 30
This would show how many minutes each machine had only one person using it, how many minutes each machine had 2 people using it etc.
Normally, I would post what I have tried so far, but all my attempts seem to be so far away, I don't think there is much point.
Obviously, I would be very grateful of a complete solution but I would also appreciate even just a little nudge in the right direction.

I think this answers your question:
declare #t table (StaffID int, MachineID int, StartTime datetime2,FinishTime datetime2)
insert into #t(StaffID,MachineID,StartTime,FinishTime) values
(1,1,'2018-01-01T12:00:00','2018-01-01T14:30:00'),
(2,1,'2018-01-01T12:00:00','2018-01-01T13:00:00'),
(3,2,'2018-01-01T12:00:00','2018-01-01T12:30:00')
;With Times as (
select MachineID,StartTime as Time from #t
union
select MachineID,FinishTime from #t
), Ordered as (
select
*,
ROW_NUMBER() OVER (PARTITION BY MachineID ORDER BY Time) rn
from Times
), Periods as (
select
o1.MachineID,o1.Time as StartTime,o2.Time as FinishTime
from
Ordered o1
inner join
Ordered o2
on
o1.MachineID = o2.MachineID and
o1.rn = o2.rn - 1
)
select
p.MachineID,
p.StartTime,
MAX(p.FinishTime) as FinishTime,
COUNT(*) as Cnt,
DATEDIFF(minute,p.StartTime,MAX(p.FinishTime)) as TotalMinutes
from
#t t
inner join
Periods p
on
p.MachineID = t.MachineID and
p.StartTime < t.FinishTime and
t.StartTime < p.FinishTime
group by p.MachineID,p.StartTime
Results:
MachineID StartTime FinishTime Cnt TotalMinutes
----------- --------------------------- --------------------------- ----------- ------------
1 2018-01-01 12:00:00.0000000 2018-01-01 13:00:00.0000000 2 60
1 2018-01-01 13:00:00.0000000 2018-01-01 14:30:00.0000000 1 90
2 2018-01-01 12:00:00.0000000 2018-01-01 12:30:00.0000000 1 30
Hopefully you can see what each of the CTEs is doing. The only place where this may not give you exactly the results you're seeking is if one person's FinishTime is precisely equal to another person's StartTime on the same machine. Should be rare in real data hopefully.

For Sql server 2012+,
Please mention your Sql server version.
Try my script with other sample data.
Please post other sample data if it is not working.
I think my script can be fix for other Test scenario.
create table #temp(StaffID int,MachineID int,StartTime datetime,FinishTime datetime)
insert into #temp VALUES
(1, 1,'01/01/2018 12:00','01/01/18 14:30')
,(2, 1,'01/01/2018 12:00','01/01/18 13:00')
,(3, 2,'01/01/2018 12:00','01/01/18 12:30')
;
WITH CTE
AS (
SELECT t.*
,t1.StaffQty
,datediff(MINUTE, t.StartTime, t.FinishTime) TotalMinutes
FROM #temp t
CROSS APPLY (
SELECT count(*) StaffQty
FROM #temp t1
WHERE t.machineid = t1.machineid
AND (
t.StartTime >= t1.StartTime
AND t.FinishTime <= t1.FinishTime
)
) t1
)
SELECT MachineID
,StaffQty
,TotalMinutes - isnull(LAG(TotalMinutes, 1) OVER (
PARTITION BY t.MachineID ORDER BY t.StartTime
,t.FinishTime
), 0)
FROM cte t
 
drop table #temp
for Sql server 2008,
;
WITH CTE
AS (
SELECT t.*
,t1.StaffQty
,datediff(MINUTE, t.StartTime, t.FinishTime) TotalMinutes
,ROW_NUMBER() OVER (
PARTITION BY t.machineid ORDER BY t.StartTime
,t.FinishTime
) rn
FROM #temp t
CROSS APPLY (
SELECT count(*) StaffQty
FROM #temp t1
WHERE t.machineid = t1.machineid
AND (
t.StartTime >= t1.StartTime
AND t.FinishTime <= t1.FinishTime
)
) t1
)
SELECT t.MachineID
,t.StaffQty
,t.TotalMinutes - isnull(t1.TotalMinutes, 0) TotalMinutes
FROM cte t
OUTER APPLY (
SELECT TOP 1 TotalMinutes
FROM cte t1
WHERE t.MachineID = t1.machineid
AND t1.rn < t.rn
ORDER BY t1.rn DESC
) t1

Related

Split String Into Individual Rows

I'm currently tying to figure out how to split an ID into 2 rows at each instance of '/'. The original IDs will still be saved in the main table as well as temp table 2 but I need the new IDs saved to a new table. All of this happens in temporary tables on a pre-import handler before a report is generated.
The tables output is currently as follows:
RWID RWLEN DESCR QTY UNIT
T2/10060 20.0000 SomeInfo 1 pcs
T2/10061 18.5689 SomeInfo 1 pcs
T2/10062 20.0000 SomeInfo 1 pcs
I need the table to out the following:
RWID RWLEN DESCR QTY UNIT
T10060 20.0000 SomeInfo 1 pcs
T20060 20.0000 SomeInfo 1 pcs
T10061 18.5689 SomeInfo 1 pcs
T20061 18.5689 SomeInfo 1 pcs
T10062 20.0000 SomeInfo 1 pcs
T20062 20.0000 SomeInfo 1 pcs
A snippet of my code is below:
-- populate temp table 1 from main table
SELECT *
INTO ##tmp1
FROM main;
-- populate temp table 2 from temp table 1, group and order by RWID
SELECT RWID, MAX(DESCR) as aux
INTO ##tmp2
FROM ##tmp1
group by RWID
ORDER by RWID;
-- populate temp table 3 from temp table 1 then split strings with dividers
SELECT RWID, RWLEN, DESCR, QTY, UNIT
INTO ##tmp3
FROM ##tmp1
UNION ALL
SELECT RWID, NULL RWLEN, NULL DESCR, NULL QTY, NULL UNIT
FROM ##tmp1
GROUP BY RWID
ORDER BY RWID, DESCR desc;
SELECT
RWID = CASE WHEN a.DESCR = b.AUX THEN a.RWID ELSE NULL END,
RWLEN = CASE WHEN a.DESCR = b.AUX THEN a.RWLEN ELSE NULL END,
a.DESCR,
a.QTY,
a.UNIT
INTO ##report
FROM ##tmp3
a
FULL OUTER JOIN ##tmp2
b on a.RWID = b.RWID;
SELECT *
FROM ##report
Thanks in advance for your time and assistance.
UPDATE! Thanks so much for all of your help, it really steered me in the right direction. I've figured out how to split the strings shown above as well as the other types of IDs that I'll encounter that I hadn't included in the example. Thanks again for your time and help, you're all awesome!!
Result: http://www.sqlfiddle.com/#!18/17a09/1
You can simply use a UNION ALL like following to get the desired output.
SELECT 'T1'+ SUBSTRING(RWID,CHARINDEX('/',RWID)+1,
LEN(RWID)- CHARINDEX('/',RWID)) -- + OTHER COLUMN
FROM [TABLE_NAME]
UNION ALL
SELECT 'T2'+ SUBSTRING(RWID,CHARINDEX('/',RWID)+1,
LEN(RWID)- CHARINDEX('/',RWID)) -- + OTHER COLUMN
FROM [TABLE_NAME]
Complete Example
DECLARE #TBL TABLE (RWID VARCHAR(30), RWLEN DECIMAL(15,2),
DESCR VARCHAR(50), QTY INT, UNIT VARCHAR(4))
INSERT INTO #TBL
values
('T2/10060', 20.0000 ,'SomeInfo', 1 ,'pcs'),
('T2/10061', 18.5689 ,'SomeInfo', 1 ,'pcs'),
('T2/10062', 20.0000 ,'SomeInfo', 1 ,'pcs')
SELECT 'T1'+ SUBSTRING(RWID, CHARINDEX('/',RWID)+1,LEN(RWID)- CHARINDEX('/',RWID)) RWID
,RWLEN, DESCR, QTY, UNIT
FROM #TBL
UNION ALL
SELECT 'T2'+ SUBSTRING(RWID, CHARINDEX('/',RWID)+1,LEN(RWID)- CHARINDEX('/',RWID)) RWID
,RWLEN, DESCR, QTY, UNIT
FROM #TBL
Output
+---------+-------+----------+-----+------+
| RWID | RWLEN | DESCR | QTY | UNIT |
+---------+-------+----------+-----+------+
| T110060 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T110061 | 18.57 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T110062 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210060 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210061 | 18.57 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
| T210062 | 20.00 | SomeInfo | 1 | pcs |
+---------+-------+----------+-----+------+
DEMO
SQL-server using CTE
declare #table table (rwid varchar(30), rwlen float, descr varchar(50), qty int, unit varchar(4))
insert into #table
values
('T2/10060', 20.0000 ,'SomeInfo', 1 ,'pcs'),
('T2/10061', 18.5689 ,'SomeInfo', 1 ,'pcs'),
('T2/10062', 20.0000 ,'SomeInfo', 1 ,'pcs')
;with mycte as (
select *, cast(right(left(rwid,charindex('/',rwid)-1),1) as int) [num], 1 [start] from #table
union all
select t.*,c.start + 1, c.num from #table t
inner join mycte c
on c.rwid = t.rwid
and c.start + 1 <= c.num
)
select
concat(left(rwid,1), start,replace(rwid,left(rwid,charindex('/',rwid)+1),'')) ,
rwlen,
descr,
qty,
unit
from mycte
order by rwid, start
Using Cross apply
;WITH CTE( RWID, RWLEN,DESCR,QTY, UNIT)
AS
(
SELECT 'T2/10060',20.0000,'SomeInfo', 1,'pcs' UNION ALL
SELECT 'T2/10061',18.5689,'SomeInfo', 1,'pcs' UNION ALL
SELECT 'T2/10062',20.0000,'SomeInfo', 1,'pcs'
)
SELECT REPLACE(RWID,'2/1',CAST(Rnk AS VARCHAr(2))) AS RWID
,RWLEN
,DESCR
,QTY
,UNIT
FROM
(
SELECT C.*,ROW_NUMBER()OVER(PARTITION BY C.RWID ORDER BY C.RWID) AS Rnk
FROM CTE C
CROSS APPLY CTE C2
)dt WHERE Rnk<3
Result
http://www.sqlfiddle.com/#!18/9eecb/11626

SQL Find Tickets Open Between Start Date and End Date

I currently have the following table:
+-----+-----------------------------+------------------------------+
| ID | StartDate | EndDate |
+-----+-----------------------------+------------------------------|
| 1 | 2017-07-24 08:00:00.000 | 2017-07-29 08:00:00.000 |
| 2 | 2017-07-25 08:00:00.000 | 2017-07-28 08:00:00.000 |
| 3 | 2017-07-25 08:00:00.000 | 2017-07-26 08:00:00.000 |
+-----+-----------------------------+------------------------------+
I would like to know the count of the ID's that were not Closed on each date.
So for example, I wan't to know the count of open ID's on 2017-07-26 00:00:00.000. This would be all 3 in this case.
Another example: I wan't to know the count of open ID's on 2017-07-29 00:00:00.000. Which would be result to 1. Only ID=1 is Not yet closed at that date.
I have tried using another solution here on StackOverflow, but I can't quite figure why it is giving me false results.
declare #dt date, #dtEnd date
set #dt = getdate()-7
set #dtEnd = dateadd(day, 100, #dt);
WITH CTEt1 (SupportCallID, StartDate, EndDate, Onhold)
as
(SELECT SupportCallID
,OpenDate
,MAX(CASE WHEN StatusID IN('19381771-8E81-40C5-8E36-62A7DB0A2A99', '95C7A5FB-2389-4D14-9DAE-A08BFCC3B09A', 'D5429790-3B43-4462-9E1E-2466EA29AC74') then CONVERT(DATE, LastChangeDate) end) EndDate
,OnHold
FROM [ClienteleITSM_Prod_Application].[dbo].[SupportCall]
group by SupportCallID, OpenDate, OnHold
)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM CTEt1
WHERE myDate BETWEEN StartDate and EndDate
)
FROM
(select dateadd(day, number, #dt) mydate
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #dt) < #dtEnd) dates
If you use a cte to create a table of dates that span the range of dates in your source table, you can easily left join from that to your source table and count up the rows returned:
declare #t table(ID int,StartDate datetime,EndDate datetime);
insert into #t values (1,'2017-07-24 08:00:00.000','2017-07-29 08:00:00.000'),(2,'2017-07-25 08:00:00.000','2017-07-28 08:00:00.000'),(3,'2017-07-25 08:00:00.000','2017-07-26 08:00:00.000');
declare #StartDate datetime = (select min(StartDate) from #t);
declare #EndDate datetime = (select max(EndDate) from #t);
-- Table with 10 rows in to be joined together to create a large tally table (10 * 10 * 10 * etc)
with t(t) as (select t from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(t))
-- Add the row_number of the tally table to your start date to generate all dates within your data range
,d(d) as (select top(datediff(d,#StartDate,#EndDate)+1) dateadd(d,row_number() over (order by (select null))-1,#StartDate) from t t1,t t2,t t3)
select d.d
,count(t.ID) as OpenIDs
from d
left join #t as t
on(d.d between cast(t.StartDate as date) and t.EndDate)
group by d.d
order by d.d;
Output:
+-------------------------+---------+
| d | OpenIDs |
+-------------------------+---------+
| 2017-07-24 08:00:00.000 | 1 |
| 2017-07-25 08:00:00.000 | 3 |
| 2017-07-26 08:00:00.000 | 3 |
| 2017-07-27 08:00:00.000 | 2 |
| 2017-07-28 08:00:00.000 | 2 |
| 2017-07-29 08:00:00.000 | 1 |
+-------------------------+---------+

SQL Query construct

I have three tables. I want to get data from all those tables and put it in a virtual table. i am using SQL Server 2012.
Sorry if my format or tags are wrong because I m getting error Stack overflow requires external javascrip from another source domain, which is blocked of failed to load.
Booking Table
BookingId | date
======================
2 | 7/1/2017 (MM/dd/yyyy)
3 | 7/1/2017
BookingCost Table
Id | bookinId | Cost
==========================
1 | 2 | 2000
2 | 3 | 4000
Expense Table
Id | ExpenseCost | Date
======================
1 | 1400 | 7/2/2017 (MM/dd/yyyy)
2 | 1422 | 7/1/2017
3 | 4000 | 6/3/2017
I want to get Monthly result like following Table.
Date | Expense | Bookings
===================================
jan/2017 | 0 | 0
feb/2017 | 0 | 0
. | . | .
. | . | .
. | . | .
jun/2017 | 4000 | 0
jul/2017 | 2822 | 6000
. | . | .
. | . | .
. | . | .
How is something like this (assuming your dates are DATE types and not VARCHAR - otherwise you could convert them).
SELECT COALESCE(EXPENSE.MONTH, BOOKINGS.MONTH) [Date], EXPENSE.Cost Expense, BOOKINGS.Cost Bookings
FROM (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(Cost) Cost
FROM Booking
INNER JOIN BookingCost
ON Booking.BookingID = BookingCost.BookingID
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) BOOKINGS
FULL JOIN (
SELECT DATEADD(DD,1-DAY([date]),[date]) MONTH, SUM(ExpenseCost) Cost
FROM Expense
GROUP BY DATEADD(DD,1-DAY([date]),[date])
) EXPENSE
ON EXPENSE.MONTH = BOOKINGS.MONTH
ORDER BY 1
To also get the 0 counts, you could left join the totals to a tally table which has all the months for the year.
The sql is using FORMAT to transform the Date
For example:
;WITH MONTHS AS
(
select
[Year], [Month],
format(datefromparts([Year],[Month],1),'MMM/yyyy') as [MonthYear]
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) m([Month])
cross join (values (2017)) y([Year])
)
select
m.[MonthYear] as [Date],
coalesce(e.TotalExpense,0) as Expense,
coalesce(bc.TotalCost,0) as Bookings
from MONTHS m
left join (
select
datepart(year,[Date]) as [Year],
datepart(month,[Date]) as [Month],
sum(ExpenseCost) as TotalExpense
from Expense
where datepart(year,[Date]) in (select distinct [Year] from MONTHS)
group by datepart(year,[Date]), datepart(month,[Date])
)e on (e.[Year] = m.[Year] and e.[Month] = m.[Month])
left join (
select
datepart(year,b.[date]) as [Year],
datepart(month,b.[date]) as [Month],
sum(c.Cost) as TotalCost
from Booking b
join BookingCost c on c.BookingId = b.BookingId
where datepart(year,b.[date]) in (select distinct [Year] from MONTHS)
group by datepart(year,b.[date]), datepart(month,b.[date])
) bc
on (bc.[Year] = m.[Year] and bc.[Month] = m.[Month])
order by m.[Year], m.[Month];
Test data I used
declare #Booking table (BookingId int, [date] date);
insert into #Booking (BookingId,[date]) values (2,'2017-07-01'),(3,'2017-07-01');
declare #BookingCost table (Id int, BookingId int, Cost int);
insert into #BookingCost (Id, BookingId, Cost) values (1,2,2000),(2,3,4000);
declare #Expense table (Id int, ExpenseCost int, [Date] date);
insert into #Expense (Id, ExpenseCost, [Date]) values
(1,1400,'2017-07-02'),(2,1422,'2017-07-01'),(3,4000,'2017-06-03');

MSSQL 2008 Merge Contiguous Dates With Groupings

I have searched high and low for weeks now trying to find a solution to my problem.
As far as I can ascertain, my SQL Server version (2008r2) is a limiting factor on this but, I am positive there is a solution out there.
My problem is as follows:
A have a table with potential contiguous dates in the form of Customer-Status-DateStart-DateEnd-EventID.
I need to merge contiguous dates by customer and status - the status field can shift up and down throughout a customers pathway.
Some example data is as follows:
DECLARE #Tbl TABLE([CustomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
Desired output:
Customer | Status | DateStart | DateEnd
---------+--------+-----------+-----------
1 | 1 | 2016-01-01| 2016-01-08
1 | 2 | 2016-01-08| 2016-01-10
1 | 1 | 2016-01-10| 2016-01-13
1 | 3 | 2016-01-13| NULL
2 | 1 | 2016-01-01| NULL
3 | 2 | 2016-01-09| 2016-01-10
3 | 1 | 2016-01-10| 2016-01-14
Any ideas / code will be greatly received.
Thanks,
Dan
Try this
DECLARE #Tbl TABLE([CusomerID] INT
,[Status] INT
,[DateStart] DATE
,[DateEnd] DATE
,[EventID] INT)
INSERT INTO #Tbl
VALUES (1,1,'20160101','20160104',1)
,(1,1,'20160104','20160108',3)
,(1,2,'20160108','20160110',4)
,(1,1,'20160110','20160113',7)
,(1,3,'20160113','20160113',9)
,(1,3,'20160113',NULL,10)
,(2,1,'20160101',NULL,2)
,(3,2,'20160109','20160110',5)
,(3,1,'20160110','20160112',6)
,(3,1,'20160112','20160114',8)
;WITH CTE
AS
(
SELECT CusomerID ,
Status ,
DateStart ,
COALESCE(DateEnd, '9999-01-01') AS DateEnd,
EventID,
ROW_NUMBER() OVER (ORDER BY CusomerID, EventID) RowId,
ROW_NUMBER() OVER (PARTITION BY CusomerID, Status ORDER BY EventID) StatusRowId FROM #Tbl
)
SELECT
A.CusomerID ,
A.Status ,
A.DateStart ,
CASE WHEN A.DateEnd = '9999-01-01' THEN NULL
ELSE A.DateEnd END AS DateEnd
FROM
(
SELECT
CTE.CusomerID,
CTE.Status,
MIN(CTE.DateStart) AS DateStart,
MAX(CTE.DateEnd) AS DateEnd
FROM
CTE
GROUP BY
CTE.CusomerID,
CTE.Status,
CTE.StatusRowId -CTE.RowId
) A
ORDER BY A.CusomerID, A.DateStart
Output
CusomerID Status DateStart DateEnd
----------- ----------- ---------- ----------
1 1 2016-01-01 2016-01-08
1 2 2016-01-08 2016-01-10
1 1 2016-01-10 2016-01-13
1 3 2016-01-13 NULL
2 1 2016-01-01 NULL
3 2 2016-01-09 2016-01-10
3 1 2016-01-10 2016-01-14

SQL - how do I generate rows for each month based on date ranges in existing dataset?

assume I have a dataset:
rowID | dateStart | dateEnd | Year | Month
121 | 2013-10-03 | 2013-12-03 | NULL | NULL
143 | 2013-12-11 | 2014-03-11 | NULL | NULL
322 | 2014-01-02 | 2014-02-11 | NULL | NULL
And I want sql to generate the following datasource based on the dateStart and the dateEnd. Note the year and month grouping.
rowID | dateStart | dateEnd | Year | Month
121 | 2013-10-03 | 2013-12-03 | 2013 | 10
121 | 2013-10-03 | 2013-12-03 | 2013 | 11
121 | 2013-10-03 | 2013-12-03 | 2013 | 12
143 | 2013-12-11 | 2014-03-11 | 2013 | 12
143 | 2013-12-11 | 2014-03-11 | 2014 | 1
143 | 2013-12-11 | 2014-03-11 | 2014 | 2
143 | 2013-12-11 | 2014-03-11 | 2014 | 3
322 | 2014-01-02 | 2014-02-11 | 2014 | 1
322 | 2014-01-02 | 2014-02-11 | 2014 | 2
I'm having a hard time wrapping my head around this one. Any ideas?
I find it easiest to approach these problems by creating a list of integers and then using that to increment the dates. Here is an example:
with nums as (
select 0 as n
union all
select n + 1 as n
from nums
where n < 11
)
select rowid, datestart, dateend,
year(dateadd(month, n.n, datestart)) as yr,
month(dateadd(month, n.n, datestart)) as mon
from table t join
nums n
on dateadd(month, n.n - 1, datestart) <= dateend;
First, create a tabled-valued function that takes the 2 dates and returns the year and month as a table:
create function dbo.YearMonths(#StartDate DateTime, #EndDate DateTime)
returns #YearMonths table
([Year] int,
[Month] int)
as
begin
set #EndDate = DATEADD(month, 1, #EndDate)
while (#StartDate < #EndDate)
begin
insert into #YearMonths
select YEAR(#StartDate), MONTH(#StartDate)
set #StartDate = DATEADD(month, 1, #StartDate)
end
return
end
As an example the following:
select *
from dbo.YearMonths('1/1/2014', '5/1/2014')
returns:
Then you would join to it like this to get what you wanted:
select m.*, ym.Year, ym.Month
from myTable m
cross apply dbo.YearMonths(dateStart, dateEnd) ym
Try this:
declare #months table(mth int)
insert into #months values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
declare #calendar table(yr int,mth int)
insert into #calendar
select distinct year(datestart),mth
from tbl cross join #months
union
select distinct year(dateend),mth
from tbl cross join #months
select t.rowID, t.datestart, t.dateend, y.yr [Year], y.mth [Month]
from
yourtable t
inner join #calendar y on year(datestart) = yr or year(dateend) = yr
where
(mth >= month(datestart) and mth <= month(dateend) and year(datestart) = year(dateend))
or
(year(datestart) < year(dateend))
and
(year(datestart) = yr and mth >= month(datestart) --All months of start year
or
(year(dateend) = yr and mth <= month(dateend))) -- All months of end year
order by t.rowID, [Year],[Month]
We create a 'Calendar table' which lists all the month and year combinations present in the source table. Then, we join the source table to the calendar table based on the year, and filter as required.

Resources