问题
I am working on automating the SMS sending part in my web application.
SQL Fiddle Link
DurationType table stores whether the sms should be sent out an interval of Hours, Days, Weeks, Months. Referred in SMSConfiguration
CREATE TABLE [dbo].[DurationType](
[Id] [int] NOT NULL PRIMARY KEY,
[DurationType] VARCHAR(10) NOT NULL
)
Bookings table Contains the original Bookings data. For this booking I need to send the SMS based on configuration.

SMS Configuration. Which defines the configuration for sending automatic SMS. It can be send before/after. Before=0, After=1. DurationType can be Hours=1, Days=2, Weeks=3, Months=4

Now I need to find out the list of bookings that needs SMS to be sent out at the current time based on the SMS Configuration Set.
Tried SQL using UNION
DECLARE @currentTime smalldatetime = '2014-07-12 11:15:00'
-- 'SMS CONFIGURED FOR HOURS BASIS'
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime,@currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE (DATEDIFF(HOUR, @currentTime, B.StartTime) = SMS.Duration AND SMS.DurationType=1 AND BeforeAfter=0)
OR
(DATEDIFF(HOUR, B.StartTime, @currentTime) = SMS.Duration AND SMS.DurationType=1 AND BeforeAfter=1)
--'SMS CONFIGURED FOR DAYS BASIS'
UNION
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime,@currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE (DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration AND SMS.DurationType=2 AND BeforeAfter=0)
OR
(DATEDIFF(DAY, B.StartTime, @currentTime) = SMS.Duration AND SMS.DurationType=2 AND BeforeAfter=1)
--'SMS CONFIGURED FOR WEEKS BASIS'
UNION
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime, @currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE (DATEDIFF(DAY, @currentTime, B.StartTime)/7 = SMS.Duration AND SMS.DurationType=3 AND BeforeAfter=0)
OR
(DATEDIFF(DAY, B.StartTime, @currentTime)/7 = SMS.Duration AND SMS.DurationType=3 AND BeforeAfter=1)
--'SMS CONFIGURED FOR MONTHS BASIS'
UNION
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime, @currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE (dbo.FullMonthsSeparation(@currentTime, B.StartTime) = SMS.Duration AND SMS.DurationType=4 AND BeforeAfter=0)
OR
(dbo.FullMonthsSeparation(B.StartTime, @currentTime) = SMS.Duration AND SMS.DurationType=4 AND BeforeAfter=1)
Result

Problem:
The SQL procedure will be running every 15 mins. Current query keep returning days/weeks/months records even for the current time '2014-07-12 11:30:00', '2014-07-12 11:45:00', etc
I want a single query that takes care of all Hours/Days/Weeks/Months calculation and I should be get records only one time when they meet the correct time. Otherwise I will be sending sms again and again every 15 mins for day/week/months records matched.
It should consider the following scenarios.
Hour, If booking is 10:15 A.M same day 9:15 A.M if it is before 1 hour configured
Day(24 Hour difference), If booking is 10:15 A.M 3rd day morning 10:15 A.M if it is configured after 3 days in SMSConfiguration
Match Week. If booking is 10:15 A.M today(Wednesday) morning then after 14 days morning 10:15 A.M if it is configured after 2 weeks.
Month also same logic like above.
回答1:
Try the simplified version FIDDLE , removed Union and used OR conditions
LIST FOR HOURS - RUN Every 15 mins
DECLARE @currentTime smalldatetime = '2014-07-12 11:15:00'
SELECT B.Id AS BookingId, C.Id, C.Name,
B.StartTime AS BookingStartTime,@currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
LEFT JOIN Category C ON C.Id=B.CategoryId
WHERE
(DATEDIFF(MINUTE, @currentTime, B.StartTime) = SMS.Duration*60 AND SMS.DurationType=1 AND BeforeAfter=0)
OR
(DATEDIFF(MINUTE, B.StartTime, @currentTime) = SMS.Duration*60 AND SMS.DurationType=1 AND BeforeAfter=1)
Order BY B.Id
GO
LIST FOR DAYS/WEEKS/MONTHS - RUN Every Day Morning Once
DECLARE @currentTime smalldatetime = '2014-07-12 08:00:00'
SELECT B.Id AS BookingId, C.Id, C.Name,
B.StartTime AS BookingStartTime,@currentTime As CurrentTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
LEFT JOIN Category C ON C.Id=B.CategoryId
WHERE
(((DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration AND SMS.DurationType=2)
OR (DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration*7 AND SMS.DurationType=3)
OR (DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration*30 AND SMS.DurationType=4))
AND BeforeAfter=0)
OR
(((DATEDIFF(DAY, B.StartTime, @currentTime) = SMS.Duration AND SMS.DurationType=2)
OR (DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration*7 AND SMS.DurationType=3)
OR (DATEDIFF(DAY, @currentTime, B.StartTime) = SMS.Duration*30 AND SMS.DurationType=4))
AND BeforeAfter=1)
Order BY B.Id
回答2:
You seem to have forgotten to pick some columns to return.
DECLARE @currentTime smalldatetime = '2014-07-12 09:15:00';
SELECT [col1], [col2], [etcetera]
FROM
Bookings B
INNER JOIN SMSConfiguration SMS ON SMS.CategoryId=B.CategoryId
WHERE DATEDIFF(hour,B.StartTime,@currentTime)=1
回答3:
This works
DECLARE @currentTime smalldatetime
set @currentTime= '2014-07-12 09:15:00'
SELECT B.CategoryId FROM
Bookings B
INNER JOIN SMSConfiguration SMS ON SMS.CategoryId=B.CategoryId
WHERE DATEDIFF(hh,B.StartTime,@currentTime)=1
There were two problems in your sql fiddle
1) you didn't set the value of currentTime
2) there were columns in your select statement
Note: I took B.CategoryId
as one of the column to fetch records, you can change it as per your requirement
回答4:
Just swap the dates in DATEDIFF
function
Change DATEDIFF(hour, B.StartTime, @currentTime) to DATEDIFF(hour, @currentTime, B.StartTime) - for HOUR only
Because yours one return the value in Negative (-1).
DECLARE @currentTime smalldatetime = '2014-07-12 09:15:00'
SELECT B.Id AS BookingId, @currentTime AS CurrentTime,
B.StartTime AS BookingStartTime, SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE (SMS.CategoryId IS NOT NULL AND
DATEDIFF(HOUR, @currentTime, B.StartTime) = SMS.Duration) OR
(SMS.CategoryId IS NULL AND
(DATEDIFF(DAY, B.StartTime, @currentTime) = SMS.Duration OR
DATEDIFF(MONTH, B.StartTime, @currentTime) = SMS.Duration))
回答5:
You can use the nested CASE expressions for achieve your results in single query. please try this,
DECLARE @currentTime smalldatetime = '2014-07-12 11:15:00'
SELECT B.Id AS BookingId,
SMS.Duration,
B.StartTime AS BookingStartTime,
@currentTime As CurrentTime,
SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS
ON SMS.CategoryId = B.CategoryId
OR SMS.CategoryId IS NULL
WHERE
SMS.Duration =
CASE
WHEN SMS.DurationType = 1 THEN --'SMS CONFIGURED FOR HOURS BASIS'
CASE WHEN BeforeAfter = 0 THEN
DATEDIFF(HOUR, @currentTime, B.StartTime)
ELSE
DATEDIFF(HOUR, B.StartTime, @currentTime)
END
WHEN SMS.DurationType = 2 THEN --'SMS CONFIGURED FOR DAY BASIS'
CASE WHEN BeforeAfter = 0 THEN
DATEDIFF(DAY, @currentTime, B.StartTime)
ELSE
DATEDIFF(DAY, B.StartTime, @currentTime)
END
WHEN SMS.DurationType = 3 THEN --'SMS CONFIGURED FOR WEEK BASIS'
CASE WHEN BeforeAfter = 0 THEN
DATEDIFF(DAY, @currentTime, B.StartTime)/7
ELSE
DATEDIFF(DAY, B.StartTime, @currentTime)/7
END
ELSE
CASE WHEN BeforeAfter = 0 THEN -- 'SMS CONFIGURED FOR MONTH BASIS'
dbo.FullMonthsSeparation(@currentTime, B.StartTime)
ELSE
dbo.FullMonthsSeparation(B.StartTime, @currentTime)
END
END
ORDER BY BOOKINGID;
you can automate in two schedules one for getting booking details of Hourly sms and other one for Daily/Weekly/Monthly booking details.
回答6:
Try:
DECLARE @currentTime smalldatetime = '2014-07-12 09:15:00'
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime,
@currentTime CURRENT_DT,
SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE ( SMS.DurationType = 1
AND @currentTime = DATEADD(HOUR, (2*SMS.BeforeAfter-1)*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 2
AND @currentTime = DATEADD(DAY, (2*SMS.BeforeAfter-1)*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 3
AND @currentTime = DATEADD(WEEK, (2*SMS.BeforeAfter-1)*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 4
AND @currentTime = DATEADD(MONTH, (2*SMS.BeforeAfter-1)*SMS.Duration, B.StartTime))
The 2*SMS.BeforeAfter-1 converts Before(0) to -1 and After(1) to 1. Thus, adding or subtracting time from the BookingStartTime
[Edit]
The above solution should work with your data model as is. Below I propose simplifying the solution by changing your data model.
Option 1: Change Before and After meta data
http://sqlfiddle.com/#!3/6e9e9/1
Instead of Before = 0
and After = 1
, use Before = -1
and After = 1
. Then these can be used to change the direction of any date offset
For example:
INSERT INTO [dbo].[SMSConfiguration]
([CategoryId],[SMSText],[BeforeAfter],[Duration],[DurationType],[IsActive])
VALUES
(1,'Before 1 hour',-1,1,1,1)--
INSERT INTO [dbo].[SMSConfiguration]
([CategoryId],[SMSText],[BeforeAfter],[Duration],[DurationType],[IsActive])
VALUES
(NULL,'After 1 hour (no category id))',1,1,1,1)
Updated query:
DECLARE @currentTime smalldatetime = '2014-07-12 09:15:00'
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime,
@currentTime CURRENT_DT,
SMS.SMSText
FROM Bookings B INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
WHERE ( SMS.DurationType = 1
AND @currentTime = DATEADD(HOUR, SMS.BeforeAfter*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 2
AND @currentTime = DATEADD(DAY, SMS.BeforeAfter*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 3
AND @currentTime = DATEADD(WEEK, SMS.BeforeAfter*SMS.Duration, B.StartTime))
OR
( SMS.DurationType = 4
AND @currentTime = DATEADD(MONTH, SMS.BeforeAfter*SMS.Duration, B.StartTime))
Option 2: Allow 30-days = 1 month
http://sqlfiddle.com/#!3/808cd/1
On top of the Before/After change, if a 30 day period is close enough to represent a month, then all duration types could be converted to hours. The question is how particular your customers will be in regard to precision. If you are off by a day or two on a "3 month before" rule, I don't think you will have many complaints.
So your DurationType table could look like this
CREATE TABLE [dbo].[DurationType](
[Id] [int] NOT NULL PRIMARY KEY,
[DurationType] VARCHAR(10) NOT NULL,
[HourConversion] [int] NOT NULL
)
GO
INSERT INTO [dbo].[DurationType]([Id],[DurationType],[HourConversion])
VALUES(1,'Hours',1)
INSERT INTO [dbo].[DurationType]([Id],[DurationType],[HourConversion])
VALUES(2,'Days',24)
INSERT INTO [dbo].[DurationType]([Id],[DurationType],[HourConversion])
VALUES(3,'Weeks',24*7)
INSERT INTO [dbo].[DurationType]([Id],[DurationType],[HourConversion])
VALUES(4,'Months',24*30)
With these changes, the query could be modified to:
DECLARE @currentTime smalldatetime = '2014-07-12 09:15:00'
SELECT B.Id AS BookingId,
B.StartTime AS BookingStartTime,
@currentTime CURRENT_DT,
SMS.SMSText
FROM Bookings B
INNER JOIN
SMSConfiguration SMS ON SMS.CategoryId = B.CategoryId OR SMS.CategoryId IS NULL
INNER JOIN
DurationType DT ON DT.Id = SMS.DurationType
WHERE @currentTime = DATEADD(HOUR, SMS.BeforeAfter*SMS.Duration*DT.HourConversion, B.StartTime)
来源:https://stackoverflow.com/questions/24609263/get-records-based-on-current-date-and-configured-data