Check if DB Exists in a Trigger - sql-server

I have an SQL Trigger that is dependent on a separate database in the same server, but the issue arises when one wants to use the database independently, in which case the second database will usually not be migrated. Meaning that, if a query were to be executed that would activate the Trigger, it will fail due to requiring that second database.
I attempted to circumvent the issue by covering the entire trigger with a script that checks if the database exists, but even when I do include it, it will basically check the entire trigger and will fail anyway. Below is what happened when I inserted a record in an SQL server without the 2nd database.
Msg 2702, Level 16, State 2, Procedure tChange2ndDB, Line 22 [Batch Start Line 0]
Database '2ndDB' does not exist.
Here's what my current (basic) code looks like:
CREATE TRIGGER [dbo].[tChange2ndDB]
ON [dbo].[crelign]
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = '[2ndDB]' OR name = '[2ndDB]')))
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE #insCount INT
DECLARE #delCount INT
DECLARE #Code VARCHAR(5)
DECLARE #CodeUpd VARCHAR(5)
DECLARE #Description VARCHAR(50)
SET #insCount = (SELECT COUNT(*) FROM INSERTED)
SET #delCount = (SELECT COUNT(*) FROM DELETED)
;IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = '[2ndDB]' OR name = '[2ndDB]')))
ALTER TABLE [2ndDB].[dbo].Field DISABLE TRIGGER [tChange1stDB];
-- * Other code here * --
;IF (EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE ('[' + name + ']' = '[2ndDB]' OR name = '[2ndDB]')))
ALTER TABLE [2ndDB].[dbo].Field ENABLE TRIGGER [tChange1stDB];
END TRY
BEGIN CATCH
-- * Error Handling --
DECLARE #ErrMsg NVARCHAR(MAX), #ErrorSeverity INT, #ErrorState INT;
SELECT #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE();
SET #ErrMsg = (SELECT 'TR : tChange2ndDB Line : ' + RTRIM(CONVERT(VARCHAR(MAX), ERROR_LINE())) + ' - ' + ERROR_MESSAGE());
RAISERROR(#ErrMsg, #ErrorSeverity, #ErrorState);
END CATCH
END
END
GO
What is the best solution to overcome this issue?
Thank you for reading.

You can try adding an Else and then use RETURN 0 or '' to dont do your code.

Related

SQL script running with error while using sys.obejcts to pre-check

I want to write a script, its function is to create a stored procedure and before create it the script should have to check if it exists in DB, so I write the procedure like below, If I comment out first 2 lines the scripts will create procedure successfully if the stored procedure not exist, but I add the first two lines, the SSMS will throw error:
IF NOT EXISTS (SELECT * FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[User_Own_Restore_From_Job]') AND type in (N'P'))
CREATE PROCEDURE [dbo].[User_Own_Restore_From_Job]
#dbname varchar(500),
#backuppath varchar (500)
AS
BEGIN
SET NOCOUNT ON
DECLARE #XPConfig int = 1
--To keep the existing value
SELECT #XPConfig = cast(value AS INT) FROM sys.configurations WHERE name LIKE 'xp_cmdshell';
--Enabling xp_cmdshell for getting registry and creating directory structure
BEGIN TRY
EXEC sp_configure 'show advanced options', 1
RECONFIGURE with override
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE with override
END TRY
BEGIN CATCH
--empty
END CATCH
DECLARE #datapath nvarchar(500);
DECLARE #logpath nvarchar(500);
--read data and log path details from registry
EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\Setup', N'SQLDataRoot', #datapath OUTPUT
EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'Software\Microsoft\MSSQLServer\MSSQLServer', N'DefaultLog', #logpath OUTPUT
SET #datapath=#datapath+'\Data';
--creating a database folder if not already exists on log and data drives
DECLARE #datapath2 varchar(500)=''
DECLARE #logpath2 varchar(500)=''
SET #DataPath2 = #datapath+ N'\'+#dbname
SET #LogPath2 = #logpath+ N'\'+#dbname
--reading all folders from DATA directory
DECLARE #DirTree TABLE (subdirectory nvarchar(255), depth INT)
INSERT INTO #DirTree(subdirectory, depth)
EXEC master.sys.xp_dirtree #DataPath
--creating a folder on DATA drive with database name if not already exists
IF NOT EXISTS (SELECT 1 FROM #DirTree WHERE subdirectory = #DBName)
EXEC master.dbo.xp_create_subdir #DataPath2
DELETE FROM #DirTree
--reading all folders from LOG directory
INSERT INTO #DirTree(subdirectory, depth)
EXEC master.sys.xp_dirtree #LogPath
--creating a folder on LOG drive with database name if not already exists
IF NOT EXISTS (SELECT 1 FROM #DirTree WHERE subdirectory = #DBName)
EXEC master.dbo.xp_create_subdir #LogPath2
DECLARE #PhysicalName nvarchar(260)=''
DECLARE #LogicalName nvarchar(128)=''
DECLARE #Type char(1)=''
DECLARE #text nvarchar(2000)=''
DECLARE #sql nvarchar(max)=''
DECLARE #restoredb TABLE ( LogicalName nvarchar(128), PhysicalName nvarchar(260),Type char(1)
,FileGroupName nvarchar(128),Size numeric(20,0),MaxSize numeric(20,0),FileID bigint,CreateLSN numeric(25,0),DropLSN numeric(25,0)
,UniqueID uniqueidentifier,ReadOnlyLSN numeric(25,0),ReadWriteLSN numeric(25,0),BackupSizeInBytes bigint,SourceBlockSize int
,FileGroupID int,LogGroupGUID uniqueidentifier,DifferentialBaseLSN numeric(25,0),DifferentialBaseGUID uniqueidentifier
,IsReadOnly bit,IsPresent bit,TDEThumbprint varbinary(32),SnapshotUrl nvarchar(128))
--reading header section of backup file
INSERT #restoredb EXECUTE(N'RESTORE FILELISTONLY FROM DISK = '''+#backuppath +'''')
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
BEGIN TRY
--***taking backup of all permissions**********************************
CREATE TABLE #Permissions (id int identity(1,1), qry nvarchar(4000),Stat bit default 0)
SET #sql='USE ['+#dbname+'];
-- Create Users If not exists
INSERT INTO #Permissions (qry)
select
txt=case when isnull(l.name,'''')=''''
then ''IF NOT EXISTS (SELECT 1 FROM sys.database_principals where name = '''''' + p.name + '''''') BEGIN CREATE USER ['' + p.name +''] FOR LOGIN ['' + p.name +''] END''
else ''IF NOT EXISTS (SELECT 1 FROM sys.database_principals where name = '''''' + p.name + '''''') BEGIN CREATE USER ['' + p.name +''] FOR LOGIN ['' + l.name +''] END''
end
from sys.database_principals p
left join master..syslogins l on l.sid = p.sid
where p.type NOT IN (''R'') AND p.name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'')
UNION ALL
--Create Role If not exists
select
txt=case when isnull(l.name,'''')=''''
then ''IF NOT EXISTS (SELECT 1 FROM sys.database_principals where name = '''''' + p.name + '''''') BEGIN CREATE Role ['' + p.name +''] END''
else ''''
end
from sys.database_principals p
left join master..syslogins l on l.sid = p.sid
where p.type =''R'' and p.name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'',
''db_owner'',''db_accessadmin'',''db_securityadmin'',''db_ddladmin'',''db_backupoperator'',''db_datareader'',''db_datawriter'',''db_denydatareader'',''db_denydatawriter'',''public'')
UNION ALL
--User Permissions
select
txt=case when isnull(p.type ,'''')<>''G''
then ''ALTER USER ['' + p.name +''] WITH DEFAULT_SCHEMA = ''+ isnull(p.default_schema_name, ''[NULL]'') +'';''
else ''''
end
+ '' EXEC sp_addrolemember '''''' + q.name + '''''', '''''' + p.name + '''''';''
from sys.database_principals p
join (select * from sys.database_principals a
join sys.database_role_members r on a.principal_id=r.role_principal_id) q
on q.member_principal_id = p.principal_id
where (p.type NOT IN (''R'')) AND p.name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'')
UNION ALL
--Orphaned users
select
txt=case when isnull(l.name,'''')=''''
then ''Exec sp_change_users_login ''''update_one'''', '''''' + p.name + '''''', '''''' + p.name + '''''' ''
else ''Exec sp_change_users_login ''''update_one'''', '''''' + p.name + '''''', '''''' + l.name + '''''' ''
end
from sys.database_principals p
left join master..syslogins l on l.sid = p.sid
where p.type=''S'' AND p.name NOT IN (''dbo'',''guest'',''INFORMATION_SCHEMA'',''sys'')
'
--get all permissions in text format
EXECUTE sp_executesql #sql
--**Permissions end******************************************************************--
--**preparing a restore statement****************************************************--
SET #SQL='USE [master];ALTER DATABASE ['+#dbname+N'] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE ['+#dbname+N'] SET SINGLE_USER;
ALTER DATABASE ['+#dbname+N'] set offline with NO_WAIT;'
--set database to single user mode
EXECUTE sp_executesql #sql
SET #SQL='RESTORE DATABASE ['+#dbname+N'] FROM DISK = '''+#backuppath+''' WITH FILE = 1'
--preparing move statement for all files
DECLARE c1 CURSOR FOR SELECT PhysicalName,LogicalName,Type FROM #restoredb
OPEN c1;
FETCH NEXT FROM c1 INTO #PhysicalName,#LogicalName,#Type -- fetch first value
WHILE ##fetch_status = 0
BEGIN
SET #text = RIGHT(#PhysicalName,CHARINDEX( '\', REVERSE(#PhysicalName)));
SET #SQL=#SQL+', MOVE '''+#LogicalName+N''''
IF (#Type = 'L')
SET #SQL=#SQL+' TO '''+#logpath+N'\'+#dbname+#text+N''''
ELSE
SET #SQL=#SQL+' TO '''+#datapath+N'\'+#dbname+#text+N''''
FETCH NEXT FROM c1 INTO #PhysicalName,#LogicalName,#Type -- fetch next value
END; --WHILE ##fetch_status = 0
CLOSE c1
DEALLOCATE c1;
-- replace ersetzt die alte db
SET #SQL=#SQL+', NOUNLOAD , replace , STATS = 10 ;'
--perform restore database operation
EXECUTE sp_executesql #sql
--setting database to online mode
SET #SQL='USE [master];
ALTER DATABASE ['+#dbname+N'] set ONLINE with NO_WAIT;
ALTER DATABASE ['+#dbname+N'] SET MULTI_USER; '
EXECUTE sp_executesql #sql
-- changes database owner to sa
SET #SQL='USE [' + #dbname + N'];
EXEC sp_changedbowner ''sa'' '
EXECUTE sp_executesql #sql
--**Database a restore is completed**********************************************--
--**restore permissions on database****************************************************--
DECLARE #ptxt nvarchar(4000)='',#Id int=0
WHILE 0=0
BEGIN
SELECT #ptxt='',#Id=0
SELECT TOP 1 #Id=Id, #ptxt=qry FROM #Permissions WHERE Stat=0 ORDER BY Id
IF Isnull(#Id,0)<=0 BREAK;
SET #ptxt= N'USE [' + #dbname + N'];' + #ptxt
--skip if any invalid login/user exists
BEGIN TRY
--print #ptxt
EXECUTE (#ptxt)
END TRY
BEGIN CATCH
--No action is needed
END CATCH
UPDATE #Permissions SET Stat=1 WHERE Id=#Id
END
END TRY
BEGIN CATCH
--setting database to online mode
SET #SQL='USE [master];
ALTER DATABASE ['+#dbname+N'] set ONLINE with NO_WAIT;
ALTER DATABASE ['+#dbname+N'] SET MULTI_USER; '
EXECUTE sp_executesql #sql
SELECT #ErrorMessage = ERROR_MESSAGE(), #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
BEGIN TRY
EXEC sp_configure 'show advanced options', 1
RECONFIGURE with override
--Retain original setting
EXEC sp_configure 'xp_cmdshell', #XPConfig
RECONFIGURE with override
END TRY
BEGIN CATCH
SELECT #ErrorMessage = ERROR_MESSAGE(), #ErrorSeverity = ERROR_SEVERITY(), #ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
END
The error message like below:
Msg 156, Level 15, State 1, Line 4
Incorrect syntax near the keyword 'PROCEDURE'.
Msg 137, Level 15, State 2, Line 69
Must declare the scalar variable "#dbname".
Msg 137, Level 15, State 2, Line 70
Must declare the scalar variable "#dbname".
Msg 137, Level 15, State 2, Line 79
Must declare the scalar variable "#DBName".
Msg 137, Level 15, State 2, Line 90
Must declare the scalar variable "#DBName".
Msg 137, Level 15, State 2, Line 105
Must declare the scalar variable "#backuppath".
Msg 137, Level 15, State 2, Line 116
Must declare the scalar variable "#dbname".
Msg 137, Level 15, State 2, Line 177
Must declare the scalar variable "#dbname".
Msg 137, Level 15, State 2, Line 184
Must declare the scalar variable "#dbname".
Msg 137, Level 15, State 2, Line 198
Must declare the scalar variable "#dbname".
......
can anyone explain this for me how to fix this issue if I want to have a pre-check?
CREATE PROCEDURE has to be the only statement in a batch. To conditionally execute it, you can use dynamic SQL.
...
IF ...
BEGIN
EXECUTE(N'CREATE PROCEDURE ...');
END;
...
If you only need the conditional execution to prevent errors of the procedure already existing and your version of SQL Server is high enough, you can alternatively use the CREATE OR ALTER PROCEDURE ... statement. It's available since 2016, I believe. That way the procedure simply gets overwritten, if it already exists, without the statement throwing any errors because of the procedure already existing.
Basically the constraint is 'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
Depends on what you want, there are several option, each has different effect.
CREATE OR ALTER PROCEDURE
create or alter procedure [dbo].[User_Own_Restore_From_Job]
as
begin
...
end;
GO
the effect is the procedure is altered and the modify_date in sys.procedures will be updated every time it executes
DROP PROCEDURE + CREATE PROCEDURE
If you are using a earlier version of SQL Server without the CREATE OR ALTER option, you can check for existence of the store procedure, drop it and create it.
if object_id('dbo.User_Own_Restore_From_Job') is not null
drop procedure dbo.User_Own_Restore_From_Job
GO
create procedure [dbo].[User_Own_Restore_From_Job]
as
begin
...
end;
GO
the effect is the procedure is drop and create everytime it executes. And also the create_date and modify_date will be change also.
Don't execute if exists
this make use of set noexec settings. If the procedure exists set noexec to off (The subsequent commands will not be executed). And set it back to on at the end.
IF object_id('dbo.User_Own_Restore_From_Job') is not null
set noexec on;
GO
create procedure [dbo].[User_Own_Restore_From_Job]
as
begin
...
end;
GO
set noexec off;

Will all transaction in this stored procedure be rolled back

I have created a stored procedure (shown below) in SQL Server and tried to include a Rollback Transaction as I need to have a "stored procedure that has a transaction around it, so that if/when it fails all inserts will be rolled back."
I am unsure if this work or not, or will work, I cannot test yet as only developing locally, but wondered if someone wouldn't mind looking over the Rollback Transaction part of the stored procedure and advise if on the right path?
USE AutomatedTesting
GO
ALTER PROCEDURE [dbo].[spInsertTestCases]
(#AddedTFS INT,
#Scenario NVARCHAR(500),
#TargetTableName NVARCHAR(100),
#TargetFieldName NVARCHAR(100),
#ExpectedResult NVARCHAR(100),
#TargetTableDBName NVARCHAR(100),
#TargetTableSchema NVARCHAR(100),
#TargetFieldIsDateTime NVARCHAR(1),
#TestCaseIdentifiers dbo.TestCaseIdentifiers READONLY ) -- can only be READONLY. meaning you cannot amend the param
/* #TestCaseIdentifiers var will be prepopulated
TestDataIdentifiersNEW is a custom data type which has fields (TestSequence ColumnName ColumnValue IsAlphaNumeric)
so stored procedure is called like:
EXEC [dbo].[spTest_UserDefinedDatatype] 'param1','param2' #temp_testdata
#temp_testdata will already be defined and popualted(INSERT INTO ) before exec to add in 1 to many rows.
for example:
ColumnName ColumnValue
PATIENTID 123456
SOURCESYS PAS
in simple terms above EXEC is:
EXEC [dbo].[spTest_UserDefinedDatatype] 'param1','param2' 'PATIENTID 123456'
'SOURCESYS PAS'
*/
AS
BEGIN TRY
BEGIN TRANSACTION
BEGIN
--DECLARE #TableNameUpdate SYSNAME = #TargetTableName
--DECLARE #CDI SYSNAME = REPLACE(#TargetTableName,'CDO','CDI') -- so if targettablename param is CDO then swap it to CDI. why?
DECLARE #sql VARCHAR(MAX) = ' INSERT INTO [dbo].[TestCasesIdentifier] ([TestCaseId], [TestCaseSequence], [FieldName], [FieldValue], [AlphaNumeric]) VALUES '
DECLARE #i INT = 1
DECLARE #TableNameUpdate SYSNAME = #TargetTableName
DECLARE #CDI SYSNAME = REPLACE(#TargetTableName,'CDO','CDI')
DECLARE #ColName SYSNAME
DECLARE #Ret NVARCHAR(256)
DECLARE #sql2 NVARCHAR(MAX)
DECLARE #TestCaseID INT = -1 --does this need default variable?
DECLARE #ErrorCode INT = ##error
DECLARE #TestSequence INT
DECLARE #ColumnName VARCHAR(100)
DECLARE #ColumnValue VARCHAR(100)
DECLARE #IsAlphaNumeric BIT
DECLARE #TableTestSequence INT = ISNULL((SELECT MAX([TableTestSequence]) + 1 FROM TestCases WHERE #TargetTableName = [TargetTableName]), 1)
-- INSERT into TestCases. 1 record
-- An assumption that a number of fields will have defaults on them - if not, extra fields will need adding
INSERT INTO [dbo].[TestCases] ([AddedTFS], [TableTestSequence], [Scenario],
[TargetTableName], [TargetFieldName], [ExpectedResult],
[TargetTableDBName], [TargetTableSchema], [TargetFieldIsDateTime])
VALUES (#AddedTFS, -- AddedTFS (The TFS Number of the Development carried out)
ISNULL((SELECT MAX([TableTestSequence]) + 1 -- TableTestSequence (Generates the next Sequence Number for a Table)
FROM TestCases -- if table doesnt exist in TestCases then sets to 1
WHERE #TargetTableName = [TargetTableName]), 1),
#Scenario, -- Scenario (A description of the scenario use GIVEN and WHERE)
#TargetTableName, -- TargetTableName (References the Target Table entered at the top of this SQL - SET #TableName = 'CDO_APC_ELECTIVE_ADMISSION_LIST')
#TargetFieldName, -- TargetFieldName (The Field in which we want to test)
#ExpectedResult, -- ExpectedResult (The expected output/result of the field in which we want to test)
#TargetTableDBName, -- The DB to be used
#TargetTableSchema, -- the schema to be used
#TargetFieldIsDateTime) ---- 1 = Yes, 0 = No (Is Target field a datetime field)
-- Grab the identity value just generated by the last statement and the last error code generated
-- in order to reference TestCases PK when adding to TestCaseIdentifiers
SELECT #TestCaseID = SCOPE_IDENTITY(), #ErrorCode = ##error
IF #ErrorCode = 0 --OR #TestCaseID <> -1 -- #ErrorCode <> 0 if error then back out testcases INSERT? surely should use BEGIN/ROLLBACK tran
--IF #ErrorCode = 0 OR #TestCaseID <> -1
-- If there was no error creating the TestCase record, create the records for the WHERE clause
BEGIN
/*
rollback insert if no matching records
rollback insert if SQL returns more than 1 record
return error message to user
*/
SELECT
ic.index_column_id, c.name
INTO #tmp
FROM sys.indexes i
JOIN sys.index_columns ic ON i.object_id = ic.object_id
AND i.index_id = ic.index_id
JOIN sys.columns c ON c.column_id = ic.column_id
AND c.object_id = ic.object_id
JOIN sys.tables t ON c.object_id = t.object_id
WHERE t.name = #CDI
AND i.is_primary_key = 1
IF (SELECT COUNT(*) FROM #TestCaseIdentifiers) = 0
--IF #PKValues IS NULL
BEGIN
WHILE #i <= (SELECT COUNT(*) FROM #tmp)
BEGIN
SELECT #ColName = [name]
FROM #tmp
WHERE index_column_id = #i
-- if #expectedvalue IS NULL
SET #sql2 = 'SELECT TOP 1 #RetvalOut = ' + QUOTENAME(#ColName) + ' FROM ' + QUOTENAME(#CDI) + ' ORDER BY NEWID()'
-- else
-- SET #sql2 = ''
EXECUTE sp_executesql #command = #sql2, #ParmDefinition = N'#RetvalOut NVARCHAR(MAX) OUTPUT', #retvalOut = #Ret OUTPUT
SET #sql += '(' + CONVERT(VARCHAR(100),#TestCaseID) + ',' + CONVERT(VARCHAR(10),#i) + ',''' + #ColName + ''',''' + #Ret + ''',1),'
SET #i+=1
SELECT #sql = REVERSE(SUBSTRING(REVERSE(#sql),2,8000))
PRINT #sql
EXEC #sql
END
END
ELSE
BEGIN
--PRINT 'got here'
DECLARE csr_TestCaseIdentifierInsert CURSOR FOR
SELECT [TestSequence],[ColumnName],[ColumnValue],[IsAlphaNumeric]
FROM #TestCaseIdentifiers
ORDER BY [TestSequence]
OPEN csr_TestCaseIdentifierInsert
FETCH NEXT FROM csr_TestCaseIdentifierInsert INTO #TestSequence, #ColumnName, #ColumnValue, #IsAlphaNumeric
WHILE ##fetch_status = 0
BEGIN
INSERT INTO [dbo].[TestCasesIdentifier]
([TestCaseId],
[TestCaseSequence],
[FieldName],
[FieldValue],
[AlphaNumeric])
VALUES
(#TestCaseID, #TestSequence, #ColumnName, #ColumnValue,#IsAlphaNumeric)
FETCH NEXT FROM csr_TestCaseIdentifierInsert INTO #TestSequence, #ColumnName, #ColumnValue, #IsAlphaNumeric
END
CLOSE csr_TestCaseIdentifierInsert
DEALLOCATE csr_TestCaseIdentifierInsert
END -- loop to add records to testcasesidentifier
END
END
COMMIT
END TRY
BEGIN CATCH
IF ##TRANCOUNT > 0
ROLLBACK TRAN
DECLARE #ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE()
DECLARE #ErrorSeverity INT = ERROR_SEVERITY()
DECLARE #ErrorState INT = ERROR_STATE()
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
You are almost there. I usually wrap the stored proc code within a BEGIN..END block as well, then next comes the most important part: you must add SET XACT_ABORT ON; before your TRY..CATCH and BEGIN TRAN, as SQL Server defaults the XACT_ABORT to OFF. Otherwise not everything will be rolled back.
Example setup:
CREATE PROCEDURE dbo.uspMyTestProc
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
BEGIN TRY
BEGIN TRANSACTION;
-- Do your magic stuff here before committing...
COMMIT;
END TRY
BEGIN CATCH
IF ##trancount > 0
ROLLBACK TRANSACTION;
-- Add extra error logging here if you want...
END CATCH;
END;
GO
Also, if you want to add a possible stacktrace if you are using nested procedures et cetera you might want to consider using a generic error handler à la Erland Sommerskog. We adapted this approach completely. See for more details How to handle Transaction in Nested procedure in SQL server?

Object_ID does not detect the recently created or dropped tables in SQL Server

I am having issue with working of Object_ID to detect if the table already exists.
It works fine the first iteration and allows to create the required tables properly.
But if executed again, it still thinks/sees that tables are not created and tries to create the table and then SQL Server gives the error that tables already exist.
SQL Server version 2008
Code
--Alter Proc spCreateCustomerChartTables
--As
Begin
Declare
#mycursorMARKET Cursor,
#mycursorCUSTOMER Cursor,
#MarketNameEnglish nchar(30),
#CustomerID nchar(10),
#DDate date,
#textdate as nchar(12),
#tableName nchar(100),
#sqlcmd as nvarchar(500)
Set #DDate = GETDATE()
Set #mycursorMARKET = Cursor for Select Distinct MarketNameEnglish From dbo.tableMarketName Order by MarketNameEnglish ASC
Open #mycursorMARKET
Fetch Next From #mycursorMARKET Into #MarketNameEnglish
While ##FETCH_STATUS = 0
Begin
Set #mycursorCUSTOMER = Cursor for Select Distinct CustomerID From dbo.CustomerEmployeeDetail Order by CustomerID ASC
Open #mycursorCUSTOMER
Fetch Next From #mycursorCUSTOMER Into #CustomerID
While ##FETCH_STATUS =0
Begin
Set #textdate = cast(#DDate as NCHAR(12))
Set #tableName = RTrim(#MarketNameEnglish) + '_' + RTrim(#CustomerID) + '_' + RTrim(#textdate )
If OBJECT_ID(#tableName , 'U') IS Not Null
--IF Not EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES
-- WHERE TABLE_NAME = #tableName )
Begin
Print 'Table Does Not Exist'
Set #sqlcmd = 'Select * Into ' + RTrim(#tableName) + ' From BlankChart'
Set #sqlcmd = RTRIM(#sqlcmd)
Set #sqlcmd = REPLACE (#sqlcmd, '-', '_')
Exec(#sqlcmd)
End
Else
Begin
Print 'Table Exists'
Set #sqlcmd = 'Drop Table ' + #tableName
Set #sqlcmd = RTRIM(#sqlcmd)
Set #sqlcmd = REPLACE (#sqlcmd, '-', '_')
Exec(#sqlcmd)
End
--Print #sqlcmd --Create chart tables here
Fetch Next From #mycursorCUSTOMER Into #CustomerID
End
--Print #MarketNameEnglish
Fetch Next From #mycursorMARKET Into #MarketNameEnglish
End
Close #mycursorMARKET
Deallocate #mycursorMARKET
Close #mycursorCUSTOMER
Deallocate #mycursorCUSTOMER
End
Your condition is "inverted":
your If OBJECT_ID(#tableName , 'U') IS Not Null should be changed to If OBJECT_ID(#tableName , 'U') IS Null
Finally found the mistake in my code. It had got nothing to do with Object_ID.
Actually the formating of variable #tablename was done incorrectly and at incorrect place. The dashes (-) of date in tablename were not replaced with underscore (_). This was causing the error.

Deadlock in SQL Server

I'm facing deadlock
was deadlocked on lock resources with another process and has been
chosen as the deadlock victim.
problem In SQL-Server as i'm inserting data in database by picking max id against a specific column then add a increment got the value against which record will be inserted.
i'm calling a procedure as code mentioned below:
CREATE
PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #MaxID BIGINT;
SET NOCOUNT ON;
-- Insert statements for procedure here
BEGIN
BEGIN TRAN
SET #MaxID = (
SELECT Col_Counter
FROM Maintenance_Counter WITH (XLOCK, ROWLOCK)
WHERE COL_NAME = #Col_Name
)
UPDATE Maintenance_Counter
SET Col_Counter = #MaxID + 1
WHERE COL_NAME = #Col_Name
COMMIT
END
SELECT (
CONVERT(
VARCHAR,
(
SELECT office_id
FROM Maintenance
)
) + '' + CONVERT(VARCHAR, (#MaxID))
) AS MaxID
END
any one help me out .....
As Marc already answered, use SEQUENCE. It's available in all supported versions of SQL Server, ie 2012 and later. The only reason to avoid it is targeting an unsupported version like 2008.
In this case, you can set the counter variable in the same statement you update the counter value. This way, you don't need any transactions or locks, eg:
declare #counterValue bigint
UPDATE Maintenance_Counter
SET Col_Counter = Col_Counter + 1 , #counterValue=Col_Counter+1
WHERE COL_NAME = #Col_Name
select #counterValue
Yo can use sequences to generate incremental values avoiding any blocking.
I have adapted my own Counter Generator to be a direct replacement for yours. It creates dynamically the SQL statements to manage sequences, if a Sequence doesn't exist for the value we are looking for, it creates it.
ALTER PROCEDURE [dbo].[Web_GetMaxColumnID]
#Col_Name nvarchar(50)
AS
declare #Value bigint;
declare #SQL nvarchar(64);
BEGIN
if not exists(select * from sys.objects where object_id = object_id(N'dbo.MY_SEQUENCES_' + #Col_Name) and type = 'SO')
begin
set #SQL = N'create sequence dbo.MY_SEQUENCES_' + #Col_Name + ' as bigint start with 1';
exec (#SQL);
end
set #SQL = N'set #Value = next value for dbo.MY_SEQUENCES_' + #Col_Name;
exec sp_executesql #SQL, N'#Value bigint out', #Value = #Value out;
select #Value ;
END
The only inconvenience is that your values can get gaps within (because you could have retrieved a value but finally not used it). This is not a problem on my tables, but you have to consider it.

Creating procedure inside IF section

I need some help with simple SQL code:
DECLARE #procExists int
SET #procExists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'dbo' AND ROUTINE_NAME = 'Table_Exists' AND ROUTINE_TYPE = 'PROCEDURE')
IF NOT #procExists > 0
BEGIN
-- test query
-- SELECT 'Something' = #procExists;
-- error throwing code
-- CREATE PROCEDURE Table_Exists
-- #schemaName varchar(50),
-- #tableName varchar(50)
-- AS
-- RETURN (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = #schemaName AND TABLE_NAME = #tableName)
END
The simple code above:
- declares an int variable
- checks if procedure dbo.Table_Exists exists
- IF NOT exists it creates it
My problem is this error information:
Msg 156, Level 15, State 1, Line 9
Incorrect syntax near the keyword 'PROCEDURE'.
Msg 137, Level 15, State 2, Line 13
Must declare the scalar variable "#schemaName".
I don't know why, but..
- when i execute 'CREATE PROCEDURE' body alone it works
- when i execute whole IF section excluding 'CREATE PROCEDURE' body, simple query works
- when i execute whole IF section including 'CREATE PROCEDURE' body, error is thrown
What am i missing?
CREATE PROCEDURE has to be in it's own batch
So, dynamic SQL is one way:
IF OBJECT_ID('Table_Exists') IS NULL
BEGIN
EXEC ('CREATE PROCEDURE Table_Exists
#schemaName varchar(50),
#tableName varchar(50)
AS
RETURN (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = #schemaName AND TABLE_NAME = #tableName)
')
END
or DROP first
IF OBJECT_ID('Table_Exists') IS NOT NULL
DROP PROC Table_Exists
GO
CREATE PROCEDURE Table_Exists
#schemaName varchar(50),
#tableName varchar(50)
AS
RETURN (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = #schemaName AND TABLE_NAME = #tableName)
GO
Note the use of OBJECT_ID to see if the proc exists.
You can do this using SET NOEXEC ON. This instructs SQL Server to ignore all SQL code until SET NOEXEC OFF is reached.
IF EXISTS (SELECT *
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_TYPE = 'PROCEDURE'
AND ROUTINE_SCHEMA = 'dbo'
AND ROUTINE_NAME = 'HelloWorld')
BEGIN
SET NOEXEC ON
END
GO
CREATE PROCEDURE dbo.HelloWorld
AS
PRINT 'Hello world'
GO
SET NOEXEC OFF
GO
From MSDN:
The CREATE PROCEDURE statement cannot be combined with other Transact-SQL statements in a single batch.
Therefore, what you are trying to do is not possible, unless you are fine with implementing it via a dynamic query.
if OBJECT_ID('PROC1') IS NULL
EXEC('CREATE PROCEDURE DBO.PROC1 AS SELECT 1')
GO
ALTER PROCEDURE DBO.PROC1(#PARAM1 INT, #PARAM2 INT)
AS
.................

Resources