How to Pivot/Unpivot Something with SQL2000 - sql-server

I'm querying a table which essentially a bunch of dates.
FRECDCUST DTRECDCUST DTINSPECTED DTRECDCUSTPO DTADDEDSO DTSHIPPEDSUP DTQUOTEDCUST
1/1/1900 12:00:00 AM 7/27/2010 12:00:00 AM 7/30/2010 12:00:00 AM 7/26/2010 12:00:00 AM 8/6/2010 12:00:00 AM 1/1/1900 12:00:00 AM 8/6/2010 12:00:00 AM
1/1/1900 12:00:00 AM 7/27/2010 12:00:00 AM 7/30/2010 12:00:00 AM 7/26/2010 12:00:00 AM 8/6/2010 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM
1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM
1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM
1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM 1/1/1900 12:00:00 AM
1/1/1900 12:00:00 AM 3/12/2010 12:00:00 AM 3/15/2010 12:00:00 AM 3/11/2010 12:00:00 AM 3/16/2010 12:00:00 AM 3/24/2010 12:00:00 AM 4/13/2010 12:00:00 AM
1/1/1900 12:00:00 AM 11/6/2009 12:00:00 AM 11/6/2009 12:00:00 AM 11/3/2009 12:00:00 AM 11/9/2009 12:00:00 AM 11/20/2009 12:00:00 AM 12/7/2009 12:00:00 AM
I am taking the date difference between two dates on each line. For example, the days to inspect is found like this:
SELECT (CASE
WHEN year(SYC_ext.DTRECDCUST) <> 1900
AND year(SYC_ext.DTINSPECTED) <> 1900
THEN
DATEDIFF(dd, SYC_ext.DTRECDCUST, SYC_ext.DTINSPECTED)
ELSE
NULL
END)
AS [AverageDaystoInspect]
Blah blah...
This creates a row of different DateDiff calcuations which end up looking like this:
Product Class Average Days to Inspect Average Days to Process SO Average Days to Ship to Affiliate Average Days to Quote Average Days to Receive Ammended PO
01 2 4 8 27 21
01 2 4 8 27 21
01 3 5 2 88 8
01 3 5 2 88 8
01 1 8 11 72 1
01 3 2 7 27 0
01 5 2 7 27 0
Please forgive the spacing problems.
So, each of these rows in the data set starts with a number which represents a product category in this case 01 and is followed by a series of day calculations.
I can average these values and give you an average for each category per product class. However, they want the day calculations along the left side of a table or matrix with the product classes along the top, exactly opposite of what I have here. If I pivot them in Excel, I get the following:
01 05 10
Avg Days to Inspect 5 3 5
Avg Days to Process PO 4 5 5
Avg Days to Ship 20 18 19
I hope this makes sense. Any idea how I can perform this in SSRS or t-sql?

This will do it, assuming that you can load the data from your first query into a intermediate temporary table:
DECLARE #t TABLE
(
ProductClass INT
, InspectDays INT
, ProcessDays INT
, ShipDays INT
)
INSERT INTO #t
SELECT 1
, 2
, 4
, 8
UNION ALL
SELECT 1
, 4
, 5
, 2
UNION ALL
SELECT 5
, 2
, 4
, 8
UNION ALL
SELECT 10
, 3
, 5
, 2
SELECT 'Avg Inspect'
, [01] = AVG(CASE WHEN ProductClass = 1 THEN InspectDays
END)
, [05] = AVG(CASE WHEN ProductClass = 5 THEN InspectDays
END)
, [10] = AVG(CASE WHEN ProductClass = 10 THEN InspectDays
END)
FROM #t
UNION ALL
SELECT 'Avg Process'
, [01] = AVG(CASE WHEN ProductClass = 1 THEN ProcessDays
END)
, [05] = AVG(CASE WHEN ProductClass = 5 THEN ProcessDays
END)
, [10] = AVG(CASE WHEN ProductClass = 10 THEN ProcessDays
END)
FROM #t

Related

SQL Server, running total, reset for each month and sum again

I have a calendar table where working days are marked.
Now I need a running total called "current_working_day" which sums up the working days until the end of a month and restarts again.
This is my query:
select
WDAYS.Date,
WDAYS.DayName,
WDAYS.WorkingDay,
sum(WDAYS.WorkingDay) OVER(order by (Date), MONTH(Date), YEAR(Date)) as 'current_working_day',
sum(WDAYS.WorkingDay) OVER(PARTITION by YEAR(WDAYS.Date), MONTH(WDAYS.Date) ) total_working_days_per_month
from WDAYS
where YEAR(WDAYS.Date) = 2022
This is my current output
Date
DayName
WorkingDay
current_working_day
total_working_days_per_month
2022-01-27
Thursday
1
19
21
2022-01-28
Friday
1
20
21
2022-01-29
Saturday
0
20
21
2022-01-30
Sunday
0
20
21
2022-01-31
Monday
1
21
21
2022-02-01
Tuesday
1
22
20
2022-02-02
Wednesday
1
23
20
2022-02-03
Thursday
1
24
20
But the column "current_workind_day" should be like this
Date
DayName
WorkingDay
current_working_day
total_working_days_per_month
2022-01-27
Thursday
1
19
21
2022-01-28
Friday
1
20
21
2022-01-29
Saturday
0
20
21
2022-01-30
Sunday
0
20
21
2022-01-31
Monday
1
21
21
2022-02-01
Tuesday
1
1
20
2022-02-02
Wednesday
1
2
20
2022-02-03
Thursday
1
3
20
Thanks for any advice.
You can try to use PARTITION by with EOMONTH function which might get the same result but better performance, then you might only need to order by Date instead of using the function with the date.
select
WDAYS.Date,
WDAYS.DayName,
WDAYS.WorkingDay,
sum(WDAYS.WorkingDay) OVER(PARTITION by EOMONTH(WDAYS.Date) order by Date) as 'current_working_day',
sum(WDAYS.WorkingDay) OVER(PARTITION by EOMONTH(WDAYS.Date) ) total_working_days_per_month
from WDAYS
where YEAR(WDAYS.Date) = 2022

Using SQL (or T-SQL), How to get the length of Time a value was 0 or 1 each hour from a table that records the date and time a value was set?

I have a table with the following data:
StartTime Value
----------------------------------------------
2016-12-20 08:09:00 1
2016-12-20 09:53:00 0
2016-12-20 11:10:21 1
2016-12-20 11:30:00 1
2016-12-20 12:20:30 0
.... ....
.... ....
2016-12-20 23:44:10 1
2016-12-21 00:33:00 0
2016-12-21 01:05:05 0
So, what I'm trying to do is find how long a value was 0 or 1 during each hour, where the hours are from 00:00:00 - 01:00:00, then from 01:00:00 - 02:00:00 ... 23:00:00 - 00:00:00.
what I want to achieve looks like this:
HourStart Period Value Was 0 Period Value Was 1
--------------------------------------------------------------------------
2016-12-20 00:00:00 50 min 10 min
2016-12-20 01:00:00 30 30
2016-12-20 02:00:00 32 28
2016-12-20 03:00:00 60 00
2016-12-20 04:00:00 21 39
.... .... ....
.... .... ....
2016-12-20 21:00:00 30 30
2016-12-20 22:00:00 20 40
2016-12-20 23:00:00 10 50
2016-12-21 00:00:00 01 59
2016-12-21 01:00:00 30 30
.... ....
By the way, I'm using Microsoft Transact-SQL.
Thanks.
Here is one way to do this:
First, create and populate sample table (Please save us this step in your future questions)
DECLARE #T as TABLE
(
startTime datetime,
value int
)
INSERT INTO #T VALUES
('2016-12-20 08:09:00', 1),
('2016-12-20 09:53:00', 0),
('2016-12-20 11:10:21', 1),
('2016-12-20 11:30:00', 1),
('2016-12-20 12:20:30', 0),
('2016-12-20 23:44:10', 1),
('2016-12-21 00:33:00', 0),
('2016-12-21 01:05:05', 0)
Then, use a couple of common table expressions - one to get rid of the minutes and seconds of the start time, and the second one to get the start time of the previous row:
;WITH cte1 as
(
SELECT StartTime,
value,
DATEADD(SECOND, -DATEPART(SECOND, startTime), DATEADD(MINUTE, -DATEPART(MINUTE, startTime), startTime)) As startHour
FROM #T
), cte2 as
(
SELECT StartTime,
value,
StartHour,
DATEDIFF(MINUTE, ISNULL(LAG(startTime) OVER(Partition by startHour ORDER BY startTime), startHour), startTime) As minutes
FROM cte1
)
Then, select from the second cte:
SELECT startHour,
value,
CASE WHEN value = 1 THEN
minutes
ELSE
60 - minutes
END as minutes_1,
CASE WHEN value = 0 THEN
minutes
ELSE
60 - minutes
END as minutes_0
FROM cte2
Results:
startHour value minutes_1 minutes_0
20.12.2016 08:00:00 1 9 51
20.12.2016 09:00:00 0 7 53
20.12.2016 11:00:00 1 10 50
20.12.2016 11:00:00 1 20 40
20.12.2016 12:00:00 0 40 20
20.12.2016 23:00:00 1 44 16
21.12.2016 00:00:00 0 27 33
21.12.2016 01:00:00 0 55 5

I want to transform part of columns to rows

I have table A
mon start end tue start end
sub009 13:00 14:00 sub004 15:00 15:30
sub004 14:00 15:00 sub005 15:30 16:00
sub005 15:00 16:00 sub009 16:00 16:30
and I am trying to get this
13:00 14:00 15:00 15:30 16:00 16:30
mon sub009 sub004 sub005
tue sub004 sub005 sub009
please I need help
Assuming you have table like this
table - monday_schedule
___________________________
Subject |startTime |endTime
___________________________
sub009 13:00 14:00
sub004 14:00 15:00
sub005 15:00 16:00
And you are plotting the subject names in the end time column, and all times are in interval of 30 min.
Then query like below will help:
select d,[14:00],[14:30],[15:00],[16:00],[16:30]
from
(select d,Subject, dayss
from
(select 'MON' as d,Subject, startTime, endTime from monday_schedule
UNION
select 'Tue' as d,Subject, startTime, endTime from tuesday_schedule
) s
UNPIVOT
( dayss for hourss in ([endTime]) )up)s
PIVOT
(
Max(Subject) for
dayss in ([14:00],[14:30],[15:00],[16:00],[16:30])
)p
Sample fiddle link : http://sqlfiddle.com/#!6/6ec0a/3

Total Minutes by Hour

I have two fields in a data table - "startTime" and "endTime." These two fields represent a duration of time the user spent on a particular task. These are varchar fields. So, let's say we have a startTime of "21:05:00" and an endTime of "22:09:00." I need the code to sum the total number of minutes spent in hour 21, and the 22 hundred hour separately (i.e. 9 minutes). So, not just a simple minute difference, but a breakdown by hour.
What might be the best way to do that?
Thus far, I have created a table that will return all possible hours in a 24-hour period. Here's a sample:
Hour startTime endTime
0 2015-01-01 00:00:00.000 2015-01-01 01:00:00.000
1 2015-01-01 01:00:00.000 2015-01-01 02:00:00.000
2 2015-01-01 02:00:00.000 2015-01-01 03:00:00.000
And I have converted the startTime field from varchar to dateteime and called it sessionHour:
Convert(datetime, startTime) As sessionHour
Additionally, I was able to get the hour of the startTime by doing:
DateAdd(Minute, 60 * (DateDiff(Minute, 0, startTime) / 60), 0) As hourOf
Beyond that, I am lost as to how to parse out the minutes per hour.
You are very close. You just need to combine the numbers table with the data. I'll use CROSS APPLY for it. Here is SQLFiddle with the final solution.
Sample data
DECLARE #Durations TABLE (ID int IDENTITY(1,1), StartTime datetime, EndTime datetime);
INSERT INTO #Durations VALUES
('2015-01-01 21:05:00', '2015-01-01 22:09:00'),
('2015-01-01 01:05:00', '2015-01-01 01:20:00'),
('2015-01-01 11:05:00', '2015-01-01 13:09:00'),
('2015-01-01 15:05:00', '2015-01-01 17:50:00'),
('2015-01-01 16:30:00', '2015-01-01 17:20:00');
I'll use datetime type from the beginning, since you've converted your varchar values to proper datetime.
I'll use a table of numbers. It should have as many rows as the longest duration in hours in your data. It could be more than 24. In general, it is useful to have such table in a database for other reports.
DECLARE #Numbers TABLE (Number int);
INSERT INTO #Numbers VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10);
-- Number of rows in this table should be more than the longest duration in hours
I'll need some reference point in time for removing minute part of the datetime. It could be any date-time as long as it doesn't have minutes and seconds.
DECLARE #VarStart datetime;
SET #VarStart = '2000-01-01';
Main step - Expand the data
SELECT *
FROM
#Durations AS D
CROSS APPLY
(
SELECT N.Number
FROM #Numbers AS N
WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
) AS CA_Number
ORDER BY ID;
ID StartTime EndTime Number
1 2015-01-01 21:05:00 2015-01-01 22:09:00 0
1 2015-01-01 21:05:00 2015-01-01 22:09:00 1
2 2015-01-01 01:05:00 2015-01-01 01:20:00 0
3 2015-01-01 11:05:00 2015-01-01 13:09:00 0
3 2015-01-01 11:05:00 2015-01-01 13:09:00 1
3 2015-01-01 11:05:00 2015-01-01 13:09:00 2
4 2015-01-01 15:05:00 2015-01-01 17:50:00 0
4 2015-01-01 15:05:00 2015-01-01 17:50:00 1
4 2015-01-01 15:05:00 2015-01-01 17:50:00 2
5 2015-01-01 16:30:00 2015-01-01 17:20:00 0
5 2015-01-01 16:30:00 2015-01-01 17:20:00 1
You can see that we created several rows for each original row depending on the duration of the original row. The rest is simple arithmetic.
Minutes per hour
SELECT *
,DATEDIFF(minute, MaxStart, MinEnd) AS MinutesPerHour
FROM
#Durations AS D
CROSS APPLY
(
SELECT N.Number
FROM #Numbers AS N
WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
) AS CA_Number
CROSS APPLY
(
SELECT
DATEADD(hour, CA_Number.Number, StartTime) AS HourStart
,DATEADD(hour, CA_Number.Number+1, StartTime) AS HourEnd
) AS CA_HourEnd
CROSS APPLY
(
-- Truncate to 1 hour.
SELECT
DATEADD(hour, DATEDIFF(hour, #VarStart, HourStart), #VarStart) AS HourStartFinal
,DATEADD(hour, DATEDIFF(hour, #VarStart, HourEnd), #VarStart) AS HourEndFinal
) AS CA_HourEndFinal
-- Intersect intervals [StartTime, EndTime] with [HourStartFinal, HourEndFinal]
CROSS APPLY
(
SELECT
CASE WHEN StartTime > HourStartFinal THEN StartTime ELSE HourStartFinal END AS MaxStart
,CASE WHEN EndTime < HourEndFinal THEN EndTime ELSE HourEndFinal END AS MinEnd
) AS CA_Intersect
ORDER BY ID;
In CA_HourEnd and CA_HourEndFinal I calculate the hour boundaries using the Number. Then intersect two intervals and calculate the number of minutes for each intersection. This is the result set:
ID StartTime EndTime Number HourStart HourEnd HourStartFinal HourEndFinal MaxStart MinEnd MinutesPerHour
1 2015-01-01 21:05:00 2015-01-01 22:09:00 0 2015-01-01 21:05:00 2015-01-01 22:05:00 2015-01-01 21:00:00 2015-01-01 22:00:00 2015-01-01 21:05:00 2015-01-01 22:00:00 55
1 2015-01-01 21:05:00 2015-01-01 22:09:00 1 2015-01-01 22:05:00 2015-01-01 23:05:00 2015-01-01 22:00:00 2015-01-01 23:00:00 2015-01-01 22:00:00 2015-01-01 22:09:00 9
2 2015-01-01 01:05:00 2015-01-01 01:20:00 0 2015-01-01 01:05:00 2015-01-01 02:05:00 2015-01-01 01:00:00 2015-01-01 02:00:00 2015-01-01 01:05:00 2015-01-01 01:20:00 15
3 2015-01-01 11:05:00 2015-01-01 13:09:00 0 2015-01-01 11:05:00 2015-01-01 12:05:00 2015-01-01 11:00:00 2015-01-01 12:00:00 2015-01-01 11:05:00 2015-01-01 12:00:00 55
3 2015-01-01 11:05:00 2015-01-01 13:09:00 1 2015-01-01 12:05:00 2015-01-01 13:05:00 2015-01-01 12:00:00 2015-01-01 13:00:00 2015-01-01 12:00:00 2015-01-01 13:00:00 60
3 2015-01-01 11:05:00 2015-01-01 13:09:00 2 2015-01-01 13:05:00 2015-01-01 14:05:00 2015-01-01 13:00:00 2015-01-01 14:00:00 2015-01-01 13:00:00 2015-01-01 13:09:00 9
4 2015-01-01 15:05:00 2015-01-01 17:50:00 0 2015-01-01 15:05:00 2015-01-01 16:05:00 2015-01-01 15:00:00 2015-01-01 16:00:00 2015-01-01 15:05:00 2015-01-01 16:00:00 55
4 2015-01-01 15:05:00 2015-01-01 17:50:00 1 2015-01-01 16:05:00 2015-01-01 17:05:00 2015-01-01 16:00:00 2015-01-01 17:00:00 2015-01-01 16:00:00 2015-01-01 17:00:00 60
4 2015-01-01 15:05:00 2015-01-01 17:50:00 2 2015-01-01 17:05:00 2015-01-01 18:05:00 2015-01-01 17:00:00 2015-01-01 18:00:00 2015-01-01 17:00:00 2015-01-01 17:50:00 50
5 2015-01-01 16:30:00 2015-01-01 17:20:00 0 2015-01-01 16:30:00 2015-01-01 17:30:00 2015-01-01 16:00:00 2015-01-01 17:00:00 2015-01-01 16:30:00 2015-01-01 17:00:00 30
5 2015-01-01 16:30:00 2015-01-01 17:20:00 1 2015-01-01 17:30:00 2015-01-01 18:30:00 2015-01-01 17:00:00 2015-01-01 18:00:00 2015-01-01 17:00:00 2015-01-01 17:20:00 20
Final query
Finally, I sum the minutes grouping by an hour:
SELECT
HourStartFinal
,SUM(DATEDIFF(minute, MaxStart, MinEnd)) AS SumMinutesPerHour
FROM
#Durations AS D
CROSS APPLY
(
SELECT N.Number
FROM #Numbers AS N
WHERE N.Number <= DATEDIFF(hour, StartTime, EndTime)
) AS CA_Number
CROSS APPLY
(
SELECT
DATEADD(hour, CA_Number.Number, StartTime) AS HourStart
,DATEADD(hour, CA_Number.Number+1, StartTime) AS HourEnd
) AS CA_HourEnd
CROSS APPLY
(
-- Truncate to 1 hour.
SELECT
DATEADD(hour, DATEDIFF(hour, #VarStart, HourStart), #VarStart) AS HourStartFinal
,DATEADD(hour, DATEDIFF(hour, #VarStart, HourEnd), #VarStart) AS HourEndFinal
) AS CA_HourEndFinal
-- Intersect intervals [StartTime, EndTime] with [HourStartFinal, HourEndFinal]
CROSS APPLY
(
SELECT
CASE WHEN StartTime > HourStartFinal THEN StartTime ELSE HourStartFinal END AS MaxStart
,CASE WHEN EndTime < HourEndFinal THEN EndTime ELSE HourEndFinal END AS MinEnd
) AS CA_Intersect
GROUP BY HourStartFinal
ORDER BY HourStartFinal;
Final result set
HourStartFinal SumMinutesPerHour
2015-01-01 01:00:00.000 15
2015-01-01 11:00:00.000 55
2015-01-01 12:00:00.000 60
2015-01-01 13:00:00.000 9
2015-01-01 15:00:00.000 55
2015-01-01 16:00:00.000 90
2015-01-01 17:00:00.000 70
2015-01-01 21:00:00.000 55
2015-01-01 22:00:00.000 9
SQLFiddle
While Vladimir Baranov's answer is correct, It uses way to many CROSS APPLY.
Another way to get the minutes by hour, can be using the fact that in a range of starttime and endtime, except for the first hour and the last all other hours will have minutediff of the hour as 60.
We can use this and construct our logic, Something like this.
DECLARE #UserTask TABLE (ID int IDENTITY(1,1),UserID INT,TaskID INT, StartTime datetime, EndTime datetime);
INSERT INTO #UserTask VALUES
(1,1,'2015-01-01 21:05:00', '2015-01-01 22:09:00'),
(1,1,'2015-01-01 01:05:00', '2015-01-01 01:20:00'),
(1,1,'2015-01-01 11:05:00', '2015-01-01 13:09:00'),
(1,1,'2015-01-01 15:05:00', '2015-01-01 17:50:00'),
(1,1,'2015-01-01 16:30:00', '2015-01-01 17:20:00'),
(2,2,'2015-01-01 21:05:00', '2015-01-01 22:09:00');
;WITH CTENum AS
(
SELECT 1 rn UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), CTEHours as
(
SELECT TOP 24 ROW_NUMBER()OVER(ORDER BY c1.rn) - 1 rn FROM CTENum c1 CROSS JOIN CTENum c2
)
SELECT ID,UserID,TaskID,StartTime,EndTime,rn as DayHour,
CASE WHEN r_asc = 1 AND r_desc = 1 THEN DATEDIFF(minute,StartTime,EndTime)
WHEN r_asc = 1 THEN 60 - DATEPART(minute,StartTime)
WHEN r_desc = 1 THEN DATEPART(minute,EndTime)
ELSE 60 END MinuteTime
FROM #UserTask
CROSS APPLY(
SELECT *,ROW_NUMBER()OVER(ORDER BY rn ASC) r_asc,ROW_NUMBER()OVER(ORDER BY rn DESC) r_desc
FROM CTEHours C
WHERE C.rn BETWEEN DATEDIFF(hour,CONVERT(VARCHAR(10),StartTime,112),StartTime) AND DATEDIFF(hour,CONVERT(VARCHAR(10),StartTime,112),EndTime)
) N
ORDER BY ID,DayHour
You might want to look into the datepart() function, it'll help you manipulate your dates more cleanly. For example:
select #endOfHour = dateadd(hour,
1,
datetimefromparts(
datepart(year, #startTime),
datepart(month, #startTime),
datepart(day, #startTime),
datepart(hour, #startTime),
0,
0,
0));
select h.theHour, #startTime, #endOfHour, datediff(minute, #startTime, #endOfHour)
from ListOfHours as h
where datepart(hour, #startTime) = h.Hour;

MS SQL: Group by date [duplicate]

This question already has answers here:
Sql Date Grouping with avaliable dates in database
(3 answers)
Closed 4 years ago.
ID DateTime EmailCount
93 6/1/2014 00:00:00 4
94 6/2/2014 00:00:00 4
95 6/3/2014 00:00:00 2
96 6/4/2014 00:00:00 2
97 6/5/2014 00:00:00 2
98 6/6/2014 00:00:00 2
99 6/7/2014 00:00:00 2
73 6/8/2014 00:00:00 2
74 6/9/2014 00:00:00 2
75 6/10/2014 00:00:00 4
76 6/11/2014 00:00:00 4
77 6/12/2014 00:00:00 2
78 6/13/2014 00:00:00 2
79 6/14/2014 00:00:00 2
80 6/16/2014 00:00:00 2
81 6/17/2014 00:00:00 4
82 6/18/2014 00:00:00 4
83 6/19/2014 00:00:00 4
84 6/20/2014 00:00:00 4
100 6/21/2014 00:00:00 4
101 6/22/2014 00:00:00 4
102 6/23/2014 00:00:00 4
103 6/24/2014 00:00:00 4
89 6/27/2014 00:00:00 4
90 6/28/2014 00:00:00 4
91 6/29/2014 00:00:00 4
92 6/30/2014 00:00:00 4
104 7/1/2014 00:00:00 4
105 7/2/2014 00:00:00 4
106 7/3/2014 00:00:00 4
121 7/6/2014 00:00:00 2
122 7/7/2014 00:00:00 2
123 7/8/2014 00:00:00 2
Generated Output
Startdate EndDate EmailCount
6/3/2014 00:00:00 6/14/2014 00:00:00 2
6/16/2014 00:00:00 6/16/2014 00:00:00 2
7/6/2014 00:00:00 7/8/2014 00:00:00 2
6/1/2014 00:00:00 6/11/2014 00:00:00 4
6/17/2014 00:00:00 6/24/2014 00:00:00 4
6/27/2014 00:00:00 7/3/2014 00:00:00 4
Here, the generated output is not perfect because I want StartDate to EndDate in groups like: (6/3/2014 to 6/9/2014 and EmailCount = 2) and (6/10/2014 to 6/11/2014 and EmailCount =4) and (6/12/2014 to 6/14/2014 and EmailCount =2). Also, date not in database should not be added to group.
A somewhat complex query to explain, but here goes an attempt;
If the time is always midnight, you could use a common table expression to assign a row number to each row, and group by the difference between the date and row number. As long as the sequence is not broken (ie the dates are consecutive and with the same emailid) they will end up in the same group and an outer query can easily extract the start and end date for each group;
WITH cte AS (
SELECT dateandtime, emailid,
ROW_NUMBER() OVER (PARTITION BY emailid ORDER BY dateandtime) rn
FROM mytable
)
SELECT MIN(dateandtime) start_time,
MAX(dateandtime) end_time,
MAX(emailid) emailid
FROM cte GROUP BY DATEADD(d, -rn, dateandtime) ORDER BY start_time
An SQLfiddle to test with.
If the datetimes are not always midnight, the grouping will fail. If that's the case, you could add a common table expression that converts the datetime to a date as a separate step before running this query.
You're looking for runs of consecutive dates in blocks with the same EmailID. This assumes you have no gaps in the dates. I'm not sure it's the most elegant approach but you can find a lot of stuff on this topic.
with BlockStart as (
select t.StartDate, t.EmailID
from T as t left outer join T as t2
on t2.StartDate = t1.StartDate - 1 and t2.EmailID = t1.EmailID
where t2.StartDate is null
union all
select max(StartDate) + 1, null
from T
) as BlockStart
select
StartDate,
(select min(StartDate) - 1 from BlockStart as bs2 where bs2 > bs.StartDate) as EndDate,
EmailID
from BlockStart as bs
where
EmailID is not null
-- /* or */ exists (select 1 from BlockStart as bs3 where bs3.StartDate > bs.StartDate)

Resources