Document: DS-00007-KL

SQL Error Handling

-- Jeffrey R. Schenk, MCDBA, MCSE, MCSA

-- This article is formatted as Transact-SQL code -
-- ready to be pasted right into Query Analyzer,
-- for example, comments and all.

---------------------------------------------------------------------
-- Before We Begin: Soapboxing On the Importance of Error Handling --
---------------------------------------------------------------------

-- S1. Why we need to trap errors in code
-- S2. Why it's impractical to implement error-checking later
-- S3. The pitfalls of indifference toward error-checking
-- S4. SQL Server doesn't care if your code is readable...

----------------------------------
-- What This Session Will Cover --
----------------------------------

-- 1. Why the @@ERROR global variable is our friend
-- 2. How to RETURN an error success (or failure) code
-- 3. How to OUTPUT non-integer error/decision data
-- 4. When to SELECT error data into resultsets
-- 5. Using RAISERROR to create some errors that ain't
-- 6. How to bit-masquerade multiple errors into a single integer
-- 7. Hunting down pesky errors in dynamically-constructed SQL
-- 8. Errors in ancient command-line stuff (here in real world)

-----------------------------------
-- What This Session Won't Cover --
-----------------------------------

-- 1. Any error-trapping outside the TRANSACT SQL Environment
-- A. DAO, RDO, ADO, ODBC, OLEDB, etc.

-- 2. Regurgitated Sql Server Online Books stuff.
-- 3. The litany of known errors in an otherwise wonderful product.
-- 4. The latest news on anything... I can't keep up with the old news!

----------------------------------------------------
-- Soapbox 1. Why we need to trap errors in code --
----------------------------------------------------

-- A. If you care about whether or not a thing worked,
-- you need to ask the question: did it work?

-- B. If you don't care whether or not a thing worked,
-- you needn't ask the question.

-- C. Most branches in code involve some form of the
-- question "Did things work as expected?"

--------------------------------------------------------------------------
-- Soapbox 2. Why it's impractical to implement error-checking "later" --
--------------------------------------------------------------------------

-- A. We'd freak if we found out that a commercial flight
-- planned to get off the ground first (they have a
-- deadline to meet), then deal with the pre-flight
-- checks later. Think of the liability... but we do
-- it with the code that runs billion-dollar enterprises
-- all the time. Go figure?

-- B. Code branches based on the "Did it work?" question
-- are an integral part of your code. Most code is
-- 20% doing the thing, and 80% figuring out how to
-- handle the mess gracefully when the answer is,
-- "Nope, it didn't work".

-- C. There'll be another crisis tomorrow that'll pull
-- you off your plans for later... I've heard tell
-- of an environment where there's lots of time to
-- fix things later, but I've never seen it.

--------------------------------------------------------------------
-- Soapbox 3. The pitfalls of indifference toward error-checking --
--------------------------------------------------------------------

-- A. Fault-intolerant (high maintenance) processes

-- B. Propagation of garbage code: You know those little
-- code snippets we all use... (the ones that spare us
-- from remembering how to do what we once did, the
-- ones that get cut-and-pasted from project to project)

-- C. Destruction of your reputation, the product's reputation,
-- your organization's reputation, etc.

-- D. Do you carry a beeper? Love spending your weekends
-- defending yourself and deflecting blame?

----------------------------------------------------------------------
-- Soapbox 4. SQL Server doesn't care if your code is readable... --
----------------------------------------------------------------------

-- A. I do.

-- B. While a little style, structure, and convention go
-- a long way toward making your code more palatable
-- to the rest of us... their importance transcends the
-- readability issue... in that some styles (especially
-- nesting) lend themselves to error-trapping.

-- C. Being a horse of habit, I find that sticking with
-- a set of conventions keeps me a better error-checker.

-- For example: I always try to build my code so
-- that I handle success conditions first, and
-- error conditions last. Technically, it doesn't
-- make a bit of difference, but it's easy to follow:

-----------------------------------------------------

IF (@@ERROR = 0) ------------------
BEGIN -- You Get What You Need --
---------------------------

PRINT 'I saw her today at the reception'

END ----------------------------------------
ELSE -- You can't always get what you want --
BEGIN ----------------------------------------

PRINT 'She was practiced in the art of deception'
END

--------------------------------------------------------


-------------------------------------------------------
-- 1. Why the @@ERROR global variable is our friend --
-------------------------------------------------------

-- A. The @@ERROR variable returns a success or failure
-- code for the most-recently executed SQL Statement.

-- B. Hint: It doesn't return the success or failure of
-- the last twenty statements, just the last one.

-- C. If you want to know whether or not something worked,
-- you need to check it immediately following the
-- execution of the statement, or:

-- D. You can capture the @@ERROR value now, but wait
-- to evaluate it until you're further along in code.

-- EXAMPLE:

--------------------------------
-- Simplistic Error Detection --
--------------------------------

SELECT DivideByZero = 1/0

-- Uncomment the following line to demo hiding errors
--SELECT TheGrandIllusion = 'Or How to Hide Your Mistakes'

IF (@@ERROR = 0) ------------------------------
BEGIN -- Here We Stand at the Great Divide --
---------------------------------------

PRINT 'Everything is wonderful, just wonderful'

END --------------------------
ELSE -- It Ain't in Colorado --
BEGIN --------------------------

PRINT 'Hey, Pilgrim, whatdaya think your doing?'
END

-------------------------------------------------------
-- Catch that Pesky Error Now, Deal With It Later :) --
-------------------------------------------------------

DECLARE @intStrikeOuts integer
SELECT @intStrikeOuts = 0 --Initialize before bitmasking

SELECT BatterBatterSwing = 'Flynn lets drive a single'
SELECT @intStrikeOuts = @@ERROR

SELECT BatterBatterSwing = 'Cooney dies at first '
SELECT 1/0 --Force an error here
SELECT @intStrikeOuts = @intStrikeOuts | @@ERROR

SELECT BatterBatterSwing = 'Johnnie safe at second'
SELECT @intStrikeOuts = @intStrikeOuts | @@ERROR

SELECT BatterBatterSwing = 'and Flynn a-hugging third'
SELECT @intStrikeOuts = @intStrikeOuts | @@ERROR

IF (@intStrikeOuts = 0) ----------------
BEGIN -- Somewhere Men are Laughing --
--------------------------------

PRINT 'Somewhere children shout!'

END ---------------------------------
ELSE -- There Is No Joy In Mudville --
BEGIN ---------------------------------

PRINT 'Mighty Casey has struck out!'
END

--------------------------------------------------------------
-- The Trick to Capturing the @@ERROR Global Variable Along --
-- With Other Global Variables (the @@ROWCOUNT for example) --
--------------------------------------------------------------

DECLARE @intRowCount integer
DECLARE @intErrorCode integer

SELECT
Song_ID, -- / 0, -- Force a divide by zero error
ArtistName,
SongTitle
FROM
Corporate.vwx_SongList
WHERE
SongTitle LIKE '%sunshine%'
ORDER BY
ArtistName

--The following lines will be uncommented for demo purposes
--SELECT @intRowCount = @@ROWCOUNT -- Wrong approach, clears error
--SELECT @intErrorCode = @@ERROR -- Wrong, again... clears rowcount
--SELECT @intRowCount = @@ROWCOUNT, @intErrorCode = @@ERROR -- It's a good thing.

IF ((@intErrorCode = 0) AND (@intRowCount > 0)) ----
BEGIN -- I'm The Happiest Girl In The Whole USA --
--------------------------------------------

PRINT 'Skip with me Sunshine'

END -----------------------------------
ELSE -- This is How the World Will Be --
BEGIN -----------------------------------

PRINT 'Everywhere I go, It Rains On Me'
END

------------------------------------------------------------
-- A word about what the global error variable won't trap --
-- (if you're running an older version of SQL Server) --
------------------------------------------------------------

EXEC ('
--SELECT 1/0 -- Force Error; demo that it is not trapped
SELECT SongTitle
FROM Songs
WHERE SongTitle LIKE ''%Tennessee%''
ORDER BY SongTitle DESC
')

IF (@@ERROR = 0) -------------------------------------
BEGIN -- Joy to the World, All the Boys and Girls --
----------------------------------------------

PRINT 'Joy to the fish-ies in the deep blue sea'

END ---------------------------------
ELSE -- I Ain't Never Been To Spain --
BEGIN ---------------------------------

PRINT 'But I''ve been to Oklahoma!'
END

----------------------------------------------------------
-- 2. How to RETURN an error success (or failure) code --
----------------------------------------------------------

-- A. Providing a RETURN Code is easy... it's providing
-- one that's useful that's the bigger challenge.

-- B. This section will focus on creating a return code,
-- and on how to read it on the calling end.

-- C. Building Cool RETURN codes will covered later in
-- in this document.

-- D. Return Codes are a handy for passing back specific
-- data on how well a procedure worked. This data can
-- be returned to another procedure, or an application.


--DROP PROCEDURE Corporate.usp_ReturnStuff

CREATE PROCEDURE usp_ReturnStuff

@strSongTitle varchar(100) = NULL
AS
SELECT *
FROM vwx_SongList
WHERE SongTitle LIKE '%' + @strSongTitle + '%'
ORDER BY SongTitle

IF ((@@ERROR = 0) AND ( @@ROWCOUNT > 0))
BEGIN
RETURN(0) --All's well that ends well.

END -----------------------------
ELSE -- No Song Found, or worse --
BEGIN -----------------------------

RETURN(1) --Things went wrong, bad wrong.
END
GO
---------------------------------------------------------
-- How to Snag the RETURN Code from a Called Procedure --
---------------------------------------------------------

DECLARE @intReturnStatus integer

EXEC Songs.Corporate.usp_ReturnStuff 'Saturday'

--EXEC @intReturnStatus = Songs.Corporate.usp_ReturnStuff 'Wednesday'

IF ((@@ERROR = 0) AND (@intReturnStatus = 0)) --
BEGIN -- I've Got a Peaceful, Easy Feeling ---
----------------------------------------

SELECT Goody = @intReturnStatus

END -----------------------------------
ELSE -- Did I Shave My Legs For This? --
BEGIN -----------------------------------

SELECT Arrrgh = @intReturnStatus
END

-------------------------------------------------------
-- 3. How to OUTPUT non-integer error/decision data --
-------------------------------------------------------

--DROP PROCEDURE Corporate.usp_OutputStuff

CREATE PROCEDURE Corporate.usp_OutputStuff

@strSongTitle varchar (100) = '%',
@datLastModified smalldatetime = NULL OUTPUT
AS

SELECT
@datLastModified = MAX(LastModified)
FROM
Corporate.Songs
WHERE
(SongTitle LIKE '%' + @strSongTitle + '%')

IF (@@ERROR = 0) RETURN(0) ELSE RETURN(1)

GO
--------------------------------------------------------
-- How to Read An OUTPUT Parameter (you might want to --
-- to evaluate it for some success/failure decision). --
--------------------------------------------------------

DECLARE @intReturnStatus integer
DECLARE @datNewestModDate smalldatetime

EXEC @intReturnStatus = Songs.Corporate.usp_OutputStuff

@strSongTitle = 'Blue Eyes Crying In The Rain',
@datLastModified = @datNewestModDate OUTPUT

SELECT
TheBigDate = @datNewestModDate,
TheStatusMan = @intReturnStatus

---------------------------------------------------
-- 4. When to SELECT error data into resultsets --
---------------------------------------------------

-- When you want to pass status codes to the client you might
-- try inserting a select statement at the close of a stored
-- procedure (that doesn't otherwise return multiple-row
-- results) to offer the status code up as a field of data.

--DROP PROCEDURE Corporate.usp_OfferStatusResults

CREATE PROCEDURE Corporate.usp_OfferStatusResults

AS
DECLARE @intLongestTitle integer
DECLARE @intShortestTitle integer

SELECT
@intLongestTitle = MAX(DATALENGTH(SongTitle)),
@intShortestTitle = MIN(DATALENGTH(SongTitle))
FROM
Corporate.vwx_SongList

IF (@@ERROR = 0) -----------------------------------
BEGIN -- Look What They've Done To My Song, Ma ---
--------------------------------------------

SELECT
LongestTitle = @intLongestTitle,
ShortestTitle = @intShortestTitle,
ReturnStatus = 0

RETURN(0)

END --------------------------------
ELSE -- Ribbon of Darkness Over Me --
BEGIN --------------------------------
SELECT
LongestTitle = NULL,
ShortestTitle = NULL,
ReturnStatus = 1

RETURN(1)
END

GO
---------------------------------
--Simulated Pass-Through Query --
---------------------------------

EXEC Songs.Corporate.usp_OfferStatusResults

-- For Those Who Care: The Longest Song Title Is
-- a five-parter (laid down on a single track) of
-- Rush's 2112 Album:

-- 2112: I: Overture/II: The Temples Of Syrinx/III: Discovery/IV: Presentation/V: Oracle: The Dream...

---------------------------------------------------
-- 5. Using RAISERROR to create some that ain't --
---------------------------------------------------

-- A. The RAISERROR command is often preferable to the print
-- command in that it is more flexible in it's use... And
-- (I think) easier to use once you are familiar with it's
-- basic syntax.

RAISERROR('Some Stuff I Wanna Get Said', -1, -1) --Default Severity, State
RAISERROR('Some Informational Stuff I Wanna Get Said', 10, -1) --Informational
RAISERROR('Some Serious Stuff I Wanna Get Said', 11, -1) --Raise an Error, slight
RAISERROR('Some Critical Stuff I Wanna Get Said', 18, -1) --Raise an Error, severe
RAISERROR('Some Stuff I''m Not Entitled to Get Said', 19, -1) --Raise an Error, drastic

-- B. Permanently creating a (possibly just informational) ERROR Message
-- to call upon later:

EXEC Master.dbo.sp_addmessage
50002, -- Message Number
10, -- Default Severity
'usp_MyProcedure -- Success(%d): %s', -- Message String
NULL, -- Language (Default)
TRUE, -- Log this error
REPLACE -- Replace any existing error message of this number
GO
EXEC Master.dbo.sp_addmessage
50003, -- Message Number
11, -- Default Severity
'usp_MyProcedure -- Failure(%d): %s', -- Message String
NULL, -- Language (Default)
TRUE, -- Log this error
REPLACE -- Replace any existing error message of this number
GO
-----------------------------------------------------------
-- Let's have a look at our newly-created error messages --
-----------------------------------------------------------

SELECT * FROM Master.dbo.sysmessages WHERE error > 50000

--------------------------------------------------------------
-- Now Let's Throw Our Boots on and RAISE some ..ugh ERRORS --
--------------------------------------------------------------

RAISERROR (50002, -1, -1, 0, 'Informational Error Raised By Jeff')
RAISERROR (50003, -1, -1, 64, 'Error Condition Raised By Jeff')
RAISERROR (50003, 10, -1, 64, 'Error Condition treated as information only')

--------------------------------------------------------------------
-- Review the Error Log from Enterprise Manager's Management Node --
--------------------------------------------------------------------

---------------------------------------------------------------------
-- 6. How to bit-masquerade multiple errors into a single integer --
---------------------------------------------------------------------

-- A quick Binary Number System Review

-- 0000000000000000 = 0
-- 0000000000000001 = 1
-- 0000000000000010 = 2
-- 0000000000000100 = 4
-- 0000000000001000 = 8
-- 0000000000010000 = 16
-- 0000000000100000 = 32
-- 0000000001000000 = 64
-- 0000000010000000 = 128
-- 0000000100000000 = 256
-- 0000001000000000 = 512
-- 0000010000000000 = 1024
-- 0000100000000000 = 2048
-- 0001000000000000 = 4096
-- 0010000000000000 = 8192
-- 0100000000000000 = 16384
-- 1000000000000000 = 32768

-- 8 Handy-dandy error flags built into a single byte (tiny integer)
-- 16 Handy-dandy error flags built into a small integer (as illustrated)

-----------------------------------------------------------------------
-- Creating a composite return code that illustrates multiple errors --
---------------------------------------------------------------------

DECLARE @intReturnCode integer
SELECT @intReturnCode = 0 -- initialize it, for later bit masking
-- Some fake error evaluation code follows

-- Fake File Error Detected

SELECT @intReturnCode = @intReturnCode | 1 -- Our routine's file error

-- Fake Insert Error Detected
SELECT @intReturnCode = @intReturnCode | 2 -- Our routine's insert error

-- Fake Insert Error Detected
SELECT @intReturnCode = @intReturnCode | 4 -- Our routine's update error

-- Fake Computational Error Detected
SELECT @intReturnCode = @intReturnCode | 64 -- Our routine's computation error

SELECT FourErrorsInOne = @intReturnCode

---------------------------------------------------------------------
-- Determine whether or not we were returned a computational error --
---------------------------------------------------------------------

IF((@intReturnCode & 64) = 64) --Value of computational component of error
BEGIN
SELECT Error_64_Detected = 'System does not compute, repeat, does not compute!'
END

IF((@intReturnCode & 32) = 32) -- Value of bogus error we want to detect
BEGIN
SELECT Error_32_Detected = 'Bogus error detected in sector B!'
END

------------------------------------------------------------------
-- 7. Hunting down pesky errors in dynamically-constructed SQL --
------------------------------------------------------------------

-- Wrapping your dynamically-create code into a temporary
-- stored procedure often helps pass data back and forth, cleanly.

DECLARE @strExecCommand varchar(255)
DECLARE @strLoginName varchar(30)
DECLARE @intReturnStatus integer

SELECT @strLoginName = 'jXrschenk'

---------------------------------------------------
-- Grant permissions to the designated Sql Login --
---------------------------------------------------

SELECT @strExecCommand

= 'GRANT SELECT, INSERT ON Songs TO '
+ @strLoginName

--------------------------------------------------------
-- Wrap the dynamically-constructed EXEC command in a --
-- temporary stored procedure so we have a chance to --
-- detect errors arising from it's execution. --
--------------------------------------------------------

EXEC (' CREATE PROCEDURE #GrantPermissions AS

DECLARE @intReturnValue integer

' + @strExecCommand + '

IF (@@ERROR = 0)
SELECT @intReturnValue = 0
ELSE
SELECT @intReturnValue = 1

RETURN (@intReturnValue)

') -- END EXEC

EXEC @intReturnStatus = #GrantPermissions

DROP PROCEDURE #GrantPermissions

SELECT GrantStatusCode = @intReturnStatus

------------------------------------------------------------------
-- 8. Errors in ancient command-line stuff (here in real world) --
------------------------------------------------------------------


------------------------------------------------------------------------------
-- Create a temporary table into which we will later EXEC command line data --
------------------------------------------------------------------------------

CREATE TABLE #CommandLineData (Returned_Data varchar(80) NULL)

INSERT #CommandLineData EXEC master..xp_cmdshell 'Dir C:\'

SELECT * FROM #CommandLineData

DROP TABLE #CommandLineData


-- Bringing it all together

sp_addmessage 50290, 10, 'usp_DropIndexes -- Success(%d): %s', NULL, TRUE, REPLACE
GO

sp_addmessage 50291, 10, 'usp_DropIndexes -- Failure(%d): %s', NULL, TRUE, REPLACE
GO

EXEC Songs.Corporate.usp_DropIndexes

@strTableName = 'Songs',
@strSearchIndex = '%banana', -- Search any index name
@intStatusInhibitMask = 6144, -- 6144 = Inhibit constraints
@blnDebugMode = 1



-- I CAN'T BELIEVE I ERROR-CHECKED THE WHOLE THING!

-- IF TIME PERMITS: Review the Drop Indexes Procedure

--Procedure: usp_DropIndexes
--Purpose: This procedure drops selected indexes from the named table

--ReturnStatus Codes:

-- 0 Successful completion
-- 1 Missing required table name
-- 2 Trouble encountered dropping index or constraint

-- Creation, Modification, Maintenance History
-------------- -------------- -------------------------------------------
-- DATE PROGRAMMER DESCRIPTION OF WORK ACCOMPLISHED
-------------- -------------- -------------------------------------------
-- 21-Oct-1999 Jeffrey Schenk Original creation of this procedure


CREATE PROCEDURE usp_DropIndexes

@strTableName varchar(30) = NULL, -- Dont actually let this fly
@strSearchIndex varchar(30) = '%', -- Search any index name
@intStatusInhibitMask integer = 0, -- 6144 = Inhibit constraints
@blnDebugMode bit = 0

AS

---------------------------------
-- Local variable declarations --
---------------------------------

DECLARE @messageText varchar(255)
DECLARE @strExecCommand varchar(255)
DECLARE @blnConstraint bit
DECLARE @intErrorBuffer integer
DECLARE @intReturnValue integer
DECLARE @intReturnStatus integer

DECLARE @intIndexStatus smallint
DECLARE @strIndexName varchar(30)

------------------------------
-- Initialization and Setup --
------------------------------

SELECT @intErrorBuffer = 0
SELECT @intReturnValue = 0
SELECT @messageText = ''


IF (@strTableName IS NOT NULL)
BEGIN

----------------------------------------------------------
-- Create a row cursor to iterating through each record --
----------------------------------------------------------

DECLARE csrSysIndexes CURSOR FOR

SELECT
name, status
FROM
dbo.sysindexes
WHERE
(ID = OBJECT_ID('Corporate.' + @strTableName))
AND (name LIKE @strSearchIndex)
AND ((status & @intStatusInhibitMask) = 0)
AND (indid BETWEEN 1 AND 254) -- Exclude Tables, TextPages

------------------------------------------------
-- Open the cursor for the selected recordset --
------------------------------------------------

OPEN csrSysIndexes

-------------------------------------------------------------
-- Go fetch the first record (to set the fetchstatus flag) --
-------------------------------------------------------------

FETCH NEXT FROM csrSysIndexes INTO @strIndexName, @intIndexStatus

------------------------------------------------------
-- While not stepped beyond the ends of the dataset --
------------------------------------------------------

WHILE (@@FETCH_STATUS <> -1)
BEGIN
---------------------------------------------------------------------
-- This record still a member of the resultset? (not deleted, etc) --
---------------------------------------------------------------------

IF (@@FETCH_STATUS <> -2)
BEGIN
--------------------------------------------------------------
-- Determine whether or not this index has been implemented --
-- as a constraint (2048 or 4096 bits set in status code). --
--------------------------------------------------------------

IF ((@intIndexStatus & 6144) > 0)
BEGIN
SELECT @blnConstraint = 1

SELECT @strExecCommand
= 'ALTER TABLE Corporate.'
+ @strTableName
+ ' DROP CONSTRAINT '
+ @strIndexName

END ---------------------------------------------------------
ELSE -- This index has not been implemented as a constraint --
BEGIN ---------------------------------------------------------

SELECT @blnConstraint = 0

SELECT @strExecCommand
= 'DROP INDEX Corporate.'
+ @strTableName
+ '.'
+ @strIndexName

END

IF(@blnDebugMode = 1)
RAISERROR(@strExecCommand, 10, 1)

----------------------------------------------------------
-- Kick this bird out of the nest, and see if it'll fly --
-- (or go drop the index or constraint now ;) --
----------------------------------------------------------

EXEC (@strExecCommand)

-----------------------------------------------------------------
-- Sometimes they don't fly... so scan the ground for wreckage --
-----------------------------------------------------------------

IF (@@ERROR = 0)
BEGIN
SELECT @messageText = @messageText + CHAR(13) + CHAR(10) + CHAR(9)
+ @strIndexName

END ---------------------------------------------------
ELSE -- Something went wrong, but we're clueless what --
BEGIN ---------------------------------------------------

SELECT @intReturnValue = (@intReturnValue | 2)

SELECT @messageText = @messageText + CHAR(13) + CHAR(10) + CHAR(9)
+ 'Errors: '
+ @strIndexName
END

END -- still within recordset bounds

FETCH NEXT FROM csrSysIndexes INTO @strIndexName, @intIndexStatus
END

CLOSE csrSysIndexes

DEALLOCATE csrSysIndexes

END ----------------------------------------
ELSE -- Errors detecting a valid tablename --
BEGIN ----------------------------------------

SELECT @intReturnValue = (@intReturnValue | 1)

SELECT @messageText = @messageText + CHAR(13) + CHAR(10) + CHAR(9)
+ 'Missing required table name'
END

----------------------------------------------------------------
-- Determine general success or failure of routine as a whole --
----------------------------------------------------------------

IF ((@@ERROR = 0) AND (@intReturnValue = 0))
BEGIN

RAISERROR(50290, -1, -1, @intReturnValue, @messageText)

END ---------------------------------------------------
ELSE -- Something went wrong, so expound a little bit --
BEGIN ---------------------------------------------------

RAISERROR(50291, -1, -1, @intReturnValue, @messageText)
END

RETURN(@intReturnValue)

GO


[Home]
611 Potomac Place • Suite 101 • Smyrna, Tennessee 37167 • 615.223.6789 • ContactUs@DataSchenk.com
Classes • Training • Nashville • © 1998 - 2010 DataSchenk, Inc.  All Rights Reserved 
We Offer • SQL Server Training • Exchange Server Training • C Sharp Training • VB.NET Training • Javascript Training
 • SSAS Training • SSIS Training • SSRS Training • Project Training • SharePoint Training • Access Training • Excel Training

We have Customers in • Florence, AL • Fort Smith, AR • San Bernadino, CA • San Diego, CA • Denver, CO • Stamford, CT
• Pensacola, FL • West Palm Beach, FL • Kennesaw, GA • Lawrenceville, GA • Iowa City, IA • Shenandoah, IA • Chicago, IL
• Campbellsville, KY • Fort Campbell, KY • Georgetown, KY • Golden Pond, KY • Paducah, KY • Princeton, KY • Minden, LA
• Minneapolis, MN • Fort Bragg, NC • Omaha, NE • Keene, NH • Reno, NV • Rochester, NY • Rome, NY • New Concord, OH
• Kennett Square, PA • State College, PA • San German, PR • Fort Mill, SC • Ashland City, TN • Chapel Hill, TN
• Cleveland, TN • Collegedale, TN • Cookeville, TN • Gallatin, TN • Gladeville, TN • Hendersonville, TN • La Vergne, TN
• Lawrenceburg, TN • Shelbyville, TN • Spencer, TN • Tullahoma, TN • Woodbury, TN • San Antonio, TX • Fredericksburg, VA
• Fort Lewis, WA

KILROY - SQL-Error-Handling