SQL JOIN WITH A BUFFER TIME - sql

I have tWO tables and have to check whether the time in a table lies in between range of other table.
ProgStartTime gives the start time of the range and ProgEndTime gives end of the range which lies in MBA table.
Am checking whether the AdvTime in MAP table exists in between the ProgStartTime and ProgEndTime from MBA table.
For time range less than one hour i have to give a buffer of + or - 5 mins.
i.e if ProgStarttTime is 18:00 & progEndTime is 19:00 & AdvTime is 17:55/19:05 it should match the range.
Whereas if ProgStarttTime is 18:00 & progEndTime is 20:00 & AdvTime is 17:55 it shouldnt match.
Sorry for the clumsy content am in bit of a hurry.
I used the below query for joining wihtout buffer time
SELECT DISTINCT mb.Id AS mbaid,
mp.id AS mapid,
mp.Channel AS Channel,
mp.Product,
mp.ProgDate,
mp.AdvTime,
mb.Channel,
mb.ProgStartTime,
mb.ProgEndTime,
convert(time, dateadd(MINUTE, datediff(MINUTE, mb.progStartTime, mb.progEndTime), 0)) AS timeDiff
FROM map22 AS mp
INNER JOIN mba22 AS mb ON ((mp.ProgDate = mp.ProgDate
AND mp.Channel=mb.Channel
AND mp.Product=mb.Product))
WHERE (mp.ProgDate = mb.ProgDate
AND AdvTime >= ProgStartTime
AND (AdvTime <= ProgEndTime
OR ProgEndTime < ProgStartTime))
OR (mp.ProgDate = Dateadd(DAY,1,mb.ProgDate)
AND ProgEndTime < ProgStartTime
AND AdvTime <= ProgEndTime)
ORDER BY mp.Id ASC

Your sample query has a lot going on, so I've created a simplified example.
Set up the data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 18:00:00', '20130318 19:00:00'
insert into MBA select 2, '20130318 18:00:00', '20130318 20:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130318 17:55:00'
insert into Map select 2, '20130318 18:30:00'
insert into Map select 3, '20130318 19:05:00'
insert into Map select 4, '20130318 20:05:00'
Based on this, we can apply a CASE statement to give AdvTime more loose matching when the difference between the dates is an hour or less:
select *
from MBA
inner join Map on
MBA.ProgStartTime <=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
and MBA.ProgEndTime >=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end
Gives results:
We can see that for MBA 1, which has an hour duration, we are matching AdvTime values slightly before and after, but for MBA 2, only matching those within the time period as required.
SQL Fiddle with demo.
Edit after comment:
Added another example for the values in the comments, with following data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 21:00:00', '20130318 22:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130318 20:55:00'
insert into Map select 2, '20130318 22:05:00'
The original query matches both of the above rows as expected.
SQL Fiddle with demo.
Edit after comment:
Tested with more data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '20130318 23:00:00', '20130319 02:00:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '20130319 00:30:00'
Still matching as expected.
SQL Fiddle with demo.
Final edit after comment?
OK, now we know a bit more about the schema we can make one final query. Set up the data:
create table MBA (MBAID int, ProgStartTime datetime, ProgEndTime datetime)
insert into MBA select 1, '18:00:00', '19:00:00'
insert into MBA select 2, '18:00:00', '20:00:00'
insert into MBA select 3, '21:00:00', '22:00:00'
insert into MBA select 4, '23:30:00', '02:00:00'
insert into MBA select 5, '23:30:00', '00:30:00'
create table Map (MapID int, AdvTime datetime)
insert into Map select 1, '17:55:00'
insert into Map select 2, '18:30:00'
insert into Map select 3, '19:05:00'
insert into Map select 4, '20:05:00'
insert into Map select 5, '20:55:00'
insert into Map select 6, '22:05:00'
insert into Map select 7, '23:25:00'
insert into Map select 8, '23:30:00'
insert into Map select 9, '00:30:00'
insert into Map select 10, '00:35:00'
Use the following query:
select *
from MBA
inner join Map on
(MBA.ProgStartTime < MBA.ProgEndTime
and MBA.ProgStartTime <=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
and MBA.ProgEndTime >=
case when datediff(mi, MBA.ProgStartTime, MBA.ProgEndTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end) or
(MBA.ProgStartTime > MBA.ProgEndTime
and (MBA.ProgStartTime <=
case when 1440 - datediff(mi, MBA.ProgEndTime, MBA.ProgStartTime) <= 60
then dateadd(mi, 5, Map.AdvTime)
else Map.AdvTime
end
or MBA.ProgEndTime >=
case when 1440 - datediff(mi, MBA.ProgEndTime, MBA.ProgStartTime) <= 60
then dateadd(mi, -5, Map.AdvTime)
else Map.AdvTime
end))
We expect the following rows to be matched:
MBA Matched Maps
1 1,2,3
2 2,3
3 5,6
4 8,9,10
5 7,8,9,10
Results:
SQL Fiddle with demo.

Related

How count of numbers id for a period of time

I am looking for a way to count the number of id in a period of time every 30 minutes.
I wrote a SQL query, but the result is incorrect
CREATE TABLE [dbo].[#tabl]
(
[Id] [varchar](100) NULL,
[TIMEStart] [datetime] NULL,
[TIMEEnd] [datetime] NULL
) ON [PRIMARY]
INSERT INTO [dbo].[#tabl] VALUES ('1', '2020-04-01 00:05:00.000', '2020-04-01 00:10:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('2', '2020-04-01 00:11:00.000', '2020-04-01 00:29:00.000')
INSERT INTO [dbo].[#tabl] VALUES ('3', '2020-04-01 00:12:00.000', '2020-04-01 00:55:00.000')
WITH CTE AS
(
SELECT
[Id],
DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart], [TIMEEnd]) / 30) * 30, 0) AS RangeTime
FROM
[dbo].[#tabl]
GROUP BY
[Id], DATEADD(MINUTE, (DATEDIFF(MINUTE, [TIMEStart],[TIMEEnd]) / 30) * 30, 0)
)
SELECT numreq, RangeTime
FROM
(SELECT COUNT(DISTINCT id) AS numreq, RangeTime
FROM CTE
GROUP BY RangeTime) temp
Correct result - table:
numreq RangeTime
-------------------------------------
3 1900-01-01 00:00:00.000
1 1900-01-01 00:30:00.000
Period of time:
1900-01-01 00:00:00.000 - includes 3 id:1, 2, 3
1900-01-01 00:30:00.000 - includes 1 id:1
I think what you need is to create a list of RangeTime values that lie within the range of the TIMEStart and TIMEEnd values in tabl (which you can do with a recursive CTE), then you can JOIN that list back to tabl on an overlapping time range and count the number of rows that overlap each RangeTime:
WITH CTE AS (
SELECT DATEADD(MINUTE,(DATEDIFF(MINUTE, 0, MIN([TIMEStart]))/30)*30,0) as RangeTime,
MAX([TIMEEnd]) AS MaxTime
FROM [dbo].[tabl]
UNION ALL
SELECT DATEADD(MINUTE, 30, RangeTime), MaxTime
FROM CTE
WHERE DATEADD(MINUTE, 30, RangeTime) < MaxTime
)
SELECT RangeTime, COUNT(tabl.Id) AS numreq
FROM CTE
LEFT JOIN tabl ON tabl.TIMEStart < DATEADD(MINUTE, 30, RangeTime)
AND RangeTime <= tabl.TIMEEnd
GROUP BY RangeTime
Output:
RangeTime numreq
2020-04-01T00:00:00Z 3
2020-04-01T00:30:00Z 1
Demo on SQLFiddle
Note: I've presumed you want the actual time for the range, not times starting at the beginning of 1900...

How do I get all records where date is 6 months greater than today's date, using Microsoft SQL Server 2008?

I have a table with a date field of lastDelivery, and I want to retrieve all records that are 6 months or more older than today's date, how do I do this?
Try this:
SELECT * FROM Table
WHERE lastdelivery <= dateadd(month, -6, getdate())
Use DATEADD
Query
select * from your_table_name
where lastDelivery <= dateadd(month, -6, getdate());
DECLARE #subscriptions TABLE
(
OrderId int,
OrderName varchar(255),
lastDelivery date default getdate()
)
INSERT INTO #subscriptions (OrderName, lastDelivery)
VALUES
( 'Allen', '2015-05-20'),
( 'Bob', '2015-06-20'),
( 'Craig', '2015-07-20'),
( 'David', '2015-08-20'),
( 'Edward', '2015-09-20'),
( 'Frank', '2015-10-20'),
( 'George', '2015-11-20'),
( 'Harry', '2015-12-20')
SELECT OrderName FROM #subscriptions
WHERE lastDelivery <= DATEADD(MONTH, -6, GETDATE())
I tried some of the above, they do not work for me but this works:
SELECT * FROM `table_Columns` WHERE `date` <= '2020-01-13 04:40:51'
Please note that my date is manually set to 6 months ago.

WHERE clause with INSERT statement, "Incorrect Syntax" at the WHERE, and IF NOT EXISTS running regardless of if record exists

I have this INSERT query:
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
VALUES (1, 11, 1, DATEADD(day, 1, GETDATE()))
WHERE NOT EXISTS
(SELECT * FROM Appointments
WHERE StaffID = 1 AND CustomerID = 11 AND TimeSlot = 1 AND AppDate = DATEADD(day, 1, GETDATE()));
Yet it says "Incorrect syntax near the keyword 'WHERE'"
This is very basic error, but have no clue whats up.
I have also tried using IF NOT EXISTS, which gives no syntax errors, but also doesn't work, preforming the insert even though the record with the values does already exist:
IF NOT EXISTS
(
SELECT * FROM Appointments
WHERE StaffID = 1 AND CustomerID = 11 AND TimeSlot = 1 AND AppDate = DATEADD(day, 1, GETDATE())
)
BEGIN
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
VALUES (1, 11, 1, DATEADD(day, 1, GETDATE()))
END;
The record with the values 1, 11, 1, and tomorrow does 100% already exist, yet inserts it again.
You can't specify any WHERE clause with the INSERT .. VALUES statement. But you can do that with the INSERT .. SELECT statement as shown below.
Also...
The record with the values 1, 11, 1, and tomorrow does 100% already exist, yet inserts it again.
GETDATE() returns a timestamp with fractional seconds precision, which is unlikely to already exist in your table. You probably want to use the DATE data type, e.g. using
DATEADD(day, 1, CAST(GETDATE() AS DATE))
INSERT .. SELECT
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
SELECT 1, 11, 1, DATEADD(day, 1, CAST(GETDATE() AS DATE))
WHERE NOT EXISTS (
SELECT *
FROM Appointments
WHERE StaffID = 1
AND CustomerID = 11
AND TimeSlot = 1
AND AppDate = DATEADD(day, 1, CAST(GETDATE() AS DATE))
);
INSERT .. SELECT with common table expression to avoid duplication
Or perhaps, avoiding calculating GETDATE() several times:
WITH ins (StaffID, CustomerID, TimeSlot, AppDate)
AS (SELECT 1, 11, 1, DATEADD(day, 1, CAST(GETDATE() AS DATE)))
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
SELECT ins.StaffID, ins.CustomerID, ins.TimeSlot, ins.AppDate
FROM ins
WHERE NOT EXISTS (
SELECT *
FROM Appointments
WHERE StaffID = ins.StaffID
AND CustomerID = ins.CustomerID
AND TimeSlot = ins.TimeSlot
AND AppDate = ins.AppDate
);
INSERT .. SELECT with set operations
Or, again, more concisely using set operations:
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
SELECT 1, 11, 1, DATEADD(day, 1, CAST(GETDATE() AS DATE))
EXCEPT
SELECT StaffID, CustomerID, TimeSlot, AppDate
FROM Appointments
MERGE
Actually, the whole "insert if not exists" concept can be modelled using MERGE as well:
MERGE INTO Appointments a
USING (
SELECT
1 StaffID,
11 CustomerID,
1 TimeSlot,
DATEADD(day, 1, CAST(GETDATE() AS DATE)) AppDate
) ins
ON (
a.StaffID = ins.StaffID AND
a.CustomerID = ins.CustomerID AND
a.TimeSlot = ins.TimeSlot AND
a.AppDate = ins.AppDate
)
WHEN NOT MATCHED THEN INSERT (StaffID, CustomerID, TimeSlot, AppDate)
VALUES (ins.StaffID, ins.CustomerID, ins.TimeSlot, ins.AppDate)
you shouldn't involve time while insertion and write a query as:
INSERT INTO Appointments (StaffID, CustomerID, TimeSlot, AppDate)
select 1, 11, 1, DATEADD(day, 1, cast(GETDATE() as date))
WHERE NOT EXISTS
(SELECT *
FROM Appointments
WHERE StaffID = 1 AND CustomerID = 11 AND TimeSlot = 1
AND AppDate = DATEADD(day, 1, cast(GETDATE() as date))
)

T-SQL - query for records before and after current time in same statement

For you T-SQL gurus:
I have the following table:
ID Arrival
1 06:16:00
2 06:17:00
3 07:19:00
4 08:21:00
5 10:22:00
6 13:21:00
7 20:22:00
Say the time is currently 08:00 AM and I want to select 2 records before and after record with closest time to now. Result should return records with IDs 2,3,4,5 and 6.
Getting the records before and after record with ID=4 is straight forward but so far, I cannot figure out how to return the complete set as part of the same query. I have these two select statements:
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) < 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate())
SELECT TOP(2) * FROM Schedules
where (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) >= 0
order by (datepart(hour, Arrival) - datepart(hour, getdate()))*60 + datepart(minute, Arrival) - datepart(minute, getdate()) asc
which return records before and after. I tried using a union on both statements but this requires dropping the first order by clause which invalidates my query criteria.
Any ideas will help, thanks.
We can use ROW_NUMBER to partition on if the arrival is before or after and order by the absolute value of the difference between the arrival and the input time.
DECLARE #CurrentTime as time
SET #CurrentTime = '08:00 AM'
DECLARE #Schedules table (id int, arrival time)
INSERT INTO #Schedules
VALUES (1 , '06:16:00' ),
(2, '06:17:00' ),
(3, '07:19:00'),
(4, '08:21:00'),
(5, '10:22:00'),
(6, '13:21:00'),
(7, '20:22:00')
DECLARE #closestTime as time
SELECT TOP 1 #closestTime = arrival FROM #Schedules ORDER BY ABS(DATEDIFF(mi, #CurrentTime ,arrival))
;WITH cte
AS (SELECT id,
arrival,
Row_number() OVER (PARTITION BY (CASE WHEN #closestTime > arrival THEN 1
WHEN #closestTime < arrival THEN 2 END)
ORDER BY Abs(Datediff(mi, #closestTime, arrival))) rn
FROM #Schedules)
SELECT *
FROM cte
WHERE rn < 3
OR arrival = #closestTime
ORDER BY id
Results in
id arrival rn
----------- ---------------- --------------------
2 06:17:00.0000000 2
3 07:19:00.0000000 1
4 08:21:00.0000000 1
5 10:22:00.0000000 1
6 13:21:00.0000000 2
See working example at this data.se query
Can you try the following? I have tested, got the result that u want
create table Arriavel (ID int, Arr_time time)
insert into Arriavel values (1, '06:16:00')
insert into Arriavel values (2, '06:17:00')
insert into Arriavel values (3, '07:19:00')
insert into Arriavel values (4, '08:21:00')
insert into Arriavel values (5, '10:22:00')
insert into Arriavel values (6, '13:21:00')
insert into Arriavel values (7, '20:22:00')
declare #cur_time time
set #cur_time = '08:00:00'
select max(arr_time) arr_time
from Arriavel
where arr_time < #cur_time
union all
select min(arr_time) arr_time
from Arriavel
where arr_time > #cur_time

How to delete when the parameter varies by group without looping? (T-SQL)

Imagine I have these columns in a table:
id int NOT NULL IDENTITY PRIMARY KEY,
instant datetime NOT NULL,
foreignId bigint NOT NULL
For each group (grouped by foreignId) I want to delete all the rows which are 1 hour older than the max(instant). Thus, for each group the parameter is different.
Is it possible without looping?
Yep, it's pretty straightforward. Try this:
DELETE mt
FROM MyTable AS mt
WHERE mt.instant <= DATEADD(hh, -1, (SELECT MAX(instant)
FROM MyTable
WHERE ForeignID = mt.ForeignID))
Or this:
;WITH MostRecentKeys
AS
(SELECT ForeignID, MAX(instant) AS LatestInstant
FROM MyTable)
DELETE mt
FROM MyTable AS mt
JOIN MostRecentKeys mrk ON mt.ForeignID = mrt.ForeignID
AND mt.Instant <= DATEADD(hh, -1, mrk.LatestInstant)
DELETE
FROM mytable
FROM mytable mto
WHERE instant <
(
SELECT DATEADD(hour, -1, MAX(instant))
FROM mytable mti
WHERE mti.foreignid = mto.foreignid
)
Note double FROM clause, it's on purpose, otherwise you won't be able to alias the table you're deleting from.
The sample data to check:
DECLARE #mytable TABLE
(
id INT NOT NULL PRIMARY KEY,
instant DATETIME NOT NULL,
foreignID INT NOT NULL
)
INSERT
INTO #mytable
SELECT 1, '2009-22-07 10:00:00', 1
UNION ALL
SELECT 2, '2009-22-07 09:30:00', 1
UNION ALL
SELECT 3, '2009-22-07 08:00:00', 1
UNION ALL
SELECT 4, '2009-22-07 10:00:00', 2
UNION ALL
SELECT 5, '2009-22-07 08:00:00', 2
UNION ALL
SELECT 6, '2009-22-07 07:30:00', 2
DELETE
FROM #mytable
FROM #mytable mto
WHERE instant <
(
SELECT DATEADD(hour, -1, MAX(instant))
FROM #mytable mti
WHERE mti.foreignid = mto.foreignid
)
SELECT *
FROM #mytable
1 2009-07-22 10:00:00.000 1
2 2009-07-22 09:30:00.000 1
4 2009-07-22 10:00:00.000 2
I'm going to assume when you say '1 hour older than the max(instant)' you mean '1 hour older than the max(instant) for that foreignId'.
Given that, there's almost certainly a more succinct way than this, but it will work:
DELETE
TableName
WHERE
DATEADD(hh, 1, instant) < (SELECT MAX(instant)
FROM TableName T2
WHERE T2.foreignId = TableName.foreignId)
The inner subquery is called a 'correlated subquery', if you want to look for more info. The way it works is that for each row under consideration by the outer query, it is the foreignId of that row that gets referenced by the subquery.