Count number of occurrences before a certain value - sql-server

I am trying to count of how many times "Agent" rows appears with 2 or more "Threading" on top of it.
In the below example, the count is 2 as row 3 (Agent) has 2 threading on top and row 7 (Agent) has got 3 threading on top. The order is determined by the date column.
Any idea how to get the count?
Below is code I am using:
DECLARE #INFO AS TABLE
(
Process VARCHAR(10) ,
Content VARCHAR(100) ,
Date DATETIME
)
INSERT INTO #INFO
(
Process,
Content ,
Date
)
VALUES
('Threading', 'Content A', GETDATE() - 9), ('Threading', 'Content B', GETDATE() - 8), ('Agent', 'Content C', GETDATE() - 7), ('Threading', 'Content D', GETDATE() - 6), ('Threading', 'Content E', GETDATE() - 5), ('Threading', 'Content F', GETDATE() - 4), ('Agent', 'Content G', GETDATE() - 3), ('Threading', 'Content H', GETDATE() - 2), ('Agent', 'Content I', GETDATE() - 1)
SELECT * FROM #INFO AS i
-- Result it 2.

One method would be to use a windowed COUNT to put the rows into groups, based on how many times 'Agent' has appeared prior to the row. Then you can COUNT how many 'Threading's are in each group, and then finally COUNT how many of those groups have a value of 2 or more:
WITH Grps AS (
SELECT Process,
Date,
COUNT(CASE Process WHEN 'Agent' THEN 1 END) OVER (ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS Grp
FROM #INFO),
Counts AS(
SELECT COUNT(CASE Process WHEN 'Threading' THEN 1 END) AS Threadings,
Grp
FROM Grps
GROUP By Grp)
SELECT COUNT(*)
FROM Counts
WHERE Threadings >= 2;

Found a solution. I am using the LAG function to get the second last and last process per row.
SELECT * , LAG(i.Process, 1) OVER ( ORDER BY i.Date) lastProcess, LAG(i.Process, 2) OVER ( ORDER BY i.Date) secondlast
FROM #INFO AS i

Related

Find the date when a milestone was achieved

I have people that do many multi-day assignments (date x to date Y). I would like to find the date that they completed a milestone e.g. 50 days work completed.
Data is stored as a single row per Assignment
AssignmentId
StartDate
EndDate
I can sum up the total days they have completed up to a date, but am struggling to see how I would find out the date that a milestone was hit. e.g. How many people completed 50 days in October 2020 showing the date within the month that this occurred?
Thanks in advance
PS. Our database is SQL Server.
As mentioned by prwvious comments, it would be much easier to help you if you could provide example data and table structure in order help you answer this question.
However, guessing a simple DB structure with a table for your peolple, your tasks and the work each user completed, you can get the required sum of days by use of a date table (or cte) which contains a entry for each day and the window function SUM with UNBOUNDED PRECEDING. Following an example:
DECLARE #people TABLE(
id int
,name nvarchar(50)
)
DECLARE #tasks TABLE(
id int
,name nvarchar(50)
)
DECLARE #work TABLE(
people_id int
,task_id int
,task_StartDate date
,task_EndDate date
)
INSERT INTO #people VALUES (1, 'Peter'), (2, 'Paul'), (3, 'Mary');
INSERT INTO #tasks VALUES (1, 'Devleopment'), (2, 'QA'), (3, 'Sales');
INSERT INTO #work VALUES
(1, 1, '2019-04-05', '2019-04-08')
,(1, 1, '2019-05-05', '2019-06-08')
,(1, 1, '2019-07-05', '2019-09-08')
,(2, 2, '2019-04-08', '2019-06-08')
,(2, 2, '2019-09-08', '2019-10-03')
,(3, 1, '2019-11-01', '2019-12-01')
;WITH cte AS(
SELECT CAST('2019-01-01' AS DATE) AS dateday
UNION ALL
SELECT DATEADD(d, 1, dateday)
FROM cte
WHERE DATEADD(d, 1, dateday) < '2020-01-01'
),
cteWorkDays AS(
SELECT people_id, task_id, dateday, 1 AS cnt
FROM #work w
INNER JOIN cte c ON c.dateday BETWEEN w.task_StartDate AND w.task_EndDate
),
ctePeopleWorkdays AS(
SELECT *, SUM(cnt) OVER (PARTITION BY people_id ORDER BY dateday ROWS UNBOUNDED PRECEDING) dayCnt
FROM cteWorkDays
)
SELECT *
FROM ctePeopleWorkdays
WHERE dayCnt = 50
OPTION (MAXRECURSION 0)
The solution depends on how you store your data. The solution below assumes that each worked day exists as a single row in your data model.
The approach below uses a common table expression (cte) to generate a running total (Total) for each person (PersonId) and then filters on the milestone target (I set it to 5 to reduce the sample data size) and target month.
Sample data
create table WorkedDays
(
PersonId int,
TaskDate date
);
insert into WorkedDays (PersonId, TaskDate) values
(100, '2020-09-01'),
(100, '2020-09-02'),
(100, '2020-09-03'),
(100, '2020-09-04'),
(100, '2020-09-05'), -- person 100 worked 5 days by 2020-09-05 = milestone (in september)
(200, '2020-09-29'),
(200, '2020-09-30'),
(200, '2020-10-01'),
(200, '2020-10-02'),
(200, '2020-10-03'), -- person 200 worked 5 days by 2020-10-03 = milestone (in october)
(200, '2020-10-04'),
(200, '2020-10-05'),
(200, '2020-10-06'),
(300, '2020-10-10'),
(300, '2020-10-11'),
(300, '2020-10-12'),
(300, '2020-10-13'),
(300, '2020-10-14'), -- person 300 worked 5 days by 2020-10-14 = milestone (in october)
(300, '2020-10-15'),
(400, '2020-10-20'),
(400, '2020-10-21'); -- person 400 did not reach the milestone yet
Solution
with cte as
(
select wd.PersonId,
wd.TaskDate,
count(1) over(partition by wd.PersonId
order by wd.TaskDate
rows between unbounded preceding and current row) as Total
from WorkedDays wd
)
select cte.PersonId,
cte.TaskDate as MileStoneDate
from cte
where cte.Total = 5 -- milestone reached
and year(cte.TaskDate) = 2020
and month(cte.TaskDate) = 10; -- in october
Result
PersonId MilestoneDate
-------- -------------
200 2020-10-03
300 2020-10-14
Fiddle (also shows the common table expression output).

Time Duration for staff on a night shift with onguard system accesscontrol database

I am using the Lenel Onguard with SQL server dBase to make the time attendance system for our employees.
I summarize each day's transaction to make their first entry and last exit for each day and get the datediff. to get the time duration. But the problem is with night shift employees, it is showing the time out which happened in the morning of the same day where actual exit is on the next day. so the datediff. returning with wrong value. Any solutions are most welcomed!
This code gives me the wrong value for night shift.can anyone help me out to modify the code to fit staff on day shift and those on night shift in which their exit time is on next day
SELECT DISTINCT
BADGE.ID,
UPPER(ISNULL(dbo.EMP.FIRSTNAME, ' ') + ' ' + ISNULL(dbo.EMP.LASTNAME, ' ') + ' ' + ISNULL(dbo.EMP.MIDNAME, ' '))AS NAMES,
A.*,
B.TIMEOUT,
datediff(hour,a.[TIMEIN],b.TIMEOUT) HoursWorked
FROM (
SELECT empid,convert(date,event_time_utc)[Date],ltrim(right(convert(varchar(25), DATEADD(HOUR,3,CAST(min(event_time_utc)AS TIME)), 100), 7)) TIMEIN
FROM events INNER JOIN READER ON EVENTS.DEVID=READER.READERID INNER JOIN EVENT ON EVENTS.EVENTTYPE=EVENT.EVTYPEID AND EVENTS.EVENTID=EVENT.EVID
WHERE READERID=19 AND PANELID=16 AND EVDESCR='Access Granted'
GROUP BY empid,convert(date,event_time_utc)
) A
JOIN
(
SELECT empid,convert(date,event_time_utc)[Date],ltrim(right(convert(varchar(25), DATEADD(HOUR,3,CAST(MAX(event_time_utc)AS TIME)), 100), 7)) TIMEOUT
FROM events INNER JOIN READER ON EVENTS.DEVID=READER.READERID INNER JOIN EVENT ON EVENTS.EVENTTYPE=EVENT.EVTYPEID AND EVENTS.EVENTID=EVENT.EVID
WHERE READERID=20 AND PANELID=16 AND EVDESCR='Access Granted'
GROUP BY empid,convert(date,event_time_utc)
) B on A.empid=b.empid and a.[Date]=b.[Date]
JOIN Emp on emp.id=A.EmpID
JOIN BADGE ON BADGE.EMPID=A.EMPID
ORDER BY DATE
Results
EmpID TIMEIN Timeout
1 2014-08-21 21:38:06.000 2014-08-22 06:00:10.000
2 2014-08-22 22:30:00.000 2014-08-23 06:00:10.000
In summary you have Event/s table, that have EmpId, the datetime of each scan, and there is no info about TimeOut or TimeIn, and you want to find that, well from the information you provided its a bit hard, Im sure you have more data that will help make this simpler, like the shift hours, any limitations, is over hours accepted and more boundary information.
Lets say you dont, so we will need to put some assumptions, for example, as the employees work in shifts, I will assume that the shift is 8-10 hours, and if I see a gab in scanning more than 14 hours that the employee went home, I will not care if he is a night shift employee or a day shift, if there is a gab between two sequentialscans more than 14 hours that mean he went home, or in other words that is a TimeOut, and then next entry is a TimeIn.
You did not provide any table structure or data, so I will ignore your query and focus on the problem you want to solve, so I will assume I have only one Events Table that have all the data, you can adapt this to your query if this helps you.
I will create a memory table here and fill it with EmpId 1, that is a on the day shift and EmpId 2 that is on the night shift, I will assume some data and do some calculations.
Declare #Events TABLE(
EmpId int,
event_time_utc datetime
)
insert into #Events values
(1, '2014-08-21 07:38:06.000'),--first day for emp1
(1, '2014-08-21 08:39:06.000'),
(1, '2014-08-21 14:44:06.000'),
(1, '2014-08-21 15:38:06.000'),
(1, '2014-08-21 16:01:06.000'),
(1, '2014-08-22 07:40:06.000'),--second day for emp1
(1, '2014-08-22 08:50:06.000'),
(1, '2014-08-22 14:30:06.000'),
(1, '2014-08-22 15:30:06.000'),
(1, '2014-08-22 16:05:06.000'),
(1, '2014-08-23 07:38:06.000'),--3rd day for emp1
(1, '2014-08-23 08:39:06.000'),
(1, '2014-08-23 14:44:06.000'),
(1, '2014-08-23 15:38:06.000'),
(1, '2014-08-23 16:01:06.000'),
(1, '2014-08-24 07:40:06.000'),--4th day for emp1
(1, '2014-08-24 08:50:06.000'),
(1, '2014-08-24 14:30:06.000'),
(1, '2014-08-24 15:30:06.000'),
(1, '2014-08-24 16:05:06.000'),
(2, '2014-08-21 21:38:06.000'),--first day for emp2 -- night shift
(2, '2014-08-21 23:38:06.000'),
(2, '2014-08-22 01:38:06.000'),
(2, '2014-08-22 04:05:06.000'),
(2, '2014-08-22 21:38:06.000'),--first day for emp2 -- night shift
(2, '2014-08-22 23:38:06.000'),
(2, '2014-08-23 01:38:06.000'),
(2, '2014-08-23 04:05:06.000'),
(2, '2014-08-23 21:38:06.000'),--3rd day for emp2 -- night shift
(2, '2014-08-23 23:38:06.000'),
(2, '2014-08-24 01:38:06.000'),
(2, '2014-08-24 04:05:06.000'),
(2, '2014-08-24 21:38:06.000'),--4th day for emp2 -- night shift
(2, '2014-08-24 23:38:06.000'),
(2, '2014-08-25 01:38:06.000'),
(2, '2014-08-25 04:05:06.000')
Now I will use the below CTEs to figure out the TimeIn and TimeOut and calculate the hours.
;with cte as (
--get the next entry, and set a row number based on EmpID and time
select *
,LEAD(event_time_utc,1) over (partition by EmpId order by event_time_utc) nextEntry
,ROW_NUMBER() over (partition by EmpId order by event_time_utc) seq
from #Events
),cte2 as (
--count the hours between this entry and the one after
select *,datediff(hour,event_time_utc,nextEntry) [hours] from cte
),cte3 as (
--if gab more then 14 or if its null, set it as time in
select *
,case when seq=1 then event_time_utc
when [hours]>14 then nextEntry
else null end [TimeIn]
from cte2
),cte4 as (
--find the seq for the Timeout
select *,
Isnull(
lead(seq) over (partition by EmpId order by event_time_utc)
,(select top(1) cte.seq from cte where cte.EmpId=cte3.EmpId order by event_time_utc desc)) [TimeOutSeq]
from cte3 where TimeIn is not null
),cte5 as (
--convert the seq to timeout by joining to the same table using the TimeOutSeq to help
select cte4.*,cte3.event_time_utc [TimeOut] from cte4
left outer join cte3 on cte3.seq=cte4.TimeOutSeq and cte3.EmpId=cte4.EmpId
)
--select * from cte3
--finally show the needed fileds only, and the hours for each employee
select EmpId,seq,TimeIn,[TimeOut],datediff(Hour,TimeIn,[TimeOut]) [hours] from cte5 order by EmpId, TimeIn
The result for my set of data is as below:-
EmpId Seq TimeIn TimeOut hours
1 1 2014-08-21 07:38:06.000 2014-08-21 16:01:06.000 9
1 5 2014-08-22 07:40:06.000 2014-08-22 16:05:06.000 9
1 10 2014-08-23 07:38:06.000 2014-08-23 16:01:06.000 9
1 15 2014-08-24 07:40:06.000 2014-08-24 16:05:06.000 9
2 1 2014-08-21 21:38:06.000 2014-08-22 04:05:06.000 7
2 4 2014-08-22 21:38:06.000 2014-08-23 04:05:06.000 7
2 8 2014-08-23 21:38:06.000 2014-08-24 04:05:06.000 7
2 12 2014-08-24 21:38:06.000 2014-08-25 04:05:06.000 7

Count(*) automatically rounds

I have a query where I am trying to determine what percentage of events happen on certain days and I'm getting nothing but zeroes back. I think (but am not sure) that something is causing my query to round. This is happening to me in SQL Server but not MySQL.
/* create the event table */
create table event (id int
, dayOf datetime
, description varchar(32)
);
/* add some events */
insert into event( id, dayOf, description ) values
( 1, '2018-01-01', 'Thing 1'),
( 2, '2018-01-01', 'Thing 2'),
( 3, '2018-01-02', 'Thing 3'),
( 4, '2018-01-02', 'Thing 4'),
( 5, '2018-01-03', 'Thing 5');
/* try to get % of events by day, but actually get zeroes */
select event_daily.dayOf, event_daily.cnt, event_total.cnt,
event_daily.cnt / event_total.cnt as pct_daily /* this is the zero */
from ( select dayOf, count(*) as cnt from event group by dayOf ) event_daily
, ( select count(*) as cnt from event ) event_total;
Anticipated result:
DateOf cnt cnt pct_daily
1/1/2018 2 5 0.40
1/2/2018 2 5 0.40
1/3/2018 1 5 0.20
Actual result:
DateOf cnt cnt pct_daily
1/1/2018 2 5 0
1/2/2018 2 5 0
1/3/2018 1 5 0
Any help would be much appreciated!
That is because SQL Server performs integer division, you can convert it into float first with CAST
select event_daily.dayOf, event_daily.cnt, event_total.cnt,
CAST(event_daily.cnt AS float) / CAST(event_total.cnt AS float) as pct_daily
from ( select dayOf, count(*) as cnt from event group by dayOf ) event_daily
, ( select count(*) as cnt from event ) event_total;
Try the below approach
declare #TotalCount DECIMAL(18, 2)
select #TotalCount = count(*) from #event
select
a.dayOf, a.DailyCount, a.TotalCount, CONVERT(DECIMAL(18, 2), (A.DailyCount/A.TotalCount)) AS pct_daily
FROM
(select
dayOf, Count(Id) AS DailyCount, #TotalCount as TotalCount
from
#event
group by
dayOf ) a

TSQL Accumulate rows every monday with distinct rows

I'm trying to get a query that returns customers I've attended per day, and i have this dataset:
fecha RecargadorPDV
2016/12/19 1
2016/12/19 2
2016/12/19 3
2016/12/20 1
2016/12/20 4
2016/12/20 5
2016/12/21 2
2016/12/21 6
2016/12/21 7
2016/12/21 8
..
...
2016/12/26 1
2016/12/26 2
2016/12/26 1
2016/12/27 2
2016/12/27 6
2016/12/27 7
2016/12/27 8
but the output I'd want to have is this:
date attended acum_customers
2016/12/19 3 3 -- Every monday it restart
2016/12/20 3 5
2016/12/21 4 8
.
..
2016/12/26 3 3 -- Every monday it restart
2016/12/27 4 3
.
..
As you can see, every monday it restart the values and if some customers are in a date and in the next day are present it needs to be ignored.
Here is a version that returns what you need for any arbitrary date. I have included sample data for a full week + two days to confirm its functionality.
DECLARE #t table (fecha date,
RecargadorPDV int
)
INSERT INTO #t VALUES
('2016/12/19', 1),
('2016/12/19', 2),
('2016/12/19', 3),
('2016/12/20', 1),
('2016/12/20', 4),
('2016/12/20', 5),
('2016/12/21', 2),
('2016/12/21', 6),
('2016/12/21', 7),
('2016/12/21', 8),
('20161222', 12),
('20161222', 1),
('20161222', 8),
('20161223', 11),
('20161223', 13),
('20161223', 15),
('20161223', 9),
('20161224', 1),
('20161225', 22),
('2016/12/26', 1),
('2016/12/26', 2),
('2016/12/26', 1),
('2016/12/27', 2),
('2016/12/27', 6),
('2016/12/27', 7),
('2016/12/27', 8)
;
With a as (
SELECT DISTINCT
fecha,
Dateadd(day, -(
case
when datepart(weekday, fecha) >=2
THEN datepart(weekday, fecha) - 2
ELSE 6
END), fecha) as LastMonday
FROM #t
)
SELECT
a.fecha as [date],
-- count(Distinct(CASE when t.fecha = a.fecha Then t.recargadorPDV else -1 END)) - 1 as attended,
SUM(CASE when t.fecha = a.fecha Then 1 else 0 END) as attended,
Count(distinct recargadorPDV) as acum_customers
FROM #t t
INNER JOIN a
ON t.fecha BETWEEN a.LastMonday and a.fecha
Group by a.fecha
ORDER BY a.fecha
Output of the above (as corrected) is:
date attended acum_customers
2016-12-19 3 3
2016-12-20 3 5
2016-12-21 4 8
2016-12-22 3 9
2016-12-23 4 13
2016-12-24 1 13
2016-12-25 1 14
2016-12-26 3 2
2016-12-27 4 5
I think your second week acum_customers is off based off the test data so check on that. I also assumed that the recargadorPDVcould only attend once per day since it's unique so I removed the one record noted below. With that... this should get you what you want. Let me know if it needs more explanation.
--change the ##DATEFIRST from 7 (english default) to 1 for the start of the week calculations
SET DATEFIRST 1;
--load some test data
declare #table table (fetcha datetime, recargadorPDV int)
insert into #table(fetcha, recargadorPDV)
values
('2016/12/19',1),
('2016/12/19',2),
('2016/12/19',3),
('2016/12/20',1),
('2016/12/20',4),
('2016/12/20',5),
('2016/12/21',2),
('2016/12/21',6),
('2016/12/21',7),
('2016/12/21',8),
--this is the break in the weeks
('2016/12/26',1),
('2016/12/26',2),
--('2016/12/26',1), -- removed this value since a unique ID shouldn't be allowed to attend twice for a single day
('2016/12/27',2),
('2016/12/27',6),
('2016/12/27',7),
('2016/12/27',8)
--temp table to hold some aggregated data
if object_id('tempdb..#tempT') is not null drop table #tempT
select
y.YR
,y.WK
,y.fetcha
,count(y.recargadorPDV) as attend
,sum(y.CTforWK) as accum
into #tempT
from(
select x.*
from
(select
fetcha
,recargadorPDV
,datepart(yy,fetcha) as YR
,datepart(wk,fetcha) as WK
--the case statment is my way of assigning 1 to each recargadorPDV ONLY once for each week so the running total is correct, ignoring duplicates as you stated
,case when count(recargadorPDV) over (partition by datepart(yy,fetcha), datepart(wk,fetcha), recargadorPDV order by fetcha) = 1 then 1 else 0 end as CTforWK
from #table) x) y
group by
y.YR
,y.WK
,y.fetcha
--see the inital results without the running total
select * from #tempT
--see the final results with the running total
select
a.fetcha
,a.attend
,sum(x.accum) as acum_customers
from #tempT a
inner join #tempT x on x.fetcha <= a.fetcha and x.YR = a.YR and x.WK = a.WK
group by a.fetcha, a.attend
order by a.fetcha
--change back the ##DATEFIRST setting
SET DATEFIRST 7;

Counting records in a subquery

I have a table with records Holding patrols of guards in SQL Server 2008R2.
Whenever a duty starts a new alert number is created and within this alert number there a patrols with a starting time.
Per 12 hours we can bill a flat rate when at least one patrol has been performed. When under the same alert number the 12 hour range is exceeded, a further flat rate has to be billed.
The calculation of the 12 hours starts with the time of the first patrol.
I tried with a temp table but could not solve it so far.
DECLARE #t1 TABLE (
AlertNo INT,
Starttime SMALLDATETIME,
Endtime SMALLDATETIME
)
INSERT INTO #t1 (AlertNo, Starttime, Endtime)
SELECT AlertNo,
Starttimepatrol,
DATEADD(HOUR, 12, Starttimepatrol)
FROM tblAllPatrols
WHERE PatrolNo = 1
SELECT AlertNo,
(
SELECT COUNT(*)
FROM [tblAllPatrols] a
INNER JOIN #t1 b ON b.AlertNo = a.AlertNo
WHERE a.Starttimepatrol BETWEEN b.Starttime AND b.Endtime
) AS patrols
FROM [vwAlleDatensaetze]
GROUP BY AlertNo
I know that this is not the end of the Story, but as I cannot even count the numbers of patrols I cannot find a way to solve the Problem.
It should somehow "group" the patrols over 12-hour ranges per alert number and then count how many groups exists under the same alert number.
Hope, someone of you can lead me to the result I Need.
Thanks your help
Michael
Try this, it assumes that after the first patrol the billing period is a multiple of 8 hours from this time:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
Query 1:
DECLARE #Patrols TABLE
(
AlertNo INT IDENTITY PRIMARY KEY,
StartTime DateTime
)
INSERT INTO #Patrols (StartTime)
VALUES ('20160126 09:57'),
('20160126 10:21'),
('20160126 19:54'),
('20160126 23:21'),
('20160127 08:13'),
('20160127 16:43'),
('20160128 07:33')
;WITH FirstBillingPeriodCTE
AS
(
SELECT MIN(StartTime) as BillingStartTime,
DateAdd(HOUR, 12, MIN(StartTime)) As BillingEndTime,
1 As BillingPeriod
FROM #Patrols
),
Numbers
As
(
SELECT num
FROM (Values (0),(1), (2), (3), (4), (5), (6), (7), (8), (9)) AS n(Num)
),
BillingPeriodsCTE
AS
(
SELECT DATEADD(Hour, 8 * (BillingPeriod + Numbers.Num), BillingStartTime) AS BillingStartTime,
DATEADD(Hour, 8 * (BillingPeriod + Numbers.Num), BillingEndTime) AS BillingEndTime,
BillingPeriod + Numbers.Num As BillingPeriod
FROM FirstBillingPeriodCTE
CROSS JOIN Numbers
)
SELECT COUNT(DISTINCT BillingPeriod)
FROM #Patrols P
INNER JOIN BillingPeriodsCTE B
ON P.StartTime >= B.BillingStartTime AND P.StartTime < B.BillingEndTime
Results:
| |
|---|
| 4 |
Here is a query that will give each billing period, up to 65,535 billing periods, accurate to the second.
My solution uses a calculated "Tally" table, but you would be better off in the long run to create your own physical "Tally" table in your database. See What is the best way to create and populate a numbers table? for more details.
You should be able to replace #tblPatrols with your patrol table.
DECLARE #tblPatrols TABLE (alertNo int, startTime datetime);
DECLARE #hoursPerBillingPeriod int, #toHoursConversion float;
SET #hoursPerBillingPeriod = 12;
SET #toHoursConversion = 60 * 60;
INSERT INTO #tblPatrols (alertNo, startTime)
VALUES
(1, '2016-01-28 05:57')
, (1, '2016-01-28 07:23')
, (1, '2016-01-28 08:10')
, (2, '2016-01-28 09:05')
, (2, '2016-01-28 12:22')
, (2, '2016-01-28 16:06')
, (2, '2016-01-28 23:45')
, (2, '2016-01-29 00:05')
, (3, '2016-01-28 12:00')
, (3, '2016-01-28 16:06')
, (3, '2016-01-29 00:00')
, (4, '2016-01-28 12:00')
, (4, '2016-01-28 16:06')
, (4, '2016-01-28 23:59:59.997')
;
;WITH
--......................
--This section used to simulate a "Tally" table... you would be better off to Create a physical Tally table
-- see: https://stackoverflow.com/questions/1393951/what-is-the-best-way-to-create-and-populate-a-numbers-table
Pass0 as (select 1 as C union all select 1) --2 rows
, Pass1 as (select 1 as C from Pass0 as A, Pass0 as B) --4 rows
, Pass2 as (select 1 as C from Pass1 as A, Pass1 as B) --16 rows
, Pass3 as (select 1 as C from Pass2 as A, Pass2 as B) --256 rows
, Pass4 as (select 1 as C from Pass3 as A, Pass3 as B)--65536 rows
, Tally as (select row_number() over(order by C) - 1 as N from Pass4) --65536 rows
--........................
,cteNumBillings as (
SELECT fp.alertNo
, firstPatrolTime = min(fp.startTime)
, lastPatrolTime = max(fp.startTime)
, hoursBetweenStartMinMax = datediff(second, min(fp.startTime), max(fp.startTime)) / #toHoursConversion
, numberOfBillingPeriods = floor(((datediff(second, min(fp.startTime), max(fp.startTime)) / #toHoursConversion) / #hoursPerBillingPeriod) + 1)
FROM #tblPatrols fp
GROUP BY fp.alertNo
)
SELECT b.alertNo
--This is the "x" value of the expression "Billing Period x of y"
, BillingPeriodNumber = t.N + 1
, BillingPeriodPatrolCount =
(select count(*)
from #tblPatrols p
where p.alertNo = b.alertNo
and p.startTime >= dateadd(hour, 12 * t.N, b.firstPatrolTime)
and p.startTime < dateadd(hour, 12 * (t.N+1), b.firstPatrolTime)
)
, BillingStart = dateadd(hour, 12 * t.N, b.firstPatrolTime)
, BillingEnd = dateadd(second, -1, dateadd(hour, 12 * (t.N + 1), b.firstPatrolTime))
--This is the "y" value of the expression "Billing Period x of y"
, TotalBillingPeriodCount = b.numberOfBillingPeriods
FROM cteNumBillings b
INNER JOIN Tally t ON t.N >= 0 and t.N < b.numberOfBillingPeriods
ORDER BY 1,2
;
I found a solution by myself, which seems to be easier and I could not find any mistake using it.
I take the first Startime of the first patrol in a variable. Then I use datediff for die difference of the all StartTimePatrol to the startime of the first patrol and divide it by 12 hours
set #BillingPeriod=(select (datediff(hour,#StartTime,#StartTimePatrol)/12)+1)
then I put the result of each record in a temp table
insert into #t2 ( Alertno, Starttime, Billings )
values ( #Alertno, #StartTimePatrol, #BillingPeriod )
then I group the altertno and Billings and count them
select alertno, count(Billings ) from (select alertno, Billings from #t2
group by alertno, Billings ) temp group by alertno
The result looks correct for me.
Thanks for all replies.
Michael

Resources