SQL Showing Every Hour of Every Day - sql

I wrote the below code to break out my data that shows patient arrival and departure by day, into patient census by hour of every day.
The code works but for every date, instead of adding one hour each for the hours 0-23, it adds a second line for 0, so it breaks every day into 25 lines instead of 24. I'm pretty sure the problem is somewhere in the Cross Apply below, but I included the rest of the code for your reference.
I'd really appreciate any help you can give. Also, if you have any tips on how to post code in here and have it look more normal, let me know. Thank you!
--Create my temporary table
SELECT *
INTO #Temporary
FROM dbo.Census
WHERE YEAR(startdatetime) >= 2018
ORDER BY
startdatetime
,pt_id
--Use the Cross Apply to split out every day into every hour
SELECT
Date = CAST(D AS DATE)
,Hour = DATEPART(HOUR, D)
,pt_id
,cendate
,locationid
,[room-bed]
,startdatetime
,enddatetime
,minutes
,DayOfWeek
,WeekInt
,MyStartMinutes = 0
,MyEndMinutes = 0
INTO #Temporary2
FROM #Temporary A
CROSS APPLY
(
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + 1))
D = DATEADD(HOUR, -1 + ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )), A.startdatetime)
FROM master..spt_values n1
,master..spt_values n2
) B
--Update values for MyStartMinutes and MyEndMinutes
UPDATE #Temporary2
SET MyStartMinutes = CASE WHEN ( DATEPART(HOUR, startdatetime) = Hour )
THEN DATEPART(mi, enddatetime)
ELSE 0 END
UPDATE #Temporary2
SET MyEndMinutes = CASE WHEN ( DATEPART(HOUR, enddatetime) = Hour )
AND DATEDIFF(DAY, enddatetime, cendate) = 0
THEN DATEPART(mi, enddatetime)
ELSE 0 END
--Update values of startdatetime and enddatetime
UPDATE #Temporary2
SET startdatetime = DATEADD(HOUR, Hour, DATEADD(MINUTE, MyStartMinutes, CAST(CAST(startdatetime AS DATE) AS DATETIME)))
UPDATE #Temporary2
SET enddatetime = CASE WHEN ( Hour < 23 )
THEN ( DATEADD(HOUR, Hour + 1, DATEADD(MINUTE, MyEndMinutes, CAST(CAST(startdatetime AS DATE) AS DATETIME))))
WHEN Hour = 23
THEN ( DATEADD(HOUR, 0, DATEADD(MINUTE, MyEndMinutes, CAST(CAST(enddatetime AS DATE) AS DATETIME))))
ELSE '' END
--Update Value of Minutes
UPDATE #Temporary2
SET Minutes = DATEDIFF(mi, startdatetime, enddatetime)
SELECT *
FROM #Temporary2
ORDER BY minutes DESC
Here is the sample data from dbo.Census:
org pt_id cendate location bed startdate enddate minutes DOW
A 5 1/8/2018 7E 50 1/8/2018 8:00 1/9/2018 0:00 960 Mon
A 5 1/9/2018 7E 50 1/9/2018 0:00 1/10/2018 0:00 1440 Tue
A 5 1/10/2018 7E 50 1/10/2018 0:00 1/11/2018 0:00 1440 Wed
A 5 1/11/2018 7E 50 1/11/2018 0:00 1/11/2018 14:00 840 Thu
A 1 10/17/2016 ED 10 10/17/2016 1:05 10/17/2016 10:21 556 Mon
A 2 5/10/2017 4L 20 5/10/2017 15:09 5/11/2017 0:00 531 Wed
A 3 5/14/2017 4L 30 5/14/2017 0:00 5/14/2017 8:12 492 Sun
A 4 6/3/2017 5C 40 6/3/2017 0:00 6/4/2017 0:00 1440 Sat

I think you're correct that your CROSS APPLY is the culprit here. After testing your code on my own sample data, I found that if there were separate records in dbo.Census that had overlapping days between their startdates and enddates, those dates and hours would get duplicated, depending on how many records and how many days they share.
So what I did was add the PK from dbo.Census into the CROSS APPLY, and then used that id column in the subquery to filter the results to only those where the ids matched. Here's the section of code I changed:
SELECT
Date = CAST(D AS DATE)
,Hour = DATEPART(HOUR, D)
,A.pt_id
,cendate
,locationid
,[room-bed]
,startdatetime
,enddatetime
,minutes
,DayOfWeek
,WeekInt
,MyStartMinutes = 0
,MyEndMinutes = 0
INTO #Temporary2
FROM #Temporary A
CROSS APPLY
(
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + 1))
D = DATEADD(HOUR, -1 + ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )), A.startdatetime)
,A.pt_id
FROM master..spt_values n1
,master..spt_values n2
) B
WHERE A.pt_id = B.pt_id
I made the assumption that pt_id is the primary key of dbo.Census. If that's not the case, you would just replace pt_id with the PK from dbo.Census.

Related

How to select weekly data from daily data

There are two columns, XCHG_DATE and USD_KRW, and the table contains daily data.
What I am trying to do is to select weekly data from the daily data.
E.g) (2022-03-01, value), (2022-03-08, value), (2022-03-15, value), (2022-03-22, value) and so one...
The current SQL I have is:
SELECT CE.XCHG_DATE xchageDate
, CE.USD_KRW usdKrw
FROM(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE), 4) xchageDate
FROM CWL_EXCHANGE
WHERE XCHG_DATE BETWEEN '20220301' AND '20220523'
GROUP BY DATEADD(WEEK, DATEDIFF(WEEK, 1, XCHG_DATE),4)
) AS RESULT
LEFT JOIN CWL_EXCHANGE CE
ON CE.XCHG_DATE = RESULT.xchageDate
WHERE RESULT.xchageDate = CE.XCHG_DATE
ORDER BY CE.XCHG_DATE;
This query gives me weekly data from 20220304 to 20220520, but I need the data from 2022-03 to 2022-05-23(today's date).
Can anyone please help me of how to solve this problem?
Thanks in advance!
Sample Data:
COLUMNS = XCHG_DATE USD_KRW
2022-05-23 1
2022-05-22 2
2022-05-21 3
2022-05-20 4
2022-05-19 5
2022-05-18 6
2022-05-17 7
2022-05-16 8
2022-05-15 9
2022-05-14 10
2022-05-13 11
2022-05-12 12
2022-05-11 13
2022-05-10 14
2022-05-09 15
2022-05-08 16
2022-05-07 17
2022-05-06 18
Current Output :
20220506 18
20220513 11
20220520 4
Expected Output :
20220509 15
20220516 8
20220523 1
You will need a calendar table with Weekdaynumber to arrive at the earlier weekdays corresponding to Today's date(23 May 2022). This will make the calculation easier.
DECLARE #StartDate DATE = '2022-05-01'
DECLARE #EndDate DATE = '2022-05-31'
declare #table table (XCHG_DATE date, USD_KRW int);
insert into #table
values ('2022-05-23', 1 )
,('2022-05-22', 2 )
,('2022-05-21', 3 )
,('2022-05-20', 4 )
,('2022-05-19', 5 )
,('2022-05-18', 6 )
,('2022-05-17', 7 )
,('2022-05-16', 8 )
,('2022-05-15', 9 )
,('2022-05-14', 10 )
,('2022-05-13', 11 )
,('2022-05-12', 12 )
,('2022-05-11', 13 )
,('2022-05-10', 14 )
,('2022-05-09', 15 )
,('2022-05-08', 16 )
,('2022-05-07', 17 )
,('2022-05-06', 18 );
;WITH Cal(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM Cal
WHERE n < DATEDIFF(DAY, #StartDate, #EndDate)
),
FnlDt(d,weeknum) AS
(
SELECT DATEADD(DAY, n, #StartDate),datepart(dw, DATEADD(DAY, n, #StartDate)) as weeknum FROM Cal
)
SELECT t.XCHG_DATE,t.USD_KRW
from FnlDt as c
INNER JOIN #table as t
on t.XCHG_DATE = c.d
where c.weeknum = datepart(dw, getdate()) -- Weekdaynumber today
XCHG_DATE
USD_KRW
2022-05-23
1
2022-05-16
8
2022-05-09
15
Sub in GETDATE() for the hardcoded value if you always want todays date
SELECT *
FROM CWL_EXCHANGE
WHERE DATEPART(dw, XCHG_DATE) = DATEPART(dw, '20220523')

Sql Server - Count occurrences of datetime group by specific interval

I have this table with sample data:
MY_TABLE
------------------------------------------
ID DateVal other columns
------------------------------------------
1 2017-01-14 11:00:00 ...
2 2017-01-14 11:01:00 ...
3 2017-01-14 11:02:00 ...
4 2017-01-14 11:03:00 ...
5 2017-01-14 11:11:00 ...
6 2017-01-14 11:11:30 ...
7 2017-01-14 11:15:00 ...
8 2017-01-14 11:15:01 ...
9 2017-01-14 11:18:00 ...
I need to have this kind of result:
start end occurrences
-----------------------------------------------------------
2017-01-14 11:00 2017-01-14 11:05 4
2017-01-14 11:05 2017-01-14 11:10 0
2017-01-14 11:10 2017-01-14 11:15 3
2017-01-14 11:15 2017-01-14 11:20 2
...
In specific I need a query that extracts all the occurrences of raws in MY_TABLE in 5 mins range (range value is variable).
Someone could help me?
Best regards,
You need to generate the timeframes you want and then left join. Here is one method:
select v.dt, dateadd(minute, 5, v.dt) as end_dt, count(t.id)
from (values (convert(datetime, '2017-01-14 11:00')),
(convert(datetime, '2017-01-14 11:05')),
. . .
) v(dt) left join
my_table t
on t.dateval >= v.dt and
t.dateval < dateadd(minute, 5, v.dt)
group by v.dt;
Note: If you want to do this for a wider range of time, then using a tally table or recursive CTE is handy.
Let's have a row generator, that generates dates every rangeSize from a startDate:
DECLARE #rangeSize INT = 5;
DECLARE #startDate DATETIME = '2020-01-01 00:00';
WITH RG(D,D2) AS (
SELECT #startDate AS D, DATEADD(MINUTE, #rangeSize, #startDate) AS D2
UNION ALL
SELECT DATEADD(MINUTE, #rangeSize, D), DATEADD(MINUTE, #rangeSize, D2)
FROM RG a
WHERE D < DATEADD(MINUTE, #rangeSize * 100, #startDate)
)
SELECT D,D2
FROM RG
OPTION (MAXRECURSION 100);
Now let's hook it up to your data and count the data:
DECLARE #rangeSize INT = 5;
DECLARE #startDate DATETIME = '2020-01-01 00:00';
WITH RG(D,D2) AS (
SELECT #startDate AS D, DATEADD(MINUTE, #rangeSize, #startDate) AS D2
UNION ALL
SELECT DATEADD(MINUTE, #rangeSize, D), DATEADD(MINUTE, #rangeSize, D2)
FROM RG a
WHERE D < DATEADD(MINUTE, #rangeSize * 100, #startDate)
)
SELECT r.D as StartDate, r.D2 as EndDate, COUNT(m.ID) as Count as EndDate
FROM
RG r
LEFT JOIN
my_table m ON m.dateval > r.D AND m.dateval <= r.D2
GROUP BY r.D, r.D2
OPTION (MAXRECURSION 100);
Note I used > and <= for the range because you seem to classify from e.g 15:01 to 20:00 as a range, whereas it feels more natural to me to have 15:00 to 19:59 as "belonging to the 15-20 range"
If you don't need your start and end to reflect the exact range:
You can use something like DATEDIFF(minute, '2000-01-01', DateVal)/5 as your grouping, then use MIN(DateVal) and MAX(DateVal) for your start and end; but those values will be of the first and last transaction in the interval, not the bounds of the interval.
Alternatively, you can use a recursive CTE to generate the intervals, and then join that to your data:
; WITH intervals AS (
SELECT CAST('2017-11-01 00:00:00' AS DATETIME) AS `start`
, CAST ('2017-11-01 00:05:00' AS DATETIME) AS `end`
UNION ALL
SELECT DATEADD(minute, 5, `start`) AS, DATEADD(minute, 5, `end`) AS end
FROM intervals
WHERE intervals.end < '2017-11-02 00:00:00'
)
SELECT i.`start`, i.`end`, COUNT(t.ID) AS occurrences
FROM intervals AS i
INNER JOIN MY_TABLE AS t ON t.DateVal >= i.`start` AND t.DateVal < i.End
GROUP BY i.`start`, i.`end`
ORDER BY i.`start`, i.`end`
;
Notes:
the literal date values can be adjusted to reflect the actual range you want to query
If you want intervals without activity to be included, the INNER can be changed to LEFT
Since your question as stated has overlapping intervals, I went with the assumption that a DateVal on the 5 minute mark belongs to the interval that starts with that value.

How to assign shift based on punch time

Based on punch time shift automatically assigned to employee
Table Trnevents:
emp_reader_id EVENTID DT
3 1 2019-07-14 17:00:00.000
3 0 2019-07-14 10:00:00.000
3 1 2019-07-13 17:50:00.000
3 0 2019-07-13 10:05:00.000
3 1 2019-07-12 16:00:00.000
3 0 2019-07-12 08:55:00.000
declare
#start_date date='2019-07-12'
,#end_date date ='2019-07-14'
;WITH ByDays AS
( -- Number the entry register in each day
SELECT
emp_reader_id,
dt AS T,
CONVERT(VARCHAR(10),dt,102) AS Day,
FLOOR(CONVERT(FLOAT,dt)) DayNumber,
ROW_NUMBER() OVER(PARTITION BY FLOOR(CONVERT(FLOAT,dt)) ORDER BY dt) InDay
FROM trnevents
where
(
CONVERT(VARCHAR(26), dt, 23) >= CONVERT(VARCHAR(26), #start_date, 23)
and CONVERT(VARCHAR(26), dt, 23) <=CONVERT(VARCHAR(26), #end_date, 23)
)
)
,Diffs AS
(
SELECT
E.Day,
E.emp_Reader_id,
E.T ET,
O.T OT,
O.T-E.T Diff,
DATEDIFF(S,E.T,O.T) DiffSeconds -- difference in seconds
FROM
(
SELECT
BE.emp_Reader_id,
BE.T,
BE.Day,
BE.InDay
FROM ByDays BE
WHERE BE.InDay % 2 = 1
) E -- Even rows
INNER JOIN
(
SELECT
BO.emp_reader_id,
BO.T,
BO.Day,
BO.InDay
FROM ByDays BO
WHERE BO.InDay % 2 = 0
) O -- Odd rows
ON E.InDay + 1 = O.InDay -- Join rows (1,2), (3,4) and so on
AND E.Day = O.Day -- in the same day
)
SELECT * FROM Diffs
DECLARE #start TIME(0) = '9:00 AM', #end TIME(0) = '18:00 PM';
WITH x(n) AS
(
SELECT TOP (DATEDIFF(HOUR, #start, #end) + 1)
rn = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns
ORDER BY [object_id]
)
SELECT
t = DATEADD(HOUR, n-1, #start)
,cast(DATEADD(HOUR, n-1, #start) as varchar(50))+' shift'
FROM x
ORDER BY t;
If employee punch in time between 8.30 to 9.30 am , it assigned to 9.00 shift
if 9.30 to 10.30. it assigned to 10.00 shift
Expected output:
Day emp_Reader_id ET OT Diff DiffSeconds Shift
2019.07.12 3 2019-07-12 08:55:00.000 2019-07-12 16:00:00.000 1900-01-01 07:05:00.000 25500 09:00:00 shift
2019.07.13 3 2019-07-13 10:05:00.000 2019-07-13 17:50:00.000 1900-01-01 07:45:00.000 27900 10:00:00 shift
2019.07.14 3 2019-07-14 12:00:00.000 2019-07-14 21:00:00.000 1900-01-01 07:00:00.000 25200 12:00:00 shift
Two solutions, one with LEAD.
First is without LEAD:
select
CAST(t1.DT as date) AS "Day",
t1.emp_reader_id AS emp_Reader_id,
t1.DT AS ET,
t2.DT AS OT,
t1.DT - t2.DT As Diff,
DATEDIFF(s, t1.DT, t2.DT) As DiffSeconds,
cast(dateadd(HOUR,datepart(HH,t1.DT)+ round(datepart(MINUTE,t1.dt)/60.0,0),0) as time) as Shift
from trnevents t1
inner join trnevents t2 on t2.emp_reader_id=t1.emp_reader_id and t2.EVENTID=1 and CAST(t2.DT as date)= CAST(t1.DT as date)
where t1.eventID=0
order by t1.DT
or:
SELECT
Day,
emp_reader_id,
ET,
OT,
ET-OT AS Diff ,
DATEDIFF(s,ET,OT) as DiffSeconds,
cast(dateadd(HOUR,datepart(HH,ET)+ round(datepart(MINUTE,ET)/60.0,0),0) as time) as Shift
FROM (
select
CAST(t1.DT as date) AS "Day",
t1.emp_reader_id AS emp_Reader_id,
t1.DT AS ET,
LEAD(t1.DT) over (order by emp_reader_id,dt) AS OT,
eventid,
--t1.DT - t2.DT As Diff,
--DATEDIFF(s, t1.DT, t2.DT) As DiffSeconds,
cast(dateadd(HOUR,datepart(HH,t1.DT)+ round(datepart(MINUTE,t1.dt)/60.0,0),0) as time) as Shift
from trnevents t1) x
where x.EVENTID=0
Both query produce same result (second one is probably quicker)
If employee punch in time between 8.30 to 9.30 am , it assigned to 9.00 shift if 9.30 to 10.30. it assigned to 10.00 shift
If I understand this correctly, you can use a case expression:
select e.*,
(case when dt >= '08:30:00' and dt < '09:30:00'
then 'Shift 09:00'
when dt >= '09:30:00' and dt < '10:30:00'
then 'Shift 10:00'
end) as shift
from Trnevents e
If you want a more general solution where the breaks are at 30 minute intervals throughout the day, then subtract 30 minutes and extract the hour:
select e.*,
datepart(hour, dateadd(minute, -30, dt)) as shift
from e;

Using Case Statements with a Cross Apply and Select Top

In the below code, I want the first line of the Select Top to look like this if the hour of the enddate is equal to 0.
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + 1))
But I want it to look like this if the hour of the enddate is not equal to 0.
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime)))
But, I'm struggling to determine how you incorporate a case statement into this, since it would need to sit within the cross apply.
--Create my temporary table
SELECT * into #Temporary
FROM dbo.Census
WHERE year(startdatetime) >= 2018
ORDER BY startdatetime, pt_id
--Use the Cross Apply to split out every day into every hour
SELECT
Date = CAST(D AS DATE)
,Hour = DATEPART(HOUR, D)
,A.pt_id
,cendate
,A.[locationid]
,A.[room-bed]
,startdatetime
,enddatetime
,minutes
,MyStartMinutes = 0
,MyEndMinutes = 0
INTO #Temporary2
FROM #Temporary A
CROSS APPLY
(
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + 1))
D = DATEADD(HOUR, -1 + ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )), A.startdatetime)
FROM master..spt_values n1
,master..spt_values n2
) B
Sample Data
Date pt_id cendate loc startdate enddate minutes
10/9/2018 100 10/2/2018 A 10/1/2018 22:00 10/2/2018 0:35 155
10/10/2018 100 10/2/2018 B 10/2/2018 0:35 10/2/2018 23:00 1345
It should result in:
Date Hour loc pt_id (Start Time) (End Time)
10/9/2018 22 A 100 10/9/2018 22:00 10/9/2018 23:00
10/9/2018 23 A 100 10/9/2018 23:00 10/10/2018 0:00
10/10/2018 0 A 100 10/10/2018 0:00 10/10/2018 0:35
10/10/2018 0 B 100 10/10/2018 0:35 10/10/2018 1:00
Replace this:
SELECT TOP ( ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + 1))
With this:
SELECT TOP (ABS(DATEDIFF(HOUR, A.startdatetime, A.enddatetime) + (CASE WHEN DATEPART(HOUR, A.enddatetime) = 0 THEN 1 ELSE 0 END)))

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