I have a cursor in my SQL 2008R2 database. This cursor takes a list of data, parses each row the data, and then runs the parsed data row through a stored procedure.
DECLARE ExecsDataCursor CURSOR FAST_FORWARD FOR
SELECT TOP (#GuessListSize)
ExecutiveId,
CompanyExecutiveId,
Email,
CompanyId,
#EmailPatternID EmailPatternID,
ExecNameForSorting
FROM
CompanyExecutive
WHERE
CurrentlyWithCompany = 1
AND
Email IS NULL
AND
CompanyExecutiveId NOT IN
(
SELECT CompanyExecutiveId
FROM ExecsData_ExecutiveCandidates
WHERE EmailPatternID = #EmailPatternID
)
ORDER BY
CompanyExecutiveId
OPEN ExecsDataCursor
DECLARE
#ExecutiveId INT,
#CompanyExecutiveId INT,
#Email NVARCHAR(255),
#CompanyId INT,
#EmailPatternID_ForCursor TINYINT,
#ExecName NVARCHAR(255)
FETCH NEXT FROM ExecsDataCursor
INTO
#ExecutiveId ,
#CompanyExecutiveId ,
#Email ,
#CompanyId ,
#EmailPatternID_ForCursor,
#ExecName
DECLARE
#FirstName NVARCHAR(50) = '',
#MiddleName NVARCHAR(50) = '',
#LastName NVARCHAR(50) = '',
#ExampleEmail NVARCHAR(255),
#Domain NVARCHAR(50) = ''
WHILE ##FETCH_STATUS = 0
BEGIN
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) = 1
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
END
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) = 2
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
SELECT #LastName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 2
END
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) >= 3
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
SELECT #MiddleName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 2
SELECT #LastName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = (SELECT MAX(id) FROM dbo.splitString(#ExecName,' '))
END
SELECT #ExampleEmail = MAX(Email) FROM CompanyExecutive WHERE Email IS NOT NULL AND CompanyId = #CompanyId
SELECT #Domain = SUBSTRING(#ExampleEmail, CHARINDEX('#', #ExampleEmail), LEN(#ExampleEmail))
IF #EmailPatternID = 1 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamelname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 2 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamedotlname#domain_DataMe #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 3 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitiallname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 4 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitialdotlname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 5 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitial_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 6 BEGIN BEGIN TRY EXEC ExecsData_Guess_fname_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 7 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamelinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 8 BEGIN BEGIN TRY EXEC ExecsData_Guess_lnamefinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 9 BEGIN BEGIN TRY EXEC ExecsData_Guess_fname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 10 BEGIN BEGIN TRY EXEC ExecsData_Guess_lname_fname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 11 BEGIN BEGIN TRY EXEC ExecsData_Guess_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 12 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitiallinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
IF #EmailPatternID = 13 BEGIN BEGIN TRY EXEC ExecsData_Guess_lnamedotfname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID END TRY BEGIN CATCH END CATCH END
FETCH NEXT FROM ExecsDataCursor
INTO
#ExecutiveId ,
#CompanyExecutiveId ,
#Email ,
#CompanyId ,
#EmailPatternID_ForCursor,
#ExecName
END
CLOSE ExecsDataCursor
DEALLOCATE ExecsDataCursor
This works very well, at least by my expectations. The cursor processes 8000 rows in about 19 seconds. The 8000 rows are specified by user input, which feeds to the #GuessListSize parameter. However, the parsed data is not always processed correctly, which is to be expected. Hence, the try-catch code. We don't really need the cursor to do anything with the failed stored procedure executions. We started tracking them in a separate table so we can figure out how to better process these data points in the future.
The issue with this is that the cursor now will not return a full list. We are hoping for a list of a size specified by the user. So the cursor pulls the list of the specified size, as it should, but some of those entries may not be properly processed by the stored procedures, returning a list less than the specified size.
So the next thing I tried was a WHILE loop. Now, the WHILE loop worked fine. It returned all of the rows the user requested. However, it took nearly 30 minutes to run through the same size of data set. This is obviously unacceptable.
DECLARE
#ExecutiveId INT,
#CompanyExecutiveId INT,
#Email NVARCHAR(255),
#CompanyId INT,
#EmailPatternID_ForCursor TINYINT,
#ExecName NVARCHAR(255)
DECLARE
#FirstName NVARCHAR(50) = '',
#MiddleName NVARCHAR(50) = '',
#LastName NVARCHAR(50) = '',
#ExampleEmail NVARCHAR(255),
#Domain NVARCHAR(50) = '',
#Counter SMALLINT = 0
--WHILE ##FETCH_STATUS = 0
WHILE #Counter < #GuessListSize
BEGIN
SELECT #CompanyExecutiveId =
MIN(CompanyExecutiveID)
FROM CompanyExecutive
WHERE CurrentlyWithCompany = 1 AND Email IS NULL
AND
CompanyExecutiveId NOT IN
(SELECT CompanyExecutiveId FROM ExecsData_ExecutiveCandidates WHERE EmailPatternID = #EmailPatternID)
AND
CompanyExecutiveID NOT IN
(SELECT CompanyExecutiveId FROM ExecsData_Errors)
SELECT
#ExecutiveId = ExecutiveId,
#Email = Email,
#CompanyId = CompanyId,
#EmailPatternID_ForCursor = #EmailPatternID,
#ExecName = ExecNameForSorting
FROM
CompanyExecutive
WHERE
CompanyExecutiveId = #CompanyExecutiveId
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) = 1
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
END
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) = 2
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
SELECT #LastName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 2
END
IF (SELECT COUNT(*) FROM dbo.splitString(#ExecName,' ')) >= 3
BEGIN
SELECT #FirstName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 1
SELECT #MiddleName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = 2
SELECT #LastName = Data FROM dbo.splitString(#ExecName,' ') WHERE id = (SELECT MAX(id) FROM dbo.splitString(#ExecName,' '))
END
SELECT #ExampleEmail = MAX(Email) FROM CompanyExecutive WHERE Email IS NOT NULL AND CompanyId = #CompanyId
SELECT #Domain = SUBSTRING(#ExampleEmail, CHARINDEX('#', #ExampleEmail), LEN(#ExampleEmail))
IF #EmailPatternID = 1 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamelname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 2 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamedotlname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 3 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitiallname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 4 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitialdotlname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 5 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitial_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 6 BEGIN BEGIN TRY EXEC ExecsData_Guess_fname_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 7 BEGIN BEGIN TRY EXEC ExecsData_Guess_fnamelinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 8 BEGIN BEGIN TRY EXEC ExecsData_Guess_lnamefinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 9 BEGIN BEGIN TRY EXEC ExecsData_Guess_fname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 10 BEGIN BEGIN TRY EXEC ExecsData_Guess_lname_fname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 11 BEGIN BEGIN TRY EXEC ExecsData_Guess_lname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 12 BEGIN BEGIN TRY EXEC ExecsData_Guess_finitiallinitial#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
IF #EmailPatternID = 13 BEGIN BEGIN TRY EXEC ExecsData_Guess_lnamedotfname#domain_DataME #ExecutiveID ,#CompanyExecutiveID ,#FirstName,#MiddleName ,#LastName ,#Domain ,#CompanyID; SET #Counter = #Counter + 1; END TRY BEGIN CATCH INSERT INTO ExecsData_Errors (CompanyExecutiveID,EmailPatternID) VALUES (#CompanyExecutiveId,#EmailPatternID) END CATCH END
END
Because the Cursor is designed to chew through a pre-defined list of data, I'm not sure how to make said cursor "dynamic" and return the user-specified number of results regardless of errors. The WHILE loop has the "counter" only increase if the SP executes without hitting the CATCH block, but I don't know how to integrate that into the Cursor, or if I even can.
Is there something obvious I'm missing here?
(As requested, the INSERT SQL SPs)
ALTER PROCEDURE [dbo].[ExecsData_Guess_fname#domain_DataMe]
(
#ExecutiveID int,
#CompanyExecutiveID int,
#FirstName nvarchar(50),
#MiddleName nvarchar(50),
#LastName nvarchar(50),
#DomainName nvarchar(255),
#CompanyID int
)
AS
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE #GUESS nvarchar(255)
DECLARE #FirstInitial nvarchar(1)
DECLARE #MiddleInitial nvarchar(1)
DECLARE #LastInitial nvarchar(1)
set #FirstInitial = SUBSTRING(#FirstName, 1, 1)
set #MiddleInitial = SUBSTRING(#MiddleName, 1, 1)
set #LastInitial = SUBSTRING(#LastName, 1, 1)
--*****Example******
--FirstName = Andy,
--Middle Name = Xanadu,
--Last Name = Farag,
--Domain = #umphreys.com
--******************
--ex. andy#umphreys.com
set #GUESS = LTRIM(#FirstName)+ #DomainName
EXEC ExecsData_InsertEmailGuessByExec_DataMe
#ExecutiveID,
#CompanyExecutiveID,
#GUESS,
#CompanyID,
9
RETURN (##ERROR)
ALTER PROCEDURE [dbo].[ExecsData_InsertEmailGuessByExec_DataMe]
(
#ExecutiveID int,
#CompanyExecutiveID int,
#EmailAddress nvarchar(50),
#CompanyID int,
#EmailPatternID tinyint
)
AS
BEGIN
INSERT ExecsData_ExecutiveCandidates
(
ExecutiveID,
CompanyExecutiveID,
EmailAddress,
CompanyID,
EmailPatternID,
GuessTimestamp
)
VALUES
(
#ExecutiveID,
#CompanyExecutiveID,
#EmailAddress,
#CompanyID,
#EmailPatternID,
CURRENT_TIMESTAMP
)
END
Per RBarryYoung's suggestion, I opted to look at the actual SP inserting process a bit. One of the problems I found was that many of our Execs were not pulling a domain to concatenate into an email address. Expanding the areas where the script could look for domain information has improved the process to near-user-specified completion. While it's still not perfect, it's a step in the right direction.
As for finding ways to dynamically force a cursor to pull a specific number of rows, I'm thinking I might nest this particular SP in a second SP that uses the WHILE loop. So basically, while the list size is less than the user-specified list size, it will re-execute the insertion SP. That might work. If it works, I'll re-edit and post this as the solution.
As suggested in the last paragraph of the post, I ended up using nested SPs. The outermost SP runs a WHILE loop that keeps track of however many execs were requested. It then runs the generation SP with the specified number of execs. If the returned number is less than the requested number, it remains in the WHILE loop.
The errors in the generation SP are being logged to be reviewed by our data team.
Sup guys, is it possible for INSERT or UPDATE to throw an exception that would stop the procedure? I'm in a little bit of a pickle, because i've hanging transactions in what seems to be a bullet proof code.
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
SET #nError = ##ERROR;
IF (1 = #nChangeMassprintTaskStatus) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState) AND (0 = #nError)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
SET #nError = ##ERROR;
END
IF 0 = #nError
BEGIN
COMMIT TRANSACTION;
END
ELSE
BEGIN
ROLLBACK TRANSACTION;
END
Halp, anyone?
Without TRY/CATCH, this is not bullet proof.
Errors can be batch aborting (eg datatype conversions or errors thrown from triggers) which means ROLLBACK does not run.
You have to use TRY/CATCH and I always use SET XACT_ABORT ON too
SET XACT_ABORT, NOCOUNT ON;
BEGIN TRY
BEGIN TRANSACTION;
SET #sSystemLogDataId = CONVERT(NCHAR(36), NEWID());
INSERT INTO crddata.crd_systemlogdata (systemdataid,systemlogid,userid,
actiondatetime,actionstate)
VALUES(#sSystemLogDataId,#inSystemLogId,#sUserId,GETDATE(),#nActionState);
IF (1 = #nChangeMassprintTaskStatus)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttaskstatus=#nMassprintTaskStatus
WHERE massprinttaskid = #inMassprintTaskId;
END
IF (#MassprintTaskType <> 1) AND (27 = #nActionState)
BEGIN
UPDATE crddata.crd_massprinttasks SET massprinttasktype=1
WHERE massprinttaskid = #inMassprintTaskId;
END
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 --may already be rolled back by SET XACT_ABORT or a trigger
ROLLBACK TRANSACTION;
RAISERROR [rethrow caught error using ERROR_NUMBER(), ERROR_MESSAGE(), etc]
END CATCH
Mandatory background reading is Erland Sommarskog's "Error Handling in SQL 2005 and Later": we'll test you on it later...
Create a trigger that will raise an exception if insert/update is not correct
example:
create table t (id int)
go
create trigger tr on t
for insert
as
if exists(select 1 from inserted where id = 0)
raiserror('id is not valid', 16, 1)
go
insert t select 1
select ##error
insert t select 0
select ##error