Showing posts with label sample. Show all posts
Showing posts with label sample. Show all posts

Wednesday, March 28, 2012

Poor performance for business day calculation from aspfaq sample

Greeting, below is the complete SQL taken from aspfaq.com (retrieved
from this newsgroup I believe) The query takes about two minutes to
run. Does anybody have a better set based way (sub-second response) to
determine business days?

CREATE TABLE dbo.Calendar

(

dt SMALLDATETIME NOT NULL PRIMARY KEY
CLUSTERED, -- Date value

IsWeekday BIT,
-- Is this date a weekday (M -
F)

IsHoliday BIT,
-- Is this date a holiday

Y SMALLINT,
-- Year the date falls in

FY SMALLINT,
-- Fiscal Year (needed?)

Q TINYINT,
-- Quarter date falls in

M TINYINT,
-- Numeric month of date

D TINYINT,
-- Numeric day of date

DW TINYINT,
-- Numeric DayOfWeek
(Sunda=1,Monday=2)

MonthName VARCHAR(9),
-- String name of month

DayName VARCHAR(9),
-- String name of day

W TINYINT
-- Week number

)

GO

-- Start & End Dates

DECLARE @.StartDate DATETIME

DECLARE @.EndDate DATETIME

SET @.StartDate = DATEADD(d, -1, '20000101')

SET @.EndDate = DATEADD(d, -1, '20300101')

-- Total number of dates to generate

DECLARE @.Days INT

SET @.Days = DATEDIFF(DAY, @.StartDate, @.EndDate)

-- Create temporary Numbers table

CREATE TABLE #Numbers

(

Number INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED

)

-- Insert a number into our temp table for each date to be generated

WHILE COALESCE(SCOPE_IDENTITY(), 0) <= @.Days

BEGIN

INSERT #Numbers DEFAULT VALUES

END

-- Generate a date for each day in our timespan

INSERT Calendar(dt)

SELECT DATEADD(DAY, Number, @.StartDate)

FROM #Numbers

WHERE Number <= @.Days

ORDER BY Number

-- Remove the temporary Numbers table

DROP TABLE #Numbers

GO

-- Update other columns

UPDATE dbo.Calendar SET

IsWeekday = CASE WHEN DATEPART(DW, dt) IN (1, 7) THEN 0
ELSE 1 END,

IsHoliday = 0,

Y = YEAR(dt),

FY = YEAR(dt),

Q = CASE

WHEN MONTH(dt) <= 3 THEN 1

WHEN MONTH(dt) <= 6 THEN 2

WHEN MONTH(dt) <= 9 THEN 3

ELSE 4 END,

M = MONTH(dt),

D = DAY(dt),

DW = DATEPART(DW, dt),

MonthName = DATENAME(MONTH, dt),

DayName = DATENAME(DW, dt),

W = DATEPART(WK, dt)

-- Query in question (takes almost 2 minutes to execute and return a
value)

SELECT

C.dt

FROM

Calendar C

WHERE

C.IsWeekDay = 1

AND C.IsHoliday = 0

AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )Here's an easy one:

SELECT
CASE
WHEN ([Date] % 7) > 1 THEN 'Business day'
ELSE 'Weekend day'
END AS "IsBusinessDay"
FROM table

The symbol % here is the modulo operator.
The remainder of dividing Date by 7 returns
0 for Saturday, 1 for Sunday and up to 6 for Friday.
Remainders from 2 to 6 correspond to Monday to Friday.

GeoSynch

"pb648174" <google@.webpaul.net> wrote in message
news:1113345847.550164.212820@.l41g2000cwc.googlegr oups.com...
> Greeting, below is the complete SQL taken from aspfaq.com (retrieved
> from this newsgroup I believe) The query takes about two minutes to
> run. Does anybody have a better set based way (sub-second response) to
> determine business days?
> CREATE TABLE dbo.Calendar
> (
> dt SMALLDATETIME NOT NULL PRIMARY KEY
> CLUSTERED, -- Date value
> IsWeekday BIT,
> -- Is this date a weekday (M -
> F)
> IsHoliday BIT,
> -- Is this date a holiday
> Y SMALLINT,
> -- Year the date falls in
> FY SMALLINT,
> -- Fiscal Year (needed?)
> Q TINYINT,
> -- Quarter date falls in
> M TINYINT,
> -- Numeric month of date
> D TINYINT,
> -- Numeric day of date
> DW TINYINT,
> -- Numeric DayOfWeek
> (Sunda=1,Monday=2)
> MonthName VARCHAR(9),
> -- String name of month
> DayName VARCHAR(9),
> -- String name of day
> W TINYINT
> -- Week number
> )
> GO
>
> -- Start & End Dates
> DECLARE @.StartDate DATETIME
> DECLARE @.EndDate DATETIME
> SET @.StartDate = DATEADD(d, -1, '20000101')
> SET @.EndDate = DATEADD(d, -1, '20300101')
>
> -- Total number of dates to generate
> DECLARE @.Days INT
> SET @.Days = DATEDIFF(DAY, @.StartDate, @.EndDate)
>
> -- Create temporary Numbers table
> CREATE TABLE #Numbers
> (
> Number INT IDENTITY(1, 1) PRIMARY KEY CLUSTERED
> )
>
> -- Insert a number into our temp table for each date to be generated
> WHILE COALESCE(SCOPE_IDENTITY(), 0) <= @.Days
> BEGIN
> INSERT #Numbers DEFAULT VALUES
> END
>
> -- Generate a date for each day in our timespan
> INSERT Calendar(dt)
> SELECT DATEADD(DAY, Number, @.StartDate)
> FROM #Numbers
> WHERE Number <= @.Days
> ORDER BY Number
>
> -- Remove the temporary Numbers table
> DROP TABLE #Numbers
> GO
>
> -- Update other columns
> UPDATE dbo.Calendar SET
> IsWeekday = CASE WHEN DATEPART(DW, dt) IN (1, 7) THEN 0
> ELSE 1 END,
> IsHoliday = 0,
> Y = YEAR(dt),
> FY = YEAR(dt),
> Q = CASE
> WHEN MONTH(dt) <= 3 THEN 1
> WHEN MONTH(dt) <= 6 THEN 2
> WHEN MONTH(dt) <= 9 THEN 3
> ELSE 4 END,
> M = MONTH(dt),
> D = DAY(dt),
> DW = DATEPART(DW, dt),
> MonthName = DATENAME(MONTH, dt),
> DayName = DATENAME(DW, dt),
> W = DATEPART(WK, dt)
>
>
> -- Query in question (takes almost 2 minutes to execute and return a
> value)
> SELECT
> C.dt
> FROM
> Calendar C
> WHERE
> C.IsWeekDay = 1
> AND C.IsHoliday = 0
> AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
> GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )|||We already have the isWeekday column in the table - what we need to
know is, how do I add 9 business days to a particular date efficiently?|||On 12 Apr 2005 15:44:07 -0700, pb648174 wrote:

> Greeting, below is the complete SQL taken from aspfaq.com (retrieved
> from this newsgroup I believe) The query takes about two minutes to
> run. Does anybody have a better set based way (sub-second response) to
> determine business days?
[snip]
> -- Query in question (takes almost 2 minutes to execute and return a
> value)
> SELECT
> C.dt
> FROM
> Calendar C
> WHERE
> C.IsWeekDay = 1
> AND C.IsHoliday = 0
> AND 9 = (SELECT COUNT(*) FROM Calendar C2 WHERE C2.dt >=
> GETDATE() AND C2.dt <= C.dt AND C2.IsWeekDay = 1 AND C2.IsHoliday = 0 )

It looks to me like you're looking for the day that is nine business days
from today. At least, once I let the query above run for the two minutes, I
got April 26th when I ran it today (April 13th).

If that's so, then you *know* ahead of time that that day will be greater
than today and less than 60 days from today, right? Unless there's some
weird span of sixty consecutive holidays, anyway. So you can add that fact
to the main where clause, and that will speed it up mightily:

SELECT
C.dt
FROM
Calendar C
WHERE
C.IsWeekDay = 1
AND C.IsHoliday = 0
AND C.dt BETWEEN GETDATE() AND DATEADD(d,60,GETDATE())
AND 9 = (
SELECT COUNT(*) FROM Calendar C2
WHERE C2.dt >= GETDATE()
AND C2.dt <= C.dt
AND C2.IsWeekDay = 1
AND C2.IsHoliday = 0)

Another thing you can do is to add indexes to the IsWeekDay and IsHoliday
columns (which requires changing them from BIT to TINYINT, since BIT can't
be indexed). Or even add a covering index on (DT, IsWeekDay, IsHoliday).
But that won't be necessary if you bound the main SELECT as I did -- I got
subsecond performance for the query above.|||That does execute much faster, but 50 is an arbitray number, and since
I will need to use it to schedule events up to one year or even
multiple years in the future, that won't work. In particular, for a set
of scheduled tasks which have a startdate and a lagdays column, I need
to calculate the end date for those tasks based on the above calendar
with business days/holidays entered. How would I do that with the above
query without the "bounding" limitation, returned as a set and with
decent performance?|||Here are some ideas:

1. This is fast but unfortunately in SQL2000 it can't be parameterized
without dynamic SQL:

SELECT MAX(dt)
FROM
(SELECT TOP 9 dt
FROM Calendar
WHERE dt >= CURRENT_TIMESTAMP
AND isweekday = 1
AND isholiday = 0
ORDER BY dt) AS T

2. This one can be parameterized but I wouldn't generally recommend it
because it relies on undocumented behaviour:

DECLARE @.days INTEGER, @.dt DATETIME

SET @.days = 9
SET ROWCOUNT @.days

SELECT @.dt = dt
FROM Calendar
WHERE dt >= CURRENT_TIMESTAMP
AND isweekday = 1
AND isholiday = 0
ORDER BY dt

SELECT @.dt

3. Extending Ross's suggestion, it shouldn't be difficult to calculate
a sensible upper bound for the query, even based on larger date ranges:

SELECT C.dt
FROM Calendar C
WHERE C.isweekday = 1
AND C.isholiday = 0
AND C.dt BETWEEN GETDATE() AND DATEADD(d,@.days*0.30+60.0,GETDATE())
AND 9 = (
SELECT COUNT(*) FROM Calendar C2
WHERE C2.dt >= GETDATE()
AND C2.dt <= C.dt
AND C2.IsWeekDay = 1
AND C2.IsHoliday = 0)

I think this last method is the best option, together with Ross's
suggestions on index improvements.

--
David Portas
SQL Server MVP
--|||And given that I have a table Called ScheduleTask with columns
StartDate & Duration, how do I for a group of rows, calculate the item
with the largest end date if the duration is based on the above
business days?

Why did you use 30%? Shouldn't it be 70% since I assume you are taking
out what you are guessing will be the weekends and holidays and adding
in a buffer of 60 days?|||On 14 Apr 2005 07:09:32 -0700, pb648174 wrote:

> That does execute much faster, but 50 is an arbitray number, and since
> I will need to use it to schedule events up to one year or even
> multiple years in the future, that won't work. In particular, for a set
> of scheduled tasks which have a startdate and a lagdays column, I need
> to calculate the end date for those tasks based on the above calendar
> with business days/holidays entered. How would I do that with the above
> query without the "bounding" limitation, returned as a set and with
> decent performance?

If you need to get sets, I think the best idea is to number the
business-days in the calendar table.

ALTER dbo.Calendar
ADD BusinessDayNum INT NOT NULL DEFAULT (0)

UPDATE dbo.calendar
SET BusinessDayNum = (
SELECT COUNT(*) FROM calendar C2
WHERE c2.isWeekday=1 and c2.isHoliday=0
AND C2.dt <= Calendar.dt
)
WHERE isWeekday=1 AND isHoliday=0

CREATE INDEX idx_Cal_BDN ON dbo.Calendar (BusinessDayNum)

Now you can do this:

CREATE TABLE #myTable
( startDate DATETIME NOT NULL, DurationDays INT NOT NULL)

INSERT #myTable VALUES ('2003-05-14',10)
INSERT #myTable VALUES ('2003-05-15',12)
INSERT #myTable VALUES ('2003-05-16',14)
INSERT #myTable VALUES ('2004-05-14',10)
INSERT #myTable VALUES ('2004-05-15',12)
INSERT #myTable VALUES ('2004-05-16',14)

SELECT T.startDate, T.DurationDays, C2.dt "endDate"
FROM dbo.Calendar C2, dbo.Calendar C1, #myTable T
WHERE C1.DT = T.startDate
AND C2.BusinessDayNum = C1.BusinessDayNum + T.DurationDays

startDate DurationDays endDate
-------- ---- -------
2003-05-14 00:00:00.000 10 2003-05-28 00:00:00
2003-05-15 00:00:00.000 12 2003-06-02 00:00:00
2003-05-16 00:00:00.000 14 2003-06-05 00:00:00
2004-05-14 00:00:00.000 10 2004-05-28 00:00:00
2004-05-15 00:00:00.000 12 2000-01-18 00:00:00
2004-05-16 00:00:00.000 14 2000-01-20 00:00:00

Unfortunately, you will need to have ALL of your holidays set before you do
this, and if your holidays change you should redo the UPDATE.|||That's why this won't work... We will actually be storing the holidays
per user in a separate table. We are basically trying to duplicate MS
Project functionality and are now thinking we should do it all in
application logic instead of SQL, i.e. port in all the task data, do
all the calculations and then do a couple hundred updates for all of
the task data. SQL gurus, please show me a way to not have to do that...|||"pb648174" <google@.webpaul.net> wrote in message
news:1113487772.592286.157190@.z14g2000cwz.googlegr oups.com...
> That does execute much faster, but 50 is an arbitray number, and since
> I will need to use it to schedule events up to one year or even
> multiple years in the future, that won't work. In particular, for a set
> of scheduled tasks which have a startdate and a lagdays column, I need
> to calculate the end date for those tasks based on the above calendar
> with business days/holidays entered. How would I do that with the above
> query without the "bounding" limitation, returned as a set and with
> decent performance?

Maybe try something like:
AND C.dt BETWEEN startdate AND DATEADD(d,3*duration,startdate)

Good Luck,
Jim|||The design for the Calendar table you have is not that good -- BIT
flags and other proprietary datatypes are a mess. There is no
Julianized day number, teh weekday numbers are nto ISO Standard, etc.

Sit down and design a good encoding for the type of day -- weekend,
holiday, workday, etc. A calendar table for US Secular holidays can be
built from the data at this website, so you will get the three-day
weekends:

http://www.smart.net/~mmontes/ushols.html

I would consider using a spreadsheet to get the raw data, since they
usually have good temporal functions. Try a query like this:

SELECT MIN(C1.cal_date)
FROM Calendar AS C1
WHERE @.workday_count
= (SELECT SUM(CASE WHEN C1.cal_date
BETWEEN CURRENT_TIMESTAMP
AND DATEADD(D,
(@.workday_count *10), CURRENT_TIMESTAMP)
AND day_type = 'workday'
THEN 1 ELSE 0 END);

I am assuming that the cal_date column is a clustered primary key so
the optimizer will know to stop and not scan the rest of the table on
the first match. Multiplying the count by ten is probably excessive,
but certainly safe. if that is too slow, change it to 2 or 3.|||I think this is a sign that SQL just isn't going to be able to handle
this kind of job effectively. There will be user holidays added to this
as well, so it is possible that someone could be out for two weeks or
more and that when adding 1 business day, *10 will not be sufficient. I
thought since I got this example from one of the regular poster's web
site that it would be straightforward to implement but that seems to
not be the case. I think we are just going to pull all the data in and
do the calculation in C# and then write all the data back out. Thanks
for the help everyone.|||I am getting a little lost here. The auxiliary Calendar table has
nothing to do with any particular user; it is for the enterprise as a
whole. A personal timesheet is a differrent matter, since each
employee's will be different.

Did you want to have code that says something like when day X is a
Moslem holiday and employee Y is a Moslem, then X is not a work day for
him? Ramada runs for a lunar month, for example, so if you do:

WHERE @.workday_count
= (SELECT SUM(CASE WHEN C1.cal_date
BETWEEN CURRENT_TIMESTAMP
AND DATEADD(D,
(@.workday_count *10 + 30), CURRENT_TIMESTAMP)
AND secular_day_type = 'workday'
AND relgion_day_type <> 'Moslem'
THEN 1 ELSE 0 END)

You know that you are safe. this same pattern can be extended to
include Mr. Y's vacation, sick and personal leave days with a join to
his personal timesheet. the predicate in the WHEN just gets uglier,
but so will any procedural code that has to have the same logic inside
a loop with IF-THEN statements. Timekeeping is not simple.|||The biggest problem with the query you posted is that the * 10 is not
sufficient for the previously posted reason. What I am saying about the
holidays is that we have an indeterminate amount of users and each will
have their own set of holidays so there will be a separate table for
those holidays and that the Calendar table would have the "master" or
default list of holidays.|||"pb648174" <google@.webpaul.net> wrote in message
news:1113826655.615952.214820@.o13g2000cwo.googlegr oups.com...
> The biggest problem with the query you posted is that the * 10 is not
> sufficient for the previously posted reason. What I am saying about the
> holidays is that we have an indeterminate amount of users and each will
> have their own set of holidays so there will be a separate table for
> those holidays and that the Calendar table would have the "master" or
> default list of holidays.

If you have a calendar table with all of the dates and day types:
(Dt, daytype)

And you have a user table with dates and day types only for those days that
are different
(User, Dt, daytype)

Then you can get bob's calendar with

Select Dt, Coalesce(u.daytype,m.daytype) as daytype
from calendar as m left join usercalendar as u on m.dt = u.dt
Where u.user = 'bob'

Regards,
Jim|||Table joins I don't have a problem with.. My problem is the following,
which I have mentioned in each of the past couple replies:

"There will be user holidays added to this
as well, so it is possible that someone could be out for two weeks or
more and that when adding 1 business day, *10 will not be sufficient."

and

"The biggest problem with the query you posted is that the * 10 is not
sufficient for the previously posted reason"

because a person could have two weeks of vacation, a month of sick
leave, etc. I need something that performs well and doesn't include
some "hacked" factor to make it perform well. The first hack was the
Calendar table, which made this "* x" factor necessary as an additional
hack...

So far I don't see any indication that SQL will be able to handle this
kind of task.|||OK here goes:

1. Create a table (temp or static with ONLY working days and a
contiguous numbering sequence between one day and the next working day)
- that makes it very easy to do the offset.

2. adding the user holiday is easy (find the number of days holiday
between the 2 dates and recalculate)

Sample code (written assuming UK date format - should work with US):

SET NOCOUNT ON

DECLARE @.StartDate datetime
DECLARE @.DaysOffset int

SET @.StartDate = '2005-04-20'
SET @.DaysOffset = 5

DECLARE @.DateWithOffsetNoUserHolidays datetime
DECLARE @.TempDay datetime
DECLARE @.nSequence int
DECLARE @.UserWorkingDayHolidays int
DECLARE @.DateWithOffset datetime

SET @.UserWorkingDayHolidays = 0

CREATE TABLE #WorkingDays ([Date] smalldatetime NOT NULL, [sequence] int
NOT NULL)

SET @.nSequence = 0
SET @.TempDay = '2005-01-01'

WHILE @.TempDay < '2006-12-31'
BEGIN

IF DATEPART(dw, @.TempDay) BETWEEN 2 AND 6 -- AND NOT EXISTS(SELECT 1
FROM BankHolidays WHERE [Date] = @.TempDay
BEGIN
INSERT INTO #WorkingDays VALUES(@.TempDay, @.nSequence)
SET @.nSequence = @.nSequence + 1
END

SELECT @.TempDay = DATEADD(dd,1, @.TempDay)
END

SELECT @.DateWithOffsetNoUserHolidays = [Date]
FROM #WorkingDays
WHERE [Sequence] = (SELECT [Sequence] + @.DaysOffset FROM #WorkingDays
WHERE [Date] = @.StartDate)

/*Get number of user holidays during period between @.StartDate and
@.DateWithOffset and redo:
e.g.
SELECT @.UserWorkingDayHolidays = Count(*)
FROM UserHolidays
WHERE Date BETWEEN @.StartDate AND @.DateWithOffsetNoUserHolidays AND
User = ...
*/

SELECT @.DateWithOffset = [Date]
FROM #WorkingDays
WHERE [Sequence] = (SELECT [Sequence] + @.DaysOffset +
@.UserWorkingDayHolidays FROM #WorkingDays WHERE [Date] = @.StartDate)

DROP TABLE #WorkingDays

SELECT @.DateWithOffset

*** Sent via Developersdex http://www.developersdex.com ***|||My last effort was flawed for the user holiday element of your
requirement. Here's a better effort, but doesn't work for calculating
bussiness days in the past - you'd have to change the penultimate select
for that.

Change @.StartDate and @.BusinessDaysOffset to change the inputs. Should
be sub-second even for a 10

The script build a temporary table of working days the first time it's
run - better to have a permanent table reallty.

Duncan

SET NOCOUNT ON

DECLARE @.StartDate datetime
DECLARE @.BusinessDaysOffset int

SET @.StartDate = '2005-04-21'
SET @.BusinessDaysOffset = 2

IF @.BusinessDaysOffset < 1
RAISERROR('Offset must be > 0',16,1)

/* Create sample temp table for user holidays */
IF OBJECT_ID('tempdb..#UserHolidays') IS NULL
BEGIN
CREATE TABLE #UserHolidays (UserId int NOT NULL, HolidayDate
smalldatetime)

/* Add tomorrow as a holiday for user 1 - date must not contain time -
so that's why there's so many CASTs */
INSERT INTO #UserHolidays VALUES (1, CAST(CAST(CAST(DATEADD(d, 1,
getdate()) as real) as int) as smalldatetime))
END

DECLARE @.TempDay datetime
DECLARE @.nSequence int

/* Get date without time element */
SET @.StartDate = CAST(CAST(CAST(@.StartDate as real) as int) as
smalldatetime)

IF OBJECT_ID('tempdb..#WorkingDays') IS NULL
BEGIN
/* better to use a permanent table as the data doesn't change very
often - but this is just an example */
CREATE TABLE #WorkingDays ([Date] smalldatetime NOT NULL, [sequence]
int NOT NULL)
CREATE UNIQUE INDEX ID_WorkingDays_Date ON #WorkingDays ([Date])

SET @.nSequence = 0
SET @.TempDay = '2005-01-01'

WHILE @.TempDay < '2030-12-31'
BEGIN
/* don't add Saturday or Sunday - or bank holidays from table */
IF DATEPART(dw, @.TempDay) BETWEEN 2 AND 6 /* AND NOT EXISTS(SELECT
1 FROM BankHolidays WHERE [Date] = @.TempDay) */
BEGIN
INSERT INTO #WorkingDays VALUES(@.TempDay, @.nSequence)
SET @.nSequence = @.nSequence + 1
END

SELECT @.TempDay = DATEADD(dd,1, @.TempDay)
END
END
/*Temp table populated */

/* Create temp table to contain working days for the user and after date
specified*/
CREATE TABLE #UserWorkingDays ([Date] smalldatetime NOT NULL)

CREATE UNIQUE INDEX ID_UserWorkingDays_Date ON #UserWorkingDays ([Date])

/*Limit the number of rows to the number of working days required */
SET ROWCOUNT @.BusinessDaysOffset

/* Insert working days after current one where not joined to user's
holiday table*/
INSERT INTO #UserWorkingDays ([Date])
SELECT WD.[Date]
FROM #WorkingDays AS WD
LEFT JOIN #UserHolidays AS UH ON WD.[Date] = UH.HolidayDate AND
UH.UserId = 1
WHERE UH.HolidayDate IS NULL AND [Date] > @.StartDate
ORDER BY WD.[Date]

/* Get last working day */
SELECT MAX([Date])
FROM #UserWorkingDays

DROP TABLE #UserWorkingDays

*** Sent via Developersdex http://www.developersdex.com ***|||I had tried the rowcount solution previously and it did not work for
some (unposted, due to other queries around the calculations and we
will eventually need time) reasons. We have given up on doing this in
SQL and are doing it in C# quite successfully - thank you everyone for
the help.sql

Monday, March 26, 2012

Poison Messages - 5 times's a charm

Hello again,
I have some poison message detection in place, based on the BOL sample. My problem is that after the 5th message retry my queue goes down - that is the fifth retry on any message. In actuallity, the first message is retried 3 times and it is taken off the queue [for real], the second message comes in and on the second retry - pooof - the queue is down.

I though the poison mechanism should work on a per message basis. It there a setting for the queue I missed? Is my only chance for to fix this: re-enable the queue upon BROKER_QUEUE_DISABLED event notification?

Thanks,

Lubomir

The poison message counter is based on RECEIVE verbs being rolled back. So five RECEIVEs rolled back in a row and your queue is disabled. The number of individual messages received/rolled back is irelevant. Any RECEIVE commited will reset the counter to 0.

Unfortunately this is not configurable in any way, you only have the BROKER_QUEUE_DISABLED notification as a way to react.

HTH,
~ Remus

|||Sorry to unmark the answer. I want to have a check after a ROLLBACK on the queue to see if the queue is DISBLED? i just could not figure out the TSQL for that?

if QUEUE is disabled
ALTER [dbo].[ProcessQueue] WITH STATUS = ON

EVENT NOTIFICATION does not allow for a direct TSQL exectuion from what I have seen so far, getting the status of queue and acting on it is not straight forward.

Thanks,

Lubomir|||

Event notifications are deliverd through normal Service Broker messages, so the way to execute TSQL on an event notification is to have an activated procedure on the queue that receives the event notification and execute the TSQL in it. The name of the queue being disabled will be part of the notification message.

To get the queue status use the is_receive_enabled or is_enqueue_enabled in sys.service_queues:

if (1 = (select is_receive_enabled from sys.service_queues where name = '...'))
begin
alter queue [...] with status = on;
end

Trying to enable back the queue from the activated procedure, after the rollback, however, will probably not work, because the queue disabling mechanism is asynchronous so you're verification check query will sometimes get the status disabled, sometimes not.

I recommend you try to avoid the rollbacks in the first place. If you try to incorporate rollback in the logic then you might hit a real poison message situation in production and you system will halt to a grind, spining on this one message.

HTH,
~ Remus

|||Thank you for the reply Remus,
My logic does take out the message after 3 retries, so I figure I should be safe, no?
I perform the rollbacks to freshly process the message in the hope conditions that caused the failure have disappeared - slim chance on that and probably something that will cause more trouble than good.
What do yo think?

Lubomir|||

If what you're looking for is a retry mechanism, then you should probably consider dialog timers, perhaps used in a fashion similar to this:

BEGIN TRANSACTION;
WAITFOR(RECEIVE ... FROM ...), TIMEOUT ...;
IF MESSAGE TYPE IS 'UNRELIABLE REQUEST'
BEGIN
INSERT REQUEST MESSAGE BODY INTO USER TABLE;
BEGIN DIALOG TIMER @.someretrytimeout;
END
ELSE IF MESSAGE TYPE IS 'TIMER'
BEGIN
SELECT REQUEST MESSAGE BODY FROM USER TABLE;
-- Re-arm the timer
BEGIN DIALOG TIMER @.someretrytimeout;
END
COMMIT;

DO UNRELIABLE WORK HERE BASED ON THE RECEIVED/SELECTED REQUEST

IF SUCCEEDED
BEGIN
BEGIN TRANSACTION
SEND BACK RESPONSE;
-- RESET TIMER TO 0 (or END CONVERSATION, as appropiate)
BEGIN DIALOG TIMER 0;
COMMIT
END

This way you separate the error prone processing outside the transaction and you don't need to rollback on failure. Also, you get to controll the retry details: number of retries, time between retries, backout policy etc.

HTH,
~ Remus

Poison Message Sample in BOL

Hello Remus,

I have implemented Event Notification sample you provided in my logic, but I am perplexed that even though my watched queue does go down, I do not get message in the Event_Collector queue all the time. The Event_Collector queue does stay enabled though. Any thoughts? I guess any suggestions on debugging the event notification portion would be helpful.

Lubomir
Upon further look at the sys.event_notifications view, I discovered that my event notification is not there any more - it disappears. So the first time the queue goes down, the notification is successful and the Event Notification is still in sys.event_notifications. Second attemp removes the Event Notification from sys.event_notifications.

Lubomir|||

Can you check if the event notification infrastructure isn't receving back an error or an end dialog message on the system dialog that delivers the notifications? Look into errorlog or in Event Viewer (Application), see if you find a record similar to one of these:

Event notification conversation on dialog handle '{4B086B77-C691-DA11-AD12-00030D303B72}' closed without an error.

or:

Closed event notification conversation endpoint with handle '{B1663957-C791-DA11-AD12-00030D303B72}', due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8494</Code><Description>You do not have permission to access the service &apos;s&apos;.</Description></Error>'.

HTH,
~ Remus

|||Thank you Remus,

Yup it was the End Dialog message I had; Things work great now.

Lubomir

Poison Message Sample in BOL

Hi,

We've been trying to diagnose a problem with service broker's poison message detection and tried to run the sample script in BOL to see how it solved the problem. It appears the example doesn't seem to work correctly,

IF (@.messageTypeName =
'//Adventure-Works.com/AccountsPayable/ExpenseReport')
BEGIN
DECLARE @.expenseReport NVARCHAR(MAX) ;
SET @.expenseReport = CAST(@.messageBody AS NVARCHAR(MAX)) ;
EXEC AdventureWorks.dbo.AddExpenseReport
@.report = @.expenseReport ;
IF @.@.ERROR <> 0
BEGIN
/* THIS ROLLBACK INCREMENTS THE FAILURE COUNT */
ROLLBACK TRANSACTION UndoReceive ;
EXEC TrackMessage @.conversationHandle ;
END ;
ELSE
BEGIN
EXEC AdventureWorks.dbo.ClearMessageTracking
@.conversationHandle ;
END ;
END ;
ELSE

In the scenario where AddExpenseReport fails it will retry the message the next time round the loop - TrackMessage looks to see how many times the message has failed:

IF @.count > 20
/* @.count WILL NEVER GET ABOVE 5 */
BEGIN
EXEC dbo.ClearMessageTracking @.conversationHandle ;
END CONVERSATION @.conversationHandle
WITH ERROR = 500
DESCRIPTION = 'Unable to process message.' ;
END ;
ELSE

But the counter will not get to 20 as after 5 the queue will be disabled which causes ProcessExpenseReport to fail. Subsequent message cannot then be processed until the queue is re-enabled.

We have changed the sample so that once the message has failed four times it is then written to a dead message table and we can then call commit so that the queue can carry on without being disabled:

[in ProcessExpenseReport]
...
EXEC AdventureWorks.dbo.AddExpenseReport @.report = @.expenseReport ;
IF @.@.error <> 0
BEGIN
-- The message has failed, we need to work out if we are safe to
-- retry or not. If this message has failed four times already
-- we need to recieve it, save it into another table and commit to
-- avoid service broker from disabling the queue. Otherwise we can
-- rollback and try again to see if it works next time.

-- Find out how many times this message has failed
declare @.failCount int
exec GetFailCount @.conversationHandle, @.failCount output
-- If we are about to reach the threshold
if @.failCount >= 4
begin
-- Insert the message into the dead message table. A sql agent
-- job will at some point in the future put the contents of this
-- table back onto the queue for reprocessing
EXEC DeadMessage @.conversationHandle, @.expenseReport
end
else
begin
-- We are safe to rollback and take another run at it
ROLLBACK TRANSACTION UndoReceive ;
EXEC TrackMessage @.conversationHandle ;
end
END ;
ELSE
BEGIN
-- Call to SP worked we can clear down our message tracking
EXEC ClearMessageTracking @.conversationHandle ;
END ;
END ;
...
/* Procedure to find out how many times this message
has failed to be processed */
CREATE PROCEDURE GetFailCount
@.conversationHandle uniqueidentifier,
@.failCount int output as
begin
SELECT @.failCount = [count] FROM ExpenseServiceFailedMessages
WHERE conversation_handle = @.conversationHandle
end
go

/* Procedure to insert the message into the Dead message
table and end the conversation */
create procedure DeadMessage
@.conversation_handle uniqueidentifier,
@.message_body varchar(max)
as
begin
insert into ExpenseServiceDeadMessages ( message_body ) values ( @.message_body )

EXEC dbo.ClearMessageTracking @.conversation_Handle ;
END CONVERSATION @.conversation_Handle
WITH ERROR = 500
DESCRIPTION = 'Unable to process message.' ;

end
go

/* Updated version of TrackMessage which no longer checks the
fail count - it just increments each time */
CREATE PROCEDURE TrackMessage
@.conversationHandle uniqueidentifier
AS
BEGIN
IF @.conversationHandle IS NULL
RETURN ;

DECLARE @.count INT ;
SET @.count = NULL ;
SET @.count = (SELECT count FROM dbo.ExpenseServiceFailedMessages
WHERE conversation_handle = @.conversationHandle) ;

/* TrackMessage no longer checks the failure count */
IF @.count IS NULL
BEGIN
INSERT INTO dbo.ExpenseServiceFailedMessages
(count, conversation_handle)
VALUES (1, @.conversationHandle) ;
END ;
ELSE
BEGIN
UPDATE dbo.ExpenseServiceFailedMessages
SET count=count+1
WHERE conversation_handle = @.conversationHandle ;
END ;
END ;
GO

/* New table to hold the Dead messages */
CREATE TABLE ExpenseServiceDeadMessages (
message_body varchar(max)
) ;


Now this solves the problem. If a message arrives that can't be processed (either becuase the message itself is bad, or because of an system fault with another system that we interact with) the message is moved to the DeadMessage table and the queue carries on and does not get disabled.

In our application however none of this code should be required, as long as we are confident that the data in the messages are "valid". In our case the only reason the processing would fail is if an external system we are dependant fails for some reason (e.g. Maybe writing the message to disk and the disk is full ). In this scenario we want to retry the message indefinately until the error clears. If we could disable the poision message detection this would solve the problem and remove the need for this solution.

Any thoughts on this? Have we missed the point somewhere....

Cheers,

Neil.

The main problem with just disabling the poison message check is that when you roll back a message, it's still at the top of the queue so nothing else gets processed until the message succeeds. You're probably better off commiting the receive and re-sending the meaasge - maybe to some kind of retry queue - so that you don't block other messages on the queue. Keep in mind that if you have multiple messages on the same dialog you'll have to be careful how you handel this so you don't end up changing the message order. I've seen a couple examples of people who use conversation timers to implement a timed retry for cases like this when there is a possibility of the remote resource being unavailable for an extended time.|||

I'd like to add that when a queue is being disabled (e.g. because of a poison message), a BROKER_QUEUE_DISABLED event notification is fired. Here is a small demo to ilustrate this:

use tempdb;
go

create procedure sp_rollback_on_pm
as
begin
set nocount on;
begin transaction;
receive * from q_pm;
rollback;
end;
go

create queue q_pm
with activation (
status = on,
max_queue_readers = 1,
procedure_name = [sp_rollback_on_pm],
execute as owner);
go

create service s_demo_pm on queue q_pm ([DEFAULT]);
go

create queue q_en;
create service s_en on queue q_en ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
go

create event notification ev_qm on queue q_pm for broker_queue_disabled to service 's_en', 'current database';
go

declare @.dh uniqueidentifier;
begin dialog conversation @.dh
from service s_demo_pm
to service 's_demo_pm'
with encryption = off;
send on conversation @.dh;
send on conversation @.dh;
send on conversation @.dh;
send on conversation @.dh;
send on conversation @.dh;
send on conversation @.dh;
go

select cast(message_body as xml),* from q_en
go

An administrator can receive notifications when queues are being disabled as a result of a poison message and it can inspect the queue and fix the issue or remove the problem dialog. Or an activated procedure on the q_en queue can automatically fix the issue and re-enable the q_pm queue (of course, assuming that the issue can be fixed automatically).

HTH,
~ Remus


|||Thanks for the replies - this has allowed us to progress to a past this problem.

I do entirely understand why service broker is doing what it's doing, it will quickly enable you to locate any messages that break the system doe to an application bug. However in the scenario where a message cannot be processed due to a situation beyond your control this features requires you to implement a lot more code then perhaps you should.

In our scenario, if we cannot process message A which is at the front of the queue, we are not going to be able to process message B either, so there's no point trying. The ability to turn of poision message detection would be very useful.

I imagine this has been discussed elsewhere but it also strikes me as odd that I can now write code:

-- About to start a transaction
BEGIN TRANSACTION
RECEIVE * FROM myQueue
ROLLBACK TRANSACTION
-- The database is now in a different state from when begin tran was called!!

I personally have always relied on the premise that EVERYTHING I DO (with SQL) inside a transaction is undone when I call rollback. This no longer seems to be true?

Cheers,

Neil|||

Everything you did in the transaction is undone when the transaction is roled back. The message is back in the queueand any other data updates are reversed. The queue is disabled as a result of the rollback, after the rollback occured.

In the case you describe, since the application is able to detect the problem message, I wouldn't actually call it a poison message. Is more like a message your app understands but is not willing to process. It seems to me that a more appropiate action would be to move the message into a different queue (i.e. forward the message to a secondary service) or end the dialog with an error.
The poison message was indeed thought more for cases when the application crashes when processing the message and the system spins on this message, w/o making any progress.

|||

I can see Neil's issue with the poison message handling, and the inability to turn it off. I guess he's got a queue whose processing ultimately depends on some external system (say a web service call or something). In the scenario where the web service is unavailable, you'd pull a message from the queue and try to call the web service. If the web service fails (for example, due to network issues), then you want to rollback the receive to enable that message to be retried in the future. It's not really possible for an application to know whether a network-based call is going to work in advance.

Moving the message of to another queue merely delays the inevitable - the other queue needs processing, and of course it can fail in the same way. I guess you could just take messages from the second queue and add them back to the first, but this seems like a poor solution (it also causes the messages to be reordered, which could have its own issues).

Much as I understand (and applaud) the reasoning behind poison message detection, it does seem that there are scenarios where it would be useful to just turn it off.

|||For the scenario you describe I'd recommend using conversation timers, not rollback the message. Rolling back would make the message visible again immeadetly, triggering another attempt. Instead, using a timer would allow for a delayed retry, like this:

begin transaction
Receive
if web request message
store the request data in table
else if timer message
read request data from table
begin conversation timer (e.g. 1 minute)
commit
do the web request
if succeeded
begin transaction
reset the conversation timer
send back response
commit

Ultimately, everything in the server is optimized for the commit case. Rollbacks should really be extraordinarily and this is how we approached the problem.

As a last resort, the BROKER_QUEUE_DISABLED event notification can be used to enable back the queue automatically.

HTH,
~ Remussql

Tuesday, March 20, 2012

Plug-in algorithm documentation and sample code

I've read in at least one book (and heard firsthand at the SQL Server launch event) that there's a plug-in framework for adding new algorithms to Data Mining. However, the book was apparently written against a Beta version, and the location it suggests for the plug-in sample code doesn't exist in the shipping version. I haven't been able to locate any documentation about the plug-in framework in the SQL Server Books Online, and I haven't been able to find any sample code, either.

Can someone give me a few pointers?Did you explicitly install the SQL Server Samples? You have to choose advanced options during setup to get them - they are not installed by default. If not, go to add/remove programs to run the installer again to add the samples to your installation.|||

You can also find the plug-in algorithm tutorial (with complete source code for a sample) here: http://www.sqlserverdatamining.com/DMCommunity/Tutorials/Links_LinkRedirector.aspx?id=62

The plug-in algorithm whitepaper is available here:
http://www.sqlserverdatamining.com/DMCommunity/Whitepapers+and+Articles/Whitepapers/Links_LinkRedirector.aspx?id=64

Raman Iyer
SQL Server Data Mining

|||Yes, I did install the samples. In fact, I tried on two different installations of SQL Server -- one in which I'd installed the samples with the original installation, and one in which I installed them after-the-fact. In neither case could I find any of the plug-in samples.|||Thank you very much!|||

Hi,

This site is very good but I need to ask aquistion.

Can I use the C# language instead of VC++ in order to create the new mining algorithm.

Is there any example?

Thanks in advance for any help,

Aya.

|||We are working on a framework/SDK for developing plug-in algorithms using managed code. We hope to make it available for download, along with sample code, in the next month or so.

Plug-in algorithm documentation and sample code

I've read in at least one book (and heard firsthand at the SQL Server launch event) that there's a plug-in framework for adding new algorithms to Data Mining. However, the book was apparently written against a Beta version, and the location it suggests for the plug-in sample code doesn't exist in the shipping version. I haven't been able to locate any documentation about the plug-in framework in the SQL Server Books Online, and I haven't been able to find any sample code, either.

Can someone give me a few pointers?Did you explicitly install the SQL Server Samples? You have to choose advanced options during setup to get them - they are not installed by default. If not, go to add/remove programs to run the installer again to add the samples to your installation.|||

You can also find the plug-in algorithm tutorial (with complete source code for a sample) here: http://www.sqlserverdatamining.com/DMCommunity/Tutorials/Links_LinkRedirector.aspx?id=62

The plug-in algorithm whitepaper is available here:
http://www.sqlserverdatamining.com/DMCommunity/Whitepapers+and+Articles/Whitepapers/Links_LinkRedirector.aspx?id=64

Raman Iyer
SQL Server Data Mining

|||Yes, I did install the samples. In fact, I tried on two different installations of SQL Server -- one in which I'd installed the samples with the original installation, and one in which I installed them after-the-fact. In neither case could I find any of the plug-in samples.|||Thank you very much!|||

Hi,

This site is very good but I need to ask aquistion.

Can I use the C# language instead of VC++ in order to create the new mining algorithm.

Is there any example?

Thanks in advance for any help,

Aya.

|||We are working on a framework/SDK for developing plug-in algorithms using managed code. We hope to make it available for download, along with sample code, in the next month or so.