First and Last Time of Day - sql-server-2005

I need to run a query to sort out records for the first time an event occurs during the day, and the last time an event happens during the day, and run the report to include a week of recorded history on the system. This is in a SQL2005 database, but I haven't found anything to help me narrow things down to just a first occurrance and a last occurance.

-- Test data in table #T
declare #T table(id int, dt datetime)
insert into #T values (1, '2011-01-01T10:00:00')
insert into #T values (2, '2011-01-01T11:00:00')
insert into #T values (3, '2011-01-01T12:00:00')
insert into #T values (4, '2011-01-02T20:00:00')
insert into #T values (5, '2011-01-02T21:00:00')
insert into #T values (6, '2011-01-02T22:00:00')
-- First day of interval to query
declare #FromDate datetime = '2011-01-01'
-- Add 7 days to get #ToDate
declare #ToDate datetime = dateadd(d, 7, #FromDate)
;with cte as
(
select *,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt) as rnMin,
row_number() over(partition by datediff(d, T.dt, 0) order by T.dt desc) as rnMax
from #T as T
where T.dt >= #FromDate and T.dt < #ToDate
)
select C.id, C.dt
from cte as C
where C.rnMax = 1 or C.rnMin = 1
order by C.dt

Related

How to get one output table from select in while-loop

Edited to live up to the rules here. Sorry about my first attempt.
I got the following sample data:
CREATE TABLE SampleData(
[Time] [time](7) NOT NULL
) ON [PRIMARY]
GO
INSERT INTO SampleData([Time]) VALUES ('01:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('02:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('03:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
INSERT INTO SampleData([Time]) VALUES ('04:00:00')
GO
This is my query:
DECLARE #Counter INT
SET #Counter = 1
WHILE (#Counter <= 4)
BEGIN
SELECT Count([Time]) AS OrdersAmount
FROM SampleData
WHERE DATEPART(HOUR, [Time]) = #Counter
SET #Counter = #Counter + 1
END
This is the result of the query:
OrdersAmount
1
-----
OrdersAmount
2
-----
OrdersAmount
3
-----
OrdersAmount
4
So 4 seperate tables. What I need is one table, with alle values in it, on each their own row, like this:
OrdersAmount
1
2
3
4
I tried with cte and declaring a temp table, but I just can't make it work.
I don't have the data so can't test.
But if I get your problem right, this should work for you.
select PromisedPickupDt = cast(PromisedPickupDate as date),
[Hour] = datepart(hour, PromisedPickupDate),
HourlyAmount = sum(OrdersAmount)
from [FLX].[dbo].[NDA_SAP_Bestillinger]
where cast(PromisedPickupDate as date) = cast(getdate() as date)
group by cast(PromisedPickupDate as date), datepart(hour, PromisedPickupDate)
You need an Hours table to join and group against. You can create this using a VALUES constructor:
SELECT
Count(*) AS OrdersAmount
FROM (VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23)
) AS v(Hour)
LEFT JOIN [FLX].[dbo].[NDA_SAP_Bestillinger]
ON v.Hour = DATEPART(hour, PromisedPickupTime)
AND PromisedPickupDate >= CAST(CAST(GETDATE() AS date) AS datetime)
AND PromisedPickupDate < CAST(DATEADD(day, 1, CAST(GETDATE() AS date)) AS datetime)
GROUP BY v.Hour;
What is going on here is that we start with a filter by the current date.
Then we join a constructed Hours table against the hour-part of the date. Because it is on the left-side of a LEFT JOIN, we now have all the hours whether or not there is any value in that hour. We can now group by it.
Note the correct method to compare to a date range: a half-open interval >= AND <
Do not use YEAR\MONTH etc in join and filter predicates, as indexes cannot be used for this.

SQL - Total Time on day

My table looks a lot like the table shown in the following StackOverflow URL:
Calculating total time excluding overlapped time & breaks in SQLServer
My table also includes an OwnerID. Each person has an unique OwnerID, and I could easily join in the person name belonging to that ID.
The result requested should be just like in the linked URL, but per Owner. I tried modifying the selected answer for his URL but that gives me the following error:
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
This is the query I try to run...
;WITH addNR AS ( -- Add row numbers
SELECT StartDate, EndDate, ROW_NUMBER() OVER (ORDER BY StartDate, EndDate) AS RowID
FROM dbo.FollowUp AS T
WHERE StartDate > '2017-10-02 08:30:00.000'
), createNewTable AS ( -- Recreate table according overlap time
SELECT StartDate, EndDate, RowID
FROM addNR
WHERE RowID = 1
UNION ALL
SELECT
CASE
WHEN a.StartDate <= AN.StartDate AND AN.StartDate <= a.EndDate THEN a.StartDate
ELSE AN.StartDate END AS StartTime,
CASE WHEN a.StartDate <= AN.EndDate AND AN.EndDate <= a.EndDate THEN a.EndDate
ELSE AN.EndDate END AS EndTime,
AN.RowID
FROM addNR AS AN
INNER JOIN createNewTable AS a
ON a.RowID + 1 = AN.RowID
), getMinutes AS ( -- Get difference in minutes
SELECT DATEDIFF(MINUTE,StartDate,MAX(EndDate)) AS diffMinutes
FROM createNewTable
GROUP BY StartDate
)
SELECT SUM(diffMinutes) AS Result
FROM getMinutes
Where I replaced StartTime=StartDate and EndTime=EndDate since my columns are named so..
Sample Data
Coincidence #vitalygolub .
Try my script with various sample data.Also Time Calendar table should be permanent table so it is only time creation.
It is not Recursive so it should perform better.If output is thrown then distinct can be avoided.
create table #tbl (ownerid int,StartTime datetime,enddate datetime);
insert into #tbl values
(1,'2014-10-01 10:30:00.000','2014-10-01 12:00:00.000') -- 90 mins
,(1,'2014-10-01 10:40:00.000','2014-10-01 12:00:00.000') -- 0 since its overlapped with previous
,(1,'2014-10-01 10:42:00.000','2014-10-01 12:20:00.000') -- 20 mins excluding overlapped time
,(1,'2014-10-01 10:40:00.000','2014-10-01 13:00:00.000') -- 40 mins
,(1,'2014-10-01 10:44:00.000','2014-10-01 12:21:00.000') -- 0 previous ones have already covered this time range
,(1,'2014-10-13 15:50:00.000','2014-10-13 16:00:00.000') -- 10 mins
create table #Timetable(timecol time primary key )
insert into #Timetable
select dateadd(minute,(c.rn-1),'00:00')
from(
select top (24*60) row_number()over(order by number)rn from
master..spt_values order by number)c
SELECT c.ownerid
,cast(c.StartTime AS DATE)
,count(DISTINCT timecol) TimeMin
FROM #Timetable t
CROSS APPLY (
SELECT *
FROM #tbl c
WHERE timecol >= cast(c.StartTime AS TIME)
AND timecol < cast(c.enddate AS TIME)
) c
GROUP BY c.ownerid
,cast(c.StartTime AS DATE)
drop table #Timetable
drop table #tbl
Ok, here is working code, am not sure about performance. The idea: create "calendar" with 1 minute precision, fill it for every OwnerId and calculate number of records
DECLARE #table TABLE (OwnerId int,StartTime DateTime2, EndTime DateTime2)
INSERT INTO #table SELECT 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000'
INSERT INTO #table SELECT 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000'
INSERT INTO #table SELECT 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000'
----------------------------------------------------------------------------
INSERT INTO #table SELECT 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000'
INSERT INTO #table SELECT 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000'
declare #period int, #start datetime;;
select #period=datediff(mi, MIN(starttime),MAX(endtime)),#start =MIN(StartTime) from #table;
declare #seconds table(num int identity(0,1),garbage bit not null);
insert into #seconds(garbage) values(0);
while( select COUNT(*) from #seconds) < #period
insert into #seconds(garbage ) select garbage from #seconds;
with a(ownerId, usedminute ) as
(
select distinct t.ownerID,s.num from #seconds s join #table t on
dateadd(mi,s.num, #start) between t.StartTime and dateadd(s,-1,t.EndTime)
)
select ownerId, count(*) time_in_minutes from a group by ownerID;
You can do this without while loops using a derived tally table and regular set based joins, which as a result will perform very efficiently:
-- Define test data
declare #table table (ownerid int,starttime datetime2, endtime datetime2);
insert into #table select 1,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 1,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
insert into #table select 1,'2014-10-01 10:40:00.000', '2014-10-01 13:00:00.000';
insert into #table select 1,'2014-10-01 10:44:00.000', '2014-10-01 12:21:00.000';
insert into #table select 1,'2014-10-13 15:50:00.000', '2014-10-13 16:00:00.000';
----------------------------------------------------------------------------
insert into #table select 2,'2014-10-01 10:30:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:40:00.000', '2014-10-01 12:00:00.000';
insert into #table select 2,'2014-10-01 10:42:00.000', '2014-10-01 12:20:00.000';
-- Query
declare #MinStartTime datetime;
declare #Minutes int;
-- Define data boundaries
select #MinStartTime = min(starttime)
,#Minutes = datediff(minute,min(starttime), max(endtime))+1
from #table;
-- Initial Numbers Table - 10 rows
with t(t) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
-- Create tally table of minutes by cross joining numbers table many times to generate 1m rows
,n(n) as (select top(#Minutes) dateadd(minute,row_number() over (order by (select null))-1,#MinStartTime) from t t1, t t2, t t3, t t4, t t5, t t6)
-- Define largest possible range for each OwnerID
,o(i,s,e) as (select ownerid, min(starttime), max(endtime) from #table group by ownerid)
select o.i as OwnerID
,cast(n.n as date) as DateValue
,count(n.n) as TotalMinutes
from o
join n -- Return minutes for each OwnerID range,
on n.n between o.s and o.e
where exists(select null -- where that minute should be included.
from #table as t
where n.n >= t.starttime
and n.n < t.endtime)
group by o.i
,cast(n.n as date)
order by o.i
,DateValue
Output:
+---------+------------+--------------+
| OwnerID | DateValue | TotalMinutes |
+---------+------------+--------------+
| 1 | 2014-10-01 | 150 |
| 1 | 2014-10-13 | 10 |
| 2 | 2014-10-01 | 111 |
+---------+------------+--------------+

Find maximum and minimum days between several dates in SQL

I want to find max. and min. days between several records in my table. For example, in the following table I would like to have max. and min. days due to DATE field for each ID.
I'm using MS-SQL 2013
I know that there is dateiff to finding days between two dates but now, I want to find maximum and minimum days between several dates.
ID DATE
10 2016/01/13
10 2016/01/10
10 2016/11/01
10 2015/12/28
11 2015/12/11
11 2016/02/01
11 2015/01/01
Now, how can I find max. and min. days between DATEs for each ID?
Can you please help me to have the query in SQL?
This solution is a bit ugly (using two subqueries) but should get you started:
CREATE TABLE #DataTable (id INT, [date] DATETIME)
INSERT INTO #DataTable (id, [date])
VALUES (10, '20160113')
,(10, '20160110')
,(10, '20161101')
,(10, '20151211')
,(11, '20151211')
,(11, '20160201')
,(11, '20150101')
SELECT
id
, MIN([days]) AS mindays
, MAX([days]) AS maxdays
FROM (
SELECT
id
, DATEDIFF(DAY, [date], (SELECT MIN([date]) FROM #DataTable AS D1 WHERE D1.id = #DataTable.id AND D1.[date] > #DataTable.[date])) AS [days]
FROM #DataTable
) AS t
GROUP BY id
ORDER BY id
Easiest way to understand is by starting at the middle query (which can be run on its own). It delivers for each row the id, and the number of days between the date in the row and the next higher one from the same id.
The outer query is then a simple MIN MAX GROUP BY.
Ok, I've re-read your answer, are you looking for something like below;
Creating temp table and inserting data;
CREATE TABLE #DataTable (ID int, DATE DateTime)
INSERT INTO #DataTable (ID, DATE)
VALUES
(10, '2016-01-13')
,(10, '2016-01-10')
,(10, '2016-11-01')
,(10, '2015-12-11')
,(11, '2015-12-11')
,(11, '2016-02-01')
,(11, '2015-01-01')
Select statement to retrieve data;
DECLARE #StartDate DateTime; SET #StartDate = '2015-12-01'
DECLARE #EndDate DateTime; SET #EndDate = '2016-12-01'
SELECT
a.ID
,MIN(DATE) FirstDate
,MAX(DATE) LastDate
,DATEDIFF(day, MIN(DATE), MAX(DATE)) DayDiff
FROM #DataTable a
WHERE a.DATE BETWEEN #StartDate AND #EndDate
GROUP BY a.ID
You can remove the fields FirstDate and LastDate, this is just to show the dates that are being compared. You'll return a 0 value if there is only one date between your variable dates so you may have to account for this. Also probably a good idea to put something to check for a NULL.
Have you tried this:
Select ID, Min(Date), Max(Date) From MyTable Group By ID

How can I check to see if a data set has a grouping of 3 entries within 5 days

I am looking to see if from a specific date there are a total of 3 entries within a grouping of 5 days within +/- 5 days of the specified date. As long as the second column is false.
Here is an example data set.
Create Table #Data(entryDate date, complete bit)
Insert into #Data Values('2013-02-05', 1)
Insert into #Data Values('2013-02-06', 0)
Insert into #Data Values('2013-02-09', 0)
Insert into #Data Values('2013-02-11', 0)
Insert into #Data Values('2013-02-12', 0)
Insert into #Data Values('2013-02-14', 0)
Given a date of 2013-02-11 I want a true result given that there are 2 scenarios that satisfy my conditions.
2013-02-09, 2013-02-11, 2013-02-12
2013-02-11, 2013-02-12, 2013-02-14
Or given a date of 2013-02-09 I would get a true result but there is only 1 scenario that satisfies the conditions
2013-02-09, 2013-02-11, 2013-02-12
Note that 2013-02-05, 2013-02-06, 2013-02-09 do not satisfy the conditions due to the fact that 2013-02-05 is set to true.
How can I write a sql expression that gives me true or false as described above.
use Outer Apply and then evaluate your result.
declare #data table(entryDate date, complete bit)
Insert into #data Values('2013-02-05', 1)
Insert into #data Values('2013-02-06', 0)
Insert into #data Values('2013-02-09', 0)
Insert into #data Values('2013-02-11', 0)
Insert into #data Values('2013-02-12', 0)
Insert into #data Values('2013-02-14', 0)
select *
from #data d1
outer apply (
SELECT Count(*) AS CountMatches
FROM #data d2
where
ABS(DateDiff(d,d1.entryDate,d2.entryDate)) < 5 AND
complete = 0 AND
d1.entryDate <> d2.entryDate
) t
where
t.CountMatches >=3
I've also come up with the following which ensures that no dates after a completion are counted towards the 3 of 5 groupings
Declare #data table(Id int, entryDate date, complete bit)
Insert into #data Values(1, '2013-02-05', 1)
Insert into #data Values(2, '2013-02-06', 0)
Insert into #data Values(3, '2013-02-09', 0)
Insert into #data Values(4, '2013-02-11', 0)
Insert into #data Values(5, '2013-02-12', 0)
Insert into #data Values(6, '2013-02-14', 0)
Declare #windowMiddle date
Set #windowMiddle = '2013-02-09'
Declare #newestComplete date
select top 1 #newestComplete = entryDate from #data where complete =1 order by entryDate desc
--if(#newestComplete > #windowMiddle) --exit
;with cte as
(
select *
from #data cte
where
cte.entryDate > DATEADD(dd, -5, #windowMiddle)
and cte.entryDate >#newestComplete
and cte.entryDate < DATEADD(dd, 5, #windowMiddle)
)
select cte.entryDate, count(*)
from cte inner join cte d on cte.entryDate between DATEADD(dd, -4, d.entryDate) and d.entryDate
group by cte.entryDate
having (count(*)>=3)
Perhaps someone can comment on whether BlackjacketMack's answer is better than this.

Retrieve value from the closest available date

I have a system which stores the history of a balance in a history table.
The table has the account number, sort code, balance, balance start date, balance end date.
When a balance is updated an entry is created in the history table which shows what the balance, what the date was when that balance first started and the date which shows when the balances changed. So for example the table will show a balance of $100.00 and this balance ran from 07/10/2013 to 07/15/2013.
What I'm trying to do is get the sum of the balances for all sort codes on a specific day however the balance may not have changed on this date so I would need to return the closest prior date but I'm failing.
This is what I tried so far.
declare #sdate datetime
set #sdate = '06/08/2012' --mm/dd/yyyy
SELECT CONVERT(varchar(20),MAX(StartDate),103) as "Closest Start Date", Sort, SUM(Balance) AS "Sum of balances"
FROM BalanceHistory
WHERE StartDate <= convert(smalldatetime ,#sdate) AND SortCode <> 'ABC123456'
GROUP BY SortCode
SELECT FROM BalanceHistory would produce something like
AccountNumber, SortCode, Balance, StartDate, EndDate, RECID
00000001, srt010203, 100.00, 06/01/2013, 06/02/2013, RECID
00000001, srt010203, 110.00, 06/02/2013, 06/03/2013, RECID
00000001, srt010203, 120.00, 06/03/2013, 06/04/2013, RECID
00000002, srt010204, 200.00, 06/01/2013, 06/02/2013, RECID
00000003, srt010204, 300.00, 06/01/2013, 06/02/2013, RECID
00000004, srt010205, 400.00, 06/01/2013, 06/02/2013, RECID
00000005, srt010205, 500.00, 06/01/2013, 06/02/2013, RECID
You can do this without a JOIN by using the ROW_NUMBER() function (assuming SQL Server 2005 or newer):
DECLARE #sdate DATE
SET #sdate = '2012-06-08'
SELECT SortCode, SUM(Balance)'Sum of Balances'
FROM (SELECT AccountNumber,SortCode, Balance,ROW_NUMBER() OVER (PARTITION BY AccountNumber ORDER BY StartDate DESC)'RowRank'
FROM BalanceHistory
WHERE StartDate <= #sdate AND SortCode <> 'ABC123456'
)sub
WHERE RowRank = 1
GROUP BY SortCode
Demo: SQL Fiddle
The ROW_NUMBER() function in the subquery assigns a 'RowRank' to the balance for each accountnumber, we order by StartDate DESC to get rank of '1' for the most recent balance for each accountnumber, the WHERE criteria limits it to most recent balance from the date you set in your variable. Then you use that rank in the outer query to limit only to that one balance.
This should work
Declare #table as table
(AccountNo int,
Balance money,
DateEntered datetime)
Declare #dt datetime
set #dt = '2013-07-01'
Insert into #table values(1, 100, '2013-04-01')
Insert into #table values(2, 200, '2013-04-01')
Insert into #table values(2, 300, '2013-05-01')
Insert into #table values(2, 400, '2013-06-01')
--select AccountNo, Max(Balance), Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo
Select Sum(t.Balance) From #table t
inner join (select AccountNo, Max(Balance) Balance, Max(DateEntered) MaxDateEntered From #table where DateEntered <= #dt group by AccountNo) tm
on t.AccountNo = tm.AccountNo and t.DateEntered = tm.MaxDateEntered
enter code here