Calculating NETWORKDAYS using a CalendarTable SQL Server 2008 - sql

Previously I created a SELECT to calculate Business Days (Weekends/Holydays) which didn't work for certain date ranges for each records. I was suggested to use a Calendar Table, which actually worked for other requirements I had on hold but I can't figure how to calculate INI and END for business days.
I've tried to do a SELECT with a semi left join and a join (suggested as well) but it doesn't seem to calculate at all.
This is the Calendar Table:
CalendarId DateValue DayNumberOfWeek NameOfDay NameOfMonth WeekOfYear JulianDay USAIsBankHoliday USADayName
----------- ----------------------- --------------- ---------- ----------- ----------- ----------- ---------------- -------------------
20100101 2010-01-01 00:00:00.000 5 Viernes Enero 1 1 1 Año nuevo
My table:
INI FIN DD --DATEDIFF BETWEEN DATES
------------------------------ ------------------------------ -----------
20180129 20180211 14
My attempt at calculating business days
SELECT CONVERT(NVARCHAR,INI,112) AS INI,
CONVERT(NVARCHAR,FIN,112) AS FIN,
DATEDIFF(DD, INI, FIN)+1 AS DD
FROM HISTORICOLICMED T
WHERE EXISTS(
SELECT 1 FROM CALENDAR C
WHERE T.INI = C.CALENDARID and T.FIN = C.CALENDARID
AND C.USAISBANKHOLIDAY = 0 BETWEEN T.INI AND T.FIN)
I expect getting the correct business day calculations including holidays so for the example above I will get something like:
INI FIN DD ND
------------------------------ ------------------------------ ----- -----
20180129 20180211 14 10

Sub-Select the count of rows in the calendar table that are between INI and FIN and are business days.
SELECT CONVERT(NVARCHAR,INI,112) AS INI,
CONVERT(NVARCHAR,FIN,112) AS FIN,
DATEDIFF(DD, INI, FIN)+1 AS DD,
(SELECT COUNT(*) FROM CalendarTable WHERE ...) AS ND
FROM HISTORICOLICMED

Proposed solution:
set datefirst 1;
select INI
,FIN
, DATEDIFF(DD, INI, FIN)+1 as DD
,(DATEDIFF(DD, INI, FIN)+1)
-(DATEDIFF(WK, INI, FIN)*2)
-(case DATEPART(DW, INI) when 7 then 1 else 0 end)
-(case DATEPART(DW, FIN) when 6 then 1 else 0 end)
-(select COUNT(*) from CALENDAR
where DateValue between INI and FIN
and USAIsBankHoliday = 1)
as ND -- Working days except US Bank holidays
from HISTORICOLICMED;

Related

SSRS Expression "Count of Days with No dissatisfied Customers" Cannot get it working

I have to get a expression in my SSRS table that achieves the count of days with no dissatisfied customers.
Right now I have an expression like this:
=RunningValue(IIF(Fields!SATISFACTION_LEVEL.Value <> "Dissatisfied",1,0),Sum, "DataSet1")
This gives me the number of rows that contain a satisfaction level other than Dissatisfied.
My issue is that I can't seem to get a count of days where there was no dissatisfied customer. I can't find a solution to counting the days. Essentially this is what it should do. If there was a record that day with a dissatisfied customer, don't count it. If there was no dissatisfied customers, tally it.
This will need to be done for the current year to date, and also for the year before.
I would really appreciate any help with this expression!
Thanks
UPDATE MORE INFO:
dataset structure is like this:
_______________________________________
| satisfaction_level | Date |
---------------------------------------
| Satisfied | 07/20/2020 |
| dissatisfied | 07/20/2020 |
| Satisfied | 07/20/2020 |
| Highly Satisfied | 07/20/2020 |
| Satisfied | 07/20/2020 |
| Satisfied | 07/21/2020 |
| Satisfied | 07/21/2020 |
| Highly Satisfied | 07/21/2020 |
expected functionality - for the day of 7/20/2020 there was 1 dissatisfied customer (do not tally), for the day of 7/21/2020 there were NO dissatisfied customers (tally). Resulting in a total number of days where there were NO dissatisfied customers. I hope this helps further explain the outcome needed.
Put SSRS to the side for now, the problem with counting days of anything is that its hard to count a row that is not there. For instance if I have a number of response records spread out over a week, but they only fall on 4 of the days, when we group by day, the query can only return results for the days that existed in the recordset:
DECLARE #Responses as Table
(
ENTRY_TIME DateTimeOffset, SATISFACTION_LEVEL VARCHAR(20)
)
INSERT INTO #Responses
VALUES
('2020-01-4', 'Dissatisfied'),
('2020-01-4', 'Dissatisfied'),
('2020-01-1', 'Satisfied'),
('2020-01-5', 'Dissatisfied'),
('2020-01-5', 'Satisfied'),
('2020-01-2', 'Dissatisfied')
SELECT
fn.DATE
, DATENAME(WEEKDAY, fn.DATE) as [Day]
, SUM(CASE SATISFACTION_LEVEL WHEN 'Dissatisfied' THEN 1 ELSE 0 END) as [Dissatisfied]
FROM #Responses
CROSS APPLY (SELECT CAST(ENTRY_TIME as Date) as [DATE]) as fn
GROUP BY fn.DATE
ORDER BY fn.DATE
DATE Day Dissatisfied
---------- ------------------------------ ------------
2020-01-01 Wednesday 0
2020-01-02 Thursday 1
2020-01-04 Saturday 2
2020-01-05 Sunday 1
(4 rows affected)
We can solve this problem by generating a series record set that we can join our real world data against that will ensure that we have a row for each day.
This can be achieved through the use of a recursive CTE, in the query below the grouped data result is joined to the series data, you could do this any number of different ways, you could even pivot the SATISFACTION_LEVEL column responses, this is just to illustrate
the technique of pre-processing the data in SQL before formatting it in an SSRS report:
DECLARE #From Date = '2019-12-30';
DECLARE #To Date = '2020-01-05';
;
WITH [Sequence] ([Date])
as
(
SELECT #From
UNION ALL
SELECT DATEADD(DAY, 1, [Date]) FROM [Sequence]
WHERE [Date] < #To
)
, [GroupedByDay]
as
(
SELECT
fn.DATE
, SUM(CASE SATISFACTION_LEVEL WHEN 'Dissatisfied' THEN 1 ELSE 0 END) as [Dissatisfied]
, SUM(CASE SATISFACTION_LEVEL WHEN 'Satisfied' THEN 1 ELSE 0 END) as [Satisfied]
FROM #Responses
CROSS APPLY (SELECT CAST(ENTRY_TIME as Date) as [DATE]) as fn
GROUP BY fn.DATE
)
SELECT
c.[Date]
, DATENAME(WEEKDAY, c.[DATE]) as [Day]
, ISNull([Dissatisfied],0) as [Dissatisfied]
, ISNULL([Satisfied],0) as [Satisfied]
FROM [GroupedByDay] g
RIGHT OUTER JOIN [Sequence] c ON g.[DATE] = c.[Date]
ORDER BY c.[Date]
Date Day Dissatisfied Satisfied
---------- ------------------------------ ------------ -----------
2019-12-30 Monday 0 0
2019-12-31 Tuesday 0 0
2020-01-01 Wednesday 0 1
2020-01-02 Thursday 1 0
2020-01-03 Friday 0 0
2020-01-04 Saturday 2 0
2020-01-05 Sunday 1 1
(7 rows affected)
Without specific information about your schema and current query, that's about the best I can offer, however data by day should be more than enough for you group this into year on year results within SSRS...
Or you could do it directly in SQL too if you want :)
#Update: Example where just the total count of days where there are no dissatisfied customers is returned:
DECLARE #From Date = '2019-12-30';
DECLARE #To Date = '2020-01-05';
;
WITH [Sequence] ([Date])
as
(
SELECT #From
UNION ALL
SELECT DATEADD(DAY, 1, [Date]) FROM [Sequence]
WHERE [Date] < #To
)
, [GroupedByDay]
as
(
SELECT
fn.DATE
, SUM(CASE SATISFACTION_LEVEL WHEN 'Dissatisfied' THEN 1 ELSE 0 END) as [Dissatisfied]
, SUM(CASE SATISFACTION_LEVEL WHEN 'Satisfied' THEN 1 ELSE 0 END) as [Satisfied]
FROM #Responses
CROSS APPLY (SELECT CAST(ENTRY_TIME as Date) as [DATE]) as fn
GROUP BY fn.DATE
)
, [InjectedMissingDays]
as
(
SELECT
c.[Date]
, DATENAME(WEEKDAY, c.[DATE]) as [Day]
, ISNull([Dissatisfied],0) as [Dissatisfied]
, ISNULL([Satisfied],0) as [Satisfied]
FROM [GroupedByDay] g
RIGHT OUTER JOIN [Sequence] c ON g.[DATE] = c.[Date]
)
--Overall
SELECT COUNT(1) as [Days with No Dissatisfied Customers] FROM [InjectedMissingDays] WHERE Dissatisfied = 0
Days with No Dissatisfied Customers
-----------------------------------
4
(1 row affected)

Grouping Data and maintaing Calculated fields

I have few fields like Department, WorkLocation, Employees and Work Date
Need to get the output based on the Given Date Parameter
Department WorkLocation EmployeeID WorkDate
---------- ------------ ---------- --------
D1 L2 121 05/01/2018
D1 L1 141 05/01/2018
D2 L1 151 05/02/2018
and so on
I have tried using temp tables to store data and get from there but is not working
Department , Number of Employees worked on the given date,Number of Employees worked in Last 7 days from the given Date,Number of Employees worked from the Begining of Month to the Given Date
i.e
Department WorkLocation Count_On_Date Count_Last7Days Count_MonthToDate
---------- ----------- ------------ --------------- -----------------
D1 L1 xxx xxx xxx
D2 L1 xxx xxx xxx
D1 L2 xxx xxx xxx
D2 L2 xxx xxx xxx
Please Help
Thanks
Use the GROUP BY clause and do the conditional aggregation with case expression
select Department, WorkLocation,
sum(case when WorkDate = #givendate then 1 else 0 end) as Count_On_Date,
sum(case when WorkDate >= dateadd(day, -8, #givendate) and
WorkDate < #givendate
then 1 else 0 end) as Count_Last7Days,
sum(case when month(WorkDate) = month(#givendate)
then 1 else 0 end) as Count_MonthToDate
from table t
group by Department, WorkLocation;
This I tested, works like expected. You need to use inline counts with different criteria. To produce each line we have case clauses with one-way conditions to cancel out the unwanted values.
select
wd.Department,
wd.WorkLocation,
COUNT(1) as Count_On_Date,
COUNT(case when DATEDIFF(DAY, wd.WorkDate, GETDATE()) < 7 then 1 end) as Count_Last7Days,
COUNT(case when DATEDIFF(DAY, wd.WorkDate, GETDATE()) < 30 then 1 end) as Count_MonthToDate
from WorkData as wd
group by wd.Department, wd.WorkLocation

OR clause with subquery taking too much time

Date Range query taking too much time.
Just i removed the one condition then it working fine taking 2 second. If adding
then 30 seconds.
SELECT UserName,COUNT ('t') TOTAL
FROM TRANSACTIONS E1
WHERE E1.START_DATE BETWEEN TO_DATE ('20130101', 'YYYYMMDD') AND TO_DATE ('20140101', 'YYYYMMDD')
AND
(
TO_CHAR (E1.START_DATE, 'D') in ( 7)
OR Exists(SELECT 1 FROM HOLIDAYS TT
WHERE E1.START_DATE BETWEEN TT.DATE_FROM AND TT.DATE_TO )
)
AND EXISTS (SELECT 't' FROM TRANSACTIONS_ORG E2 WHERE E1.TRANTYPE = E2.tran_type)
GROUP BY UserName;
HOLIDAYS table
Id FromDate ToDate Description
1 1-Feb-11 3-Feb-11 Maintance
3 2-Sep-09 5-Sep-09 Eid Holiday Fine Block
4 3-Dec-09 4-Dec-09 Due to System Problem
5 4-Dec-07 04-Dec-07 National Day
EIDTED
I figured out that the issue is not in the date range. but the OR clause in the
TO_CHAR (E1.START_DATE, 'D') in ( 5,6)
OR
Exists(SELECT 1 FROM HOLIDAYS TT
WHERE E1.START_DATE BETWEEN TT.DATE_FROM AND TT.DATE_TO )
if removed OR and put AND then fine and if shuffle conditions with OR still same issue.
The problem is likely with the OR <subquery> construct.
If there can only be one holiday for a particular date, then you could use the following:
select username
,count(*)
from transactions e1
left join holidays tt on(e1.start_date between tt.date_from and tt.date_to)
where e1.start_date between date '2017-02-01' and date '2018-02-01'
and ( to_char(e1.start_date, 'D') in(5, 6)
or tt.date_from is not null)
)
and exists(
select *
from transactions_org e2
where e1.trantype = e2.tran_type
)
group
by username;
This entire category of problems can be solved by implementing a Calendar table. If you had such a table with one record per date, you could easily add columns indicating day of week and holiday flags and such. If your calendar table looked something like this:
DAY DAYNAME IS_WEEKEND IS_WEEKDAY HOLINAME
---------- --------- ---------- ---------- ------------
2017-02-01 WEDNESDAY 0 1
2017-02-02 THURSDAY 0 1
2017-02-03 FRIDAY 0 1 Some holiday
2017-02-04 SATURDAY 1 0
2017-02-05 SUNDAY 1 0
2017-02-06 MONDAY 0 1
2017-02-07 TUESDAY 0 1
2017-02-08 WEDNESDAY 0 1
Your query could be rewritten as:
from transactions e1
join calendar c on(c.day = trunc(e1.start_date, 'DD')) -- Remove hours, minutes
where e1.start_date between date '2017-02-01' and date '2018-02-01'
and ( c.weekday in('THURSDAY', 'FRIDAY') -- Either specific weekdays
or c.holiname is not null -- or there is a holiday
)
and exists(
select *
from transactions_org e2
where e1.trantype = e2.tran_type
)

SQL how to count census points occurring between date records

I’m using MS-SQL-2008 R2 trying to write a script that calculates the Number of Hospital Beds occupied on any given day, at 2 census points: midnight, and 09:00.
I’m working from a data set of patient Ward Stays. Basically, each row in the table is a record of an individual patient's stay on a single ward, and records the date/time the patient is admitted onto the ward, and the date/time the patient leaves the ward.
A sample of this table is below:
Ward_Stay_Primary_Key | Ward_Start_Date_Time | Ward_End_Date_Time
1 | 2017-09-03 15:04:00.000 | 2017-09-27 16:55:00.000
2 | 2017-09-04 18:08:00.000 | 2017-09-06 18:00:00.000
3 | 2017-09-04 13:00:00.000 | 2017-09-04 22:00:00.000
4 | 2017-09-04 20:54:00.000 | 2017-09-08 14:30:00.000
5 | 2017-09-04 20:52:00.000 | 2017-09-13 11:50:00.000
6 | 2017-09-05 13:32:00.000 | 2017-09-11 14:49:00.000
7 | 2017-09-05 13:17:00.000 | 2017-09-12 21:00:00.000
8 | 2017-09-05 23:11:00.000 | 2017-09-06 17:38:00.000
9 | 2017-09-05 11:35:00.000 | 2017-09-14 16:12:00.000
10 | 2017-09-05 14:05:00.000 | 2017-09-11 16:30:00.000
The key thing to note here is that a patient’s Ward Stay can span any length of time, from a few hours to many days.
The following code enables me to calculate the number of beds at both census points for any given day, by specifying the date in the case statement:
SELECT
'05/09/2017' [Date]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 00:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 00:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 00:00]
,SUM(case when Ward_Start_Date_Time <= '05/09/2017 09:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 09:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 09:00]
FROM
WardStaysTable
And, based on the sample 10 records above, generates this output:
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
05/09/2017 | 4 | 4
To perform this for any number of days is obviously onerous, so what I’m looking to create is a query where I can specify a start/end date parameter (e.g. 1st-5th Sept), and for the query to then evaluate the Ward_Start_Date_Time and Ward_End_Date_Time variables for each record, and – grouping by the dates defined in the date parameter – count each time the 00:00:00.000 and 09:00:00.000 census points fall between these 2 variables, to give an output something along these lines (based on the above 10 records):
Date | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
01/09/2017 | 0 | 0
02/09/2017 | 0 | 0
03/09/2017 | 0 | 0
04/09/2017 | 1 | 1
05/09/2017 | 4 | 4
I’ve approached this (perhaps naively) thinking that if I use a cte to create a table of dates (defined by the input parameters), along with associated midnight and 9am census date/time points, then I could use these variables to group and evaluate the dataset.
So, this code generates the grouping dates and census date/time points:
DECLARE
#StartDate DATE = '01/09/2017'
,#EndDate DATE = '05/09/2017'
,#0900 INT = 540
SELECT
DATEADD(DAY, nbr - 1, #StartDate) [Date]
,CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))) [MidnightDate]
,DATEADD(mi, #0900,(CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, #StartDate))))) [0900Date]
FROM
(
SELECT
ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
The stumbling block I’ve hit is how to join the cte to the WardStays dataset, because there’s no appropriate key… I’ve tried a few iterations of using a subquery to make this work, but either I’m taking the wrong approach or I’m getting my syntax in a mess.
In simple terms, the logic I’m trying to create to get the output is something like:
SELECT
[Date]
,SUM (case when WST.Ward_Start_Date_Time <= [MidnightDate] AND (WST.Ward_End_Date_Time >= [MidnightDate] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 00:00]
,SUM (case when WST.Ward_Start_Date_Time <= [0900Date] AND (WST.Ward_End_Date_Time >= [0900Date] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 09:00]
FROM WardStaysTable WST
GROUP BY [Date]
Is the above somehow possible, or am I barking up the wrong tree and need to take a different approach altogether? Appreciate any advice.
I would expect something like this:
WITH dates as (
SELECT CAST(#StartDate as DATETIME) as dte
UNION ALL
SELECT DATEADD(DAY, 1, dte)
FROM dates
WHERE dte < #EndDate
)
SELECT dates.dte [Date],
SUM(CASE WHEN Ward_Start_Date_Time <= dte AND
Ward_END_Date_Time >= dte
THEN 1 ELSE 0
END) as num_beds_0000,
SUM(CASE WHEN Ward_Start_Date_Time <= dte + CAST('09:00' as DATETIME) AND
Ward_END_Date_Time >= dte + CAST('09:00' as DATETIME)
THEN 1 ELSE 0
END) as num_beds_0900
FROM dates LEFT JOIN
WardStaysTable wt
ON wt.Ward_Start_Date_Time <= DATEADD(day, 1, dates.dte) AND
wt.Ward_END_Date_Time >= dates.dte
GROUP BY dates.dte
ORDER BY dates.dte;
The cte is just creating the list of dates.
What a cool exercise. Here is what I came up with:
CREATE TABLE #tmp (ID int, StartDte datetime, EndDte datetime)
INSERT INTO #tmp values(1,'2017-09-03 15:04:00.000','2017-09-27 06:55:00.000')
INSERT INTO #tmp values(2,'2017-09-04 08:08:00.000','2017-09-06 18:00:00.000')
INSERT INTO #tmp values(3,'2017-09-04 13:00:00.000','2017-09-04 22:00:00.000')
INSERT INTO #tmp values(4,'2017-09-04 20:54:00.000','2017-09-08 14:30:00.000')
INSERT INTO #tmp values(5,'2017-09-04 20:52:00.000','2017-09-13 11:50:00.000')
INSERT INTO #tmp values(6,'2017-09-05 13:32:00.000','2017-09-11 14:49:00.000')
INSERT INTO #tmp values(7,'2017-09-05 13:17:00.000','2017-09-12 21:00:00.000')
INSERT INTO #tmp values(8,'2017-09-05 23:11:00.000','2017-09-06 07:38:00.000')
INSERT INTO #tmp values(9,'2017-09-05 11:35:00.000','2017-09-14 16:12:00.000')
INSERT INTO #tmp values(10,'2017-09-05 14:05:00.000','2017-09-11 16:30:00.000')
DECLARE
#StartDate DATE = '09/01/2017'
,#EndDate DATE = '10/01/2017'
, #nHours INT = 9
;WITH d(OrderDate) AS
(
SELECT DATEADD(DAY, n-1, #StartDate)
FROM (SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS x(n)
)
, CTE AS(
select OrderDate, t2.*
from #tmp t2
cross apply(select orderdate from d ) d
where StartDte >= #StartDate and EndDte <= #EndDate)
select OrderDate,
SUM(CASE WHEN OrderDate >= StartDte and OrderDate <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 00:00],
SUM(CASE WHEN StartDTE <= DateAdd(hour,#nHours,CAST(OrderDate as datetime)) and DateAdd(hour,#nHours,CAST(OrderDate as datetime)) <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 09:00]
from CTE
GROUP BY OrderDate
This should allow you to check for any hour of the day using the #nHours parameter if you so choose. If you only want to see records that actually fall within your date range then you can filter the cross apply on start and end dates.

SQL Server : Gap / Island, datetime, contiguous block 365 day block

I have a table that looks like this:-
tblMeterReadings
id meter period_start period_end amount
1 1 2014-01-01 00:00 2014-01-01 00:29:59 100.3
2 1 2014-01-01 00:30 2014-01-01 00:59:59 50.5
3 1 2014-01-01 01:00 2014-01-01 01:29:59 70.7
4 1 2014-01-01 01:30 2014-01-01 01:59:59 900.1
5 1 2014-01-01 02:00 2014-01-01 02:29:59 400.0
6 1 2014-01-01 02:30 2014-01-01 02:59:59 200.3
7 1 2014-01-01 03:00 2014-01-01 03:29:59 100.8
8 1 2014-01-01 03:30 2014-01-01 03:59:59 140.3
This is a tiny "contiguous block" from '2014-01-01 00:00' to '2014-01-01 3:59:59'.
In the real table there are "contiguous blocks" of years in length.
I need to find the the period_start and period_end of the most recent CONTINUOUS 365 COMPLETE DAYs (fileterd by meter column).
When I say COMPLETE DAYs I mean a day that has entries spanning 00:00 to 23:59.
When I say CONTINUOUS I mean there must be no days missing.
I would like to select all the rows that make up this block of CONTINUOUS COMPLETE DAYs.
I also need an output like:
block_start block_end total_amount_for_block
2013-02-26 00:00 2014-02-26 23:59:59 1034234.5
This is beyond me, so if someone can solve... I will be very impressed.
Since your granularity is 1 second, you need to expand your periods into all the date/times between the start and end at 1 second intervals. To do this you need to cross join with a numbers table (The numbers table is generated on the fly by ranking object ids from an arbitrary system view, I have limited it to TOP 86400 since this is the number of seconds in a day, and you have stated your time periods never span more than one day):
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
)
SELECT r.ID, r.meter, dt.[DateTime]
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End;
You then have your continuous range in which to perform the normal gaps and islands grouping:
WITH Numbers AS
( SELECT TOP (86400)
Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
ORDER BY a.object_id
), Grouped AS
( SELECT r.meter,
Amount = CASE WHEN Number = 1 THEN r.Amount ELSE 0 END,
dt.[DateTime],
GroupingSet = DATEADD(SECOND,
-DENSE_RANK() OVER(PARTITION BY r.Meter
ORDER BY dt.[DateTime]),
dt.[DateTime])
FROM tblMeterReadings r
CROSS JOIN Numbers n
OUTER APPLY
( SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
) dt
WHERE dt.[DateTime] <= r.Period_End
)
SELECT meter,
PeriodStart = MIN([DateTime]),
PeriodEnd = MAX([DateTime]),
Amount = SUM(Amount)
FROM Grouped
GROUP BY meter, GroupingSet
HAVING DATEADD(YEAR, 1, MIN([DateTime])) < MAX([DateTime]);
N.B. Since the join to Number causes amounts to be duplicated, it is necessary to set all duplicates to 0 using CASE WHEN Number = 1 THEN r.Amount ELSE 0 END, i.e only include the amount for the first row for each ID
Removing the Having clause for your sample data will give:
meter | PeriodStart | PeriodEnd | Amount
------+---------------------+---------------------+----------
1 | 2014-01-01 00:00:00 | 2014-01-01 03:59:59 | 1963
Example on SQL Fiddle
You could try this:
Select MIN(period_start) as "block start"
, MAX(period_end) as "block end"
, SUM(amount) as "total amount"
FROM YourTable
GROUP BY datepart(year, period_start)
, datepart(month, period_start)
, datepart(day, period_start)
, datepart(year, period_end)
, datepart(month, period_end)
, datepart(day, period_end)
Having datepart(year, period_start) = datepart(year, period_end)
AND datepart(month, period_start) = datepart(month, period_end)
AND datepart(day, period_start) = datepart(day, period_end)
AND datepart(hour, MIN(period_start)) = 0
AND datepart(minute,MIN(period_start)) = 0
AND datepart(hour, MAX(period_end)) = 23
AND datepart(minute,MIN(period_end)) = 59