"WHEN OTHERS THEN NULL" in SQL Server 2005 - sql

tracking_table is a log table declared as follows:
create table tracking_table (my_command nvarchar(500), my_date datetime);
Please suppose you have the following block of SQL SERVER 2005 code, declared within a SQL Server 2005 job:
DECLARE #my_statement NVARCHAR(500)
delete from tracking_table
SET #my_statement = 'ALTER INDEX ALL ON my_user.dbo.my_fact_table REBUILD WITH (FILLFACTOR = 90)'
insert into tracking_table values (#my_statement,getdate())
EXEC (#my_statement)
SET #my_statement = 'ALTER INDEX ALL ON my_user.dbo.my_second_table REBUILD WITH (FILLFACTOR = 90)'
insert into tracking_table (#my_statement,getdate())
EXEC (#my_statement)
At runtime, if the first statement (ALTER INDEX ALL ON my_user.dbo.my_fact_table REBUILD WITH (FILLFACTOR=90)) fails, the second statement which acts on my_second table WON'T be executed.
I would like to know how could I modify the SQL Server 2005 code, in order to skip any error, going forward (in Oracle I would say, WHEN OTHERS THEN NULL).
How could I achieve this?
Thank you in advance for your kind help.

I cannot advice to suppress errors, but if you really want to do it, I think you can try:
declare #my_statement nvarchar(500)
begin try
delete from tracking_table
end try
begin catch
print null // or errormessage
end catch
begin try
set #my_statement = 'ALTER INDEX ALL ON my_user.dbo.my_fact_table REBUILD WITH (FILLFACTOR = 90)'
insert into tracking_table values (#my_statement,getdate())
exec (#my_statement)
end try
begin catch
print null // or errormessage
end catch
begin try
set #my_statement = 'ALTER INDEX ALL ON my_user.dbo.my_second_table REBUILD WITH (FILLFACTOR = 90)'
insert into tracking_table (#my_statement,getdate())
exec (#my_statement)
end try
begin catch
print null // or errormessage
end catch
you can also create procedure
create procedure sp_executesql_Suppress_Errors
(
#stmt nvarchar(max)
)
as
begin
begin try
exec sp_executesql
#stmt = #stmt
end try
begin catch
print null // or errormessage
end catch
end
and then call it with your statements. I also advice you to use exec sp_executesql instead of exec (see Dynamic SQL - EXEC(#SQL) versus EXEC SP_EXECUTESQL(#SQL))

Related

TSQL procedure that neither raises an error nor completes prescribed behaviour

Overview
I wrote a procedure that changes a column type and applies a unique constraint to it. The procedure executes and completes successfully, however, neither the column type is changed nor has the unique constraint been applied. When I SELECT #tsql to inspect the statements produced and run them in Azure Data Studio they work fine. Equally at no point is an error raised.
The database service I am using is Azure SQL Database Instance.
The procedure
CREATE PROCEDURE [dbo].[usp_aProcName]
#tableName NVARCHAR(250),
#fieldName NVARCHAR(250)
AS
BEGIN TRY
DECLARE #tsql VARCHAR(MAX) = N'ALTER TABLE my_schema.'+#tableName+' ALTER COLUMN '+#fieldName+' INT NOT NULL;';
EXECUTE sp_executesql #tsql;
SET #tsql = N'ALTER TABLE my_schema.'+#tableName+' ADD CONSTRAINT AK_'+#tableName+'_'+#fieldName+' UNIQUE ('+#fieldName+');';
EXECUTE sp_executesql #tsql;
RETURN 0;
END TRY
BEGIN CATCH
RETURN ##ERROR;
END CATCH
;
Any ideas?
Resolving the runtime error;
Dynamic SQL must be to be assigned to an NVARCHAR, NCHAR or NTEXT type, not VARCHAR. The error was being caught but not displayed in Azure Data Studio despite returning ##ERROR in the case of exception.
Errors are intended to be returned to the orchestration layer (Azure Data Factory) and stored in another databsae. Error handling remodelled (this is a mock);
CREATE PROCEDURE [dbo].[usp_aProcName]
#tableName NVARCHAR(250),
#fieldName NVARCHAR(250)
AS
BEGIN TRY
BEGIN TRAN TranName;
DECLARE #tsql VARCHAR(MAX) = N'ALTER TABLE my_schema.'+#tableName+' ALTER COLUMN '+#fieldName+' INT NOT NULL;';
EXECUTE sp_executesql #tsql;
SET #tsql = N'ALTER TABLE my_schema.'+#tableName+' ADD CONSTRAINT AK_'+#tableName+'_'+#fieldName+' UNIQUE ('+#fieldName+');';
EXECUTE sp_executesql #tsql;
COMMIT TRAN TranName;
END TRY
BEGIN CATCH
ROLLBACK TRAN TranName;
SELECT
OBJECT_NAME(##PROCID) AS [ProcedureName],
ERROR_MESSAGE() AS [ErrorMessage],
ERROR_NUMBER() AS [ErrorNumber],
ERROR_LINE() AS [ErrorLine],
ERROR_SEVERITY() AS [ErrorSeverity],
ERROR_STATE() AS [ErrorState]
;
END CATCH
;

How to check a MariaDB syntax error (Error Code 1064)?

I've 2 simples stored procedures on a MariaDB 10, in order to clean automatically data from my tables.
The first one reads configuration items from a simple table and passes that data to the second one, that deletes physically the records.
During the test all worked fine, but now I get the error "Error Code: 1064. You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'NULL' at line 1 0,052 sec" and I don't know why.
The procedure is the following:
CREATE DEFINER=`root`#`10.135.15.%` PROCEDURE `clean_table_checker`()
BEGIN
DECLARE TMP_TIME_AGO INT(11);
DECLARE TMP_ID INT(11);
DECLARE TMP_RETENTION_SECS INT(11);
DECLARE TMP_DBNAME VARCHAR(45);
DECLARE TMP_TABLENAME VARCHAR(45);
DECLARE TMP_TS_FIELD VARCHAR(45);
DECLARE TMP_LASTUPDATE INT(11);
DECLARE TMP_RETENTION INT(4);
DECLARE DONE INT DEFAULT 0;
DECLARE get_tables CURSOR FOR SELECT `id`, `dbname`, `tablename`, `ts_field`, `lastupdate`, `retention` FROM management.clean_table;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN get_tables;
loop_cursor_ptr: LOOP
IF DONE THEN
LEAVE loop_cursor_ptr;
END IF;
FETCH get_tables INTO TMP_ID, TMP_DBNAME, TMP_TABLENAME, TMP_TS_FIELD, TMP_LASTUPDATE, TMP_RETENTION;
SET TMP_TIME_AGO = UNIX_TIMESTAMP(NOW()) - TMP_LASTUPDATE;
SET TMP_RETENTION_SECS = TMP_RETENTION * 86400;
IF TMP_LASTUPDATE is NULL THEN
SET #SQL = CONCAT('UPDATE management.clean_table SET `lastupdate`=',UNIX_TIMESTAMP(NOW()),' WHERE `id`=',TMP_ID,';');
ELSEIF (TMP_TIME_AGO > TMP_RETENTION_SECS) THEN
CALL clean_table_proc(TMP_DBNAME, TMP_TABLENAME, TMP_TS_FIELD, TMP_RETENTION_SECS);
SET #SQL = CONCAT('UPDATE management.clean_table SET `lastupdate`=',UNIX_TIMESTAMP(NOW()),' WHERE `id`=',TMP_ID,';');
END IF;
PREPARE STMT FROM #SQL;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END LOOP loop_cursor_ptr;
CLOSE get_tables;
END
Any ideas? Suggestions?
If neither of your conditions (TMP_LASTUPDATE is NULL, TMP_TIME_AGO > TMP_RETENTION_SECS) is true, the value of #SQL is not set and hence NULL. You are then trying to prepare a statement from a NULL value, which is why you are seeing the error that you are. You need to add a test as to whether execution of a query is necessary, for example:
SET #SQL = ''
IF TMP_LASTUPDATE is NULL THEN
SET #SQL = CONCAT('UPDATE management.clean_table SET `lastupdate`=',UNIX_TIMESTAMP(NOW()),' WHERE `id`=',TMP_ID,';');
ELSEIF (TMP_TIME_AGO > TMP_RETENTION_SECS) THEN
CALL clean_table_proc(TMP_DBNAME, TMP_TABLENAME, TMP_TS_FIELD, TMP_RETENTION_SECS);
SET #SQL = CONCAT('UPDATE management.clean_table SET `lastupdate`=',UNIX_TIMESTAMP(NOW()),' WHERE `id`=',TMP_ID,';');
END IF;
IF #SQL != '' THEN
PREPARE STMT FROM #SQL;
EXECUTE STMT;
DEALLOCATE PREPARE STMT;
END IF;

procedure to insert columns from one table to another

i want to insert columns date, service and service_count from an existing table to a new table. i want to do it using a procedure but it's not working.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROC [dbo].[SP_INSERTCOLOUMNS]
AS
BEGIN
DECLARE #SQLS NVARCHAR(MAX)
SET #SQLS = 'insert into test(date,service,service_count)
select date,service,count(service) from tbl_OBD_CDRS
group by date,service'
PRINT #SQLS
EXEC SP_EXECUTESQL #SQLS
PRINT 'INSERTED SUCCESSFULLY'
END
if i run the insert command independently it works fine. if i run it using this procedure it says Command(s) completed successfully, but no changes are made in the table "test".
I don't see any issue in your script. Why don't you try to check count in Test table. See if no of records increases in your Test table.
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROC [dbo].[SP_INSERTCOLOUMNS]
AS
BEGIN
select date,service,count(service) from tbl_OBD_CDRS
group by date,service
DECLARE #SQLS NVARCHAR(MAX)
SET #SQLS = 'insert into test(date,service,service_count)
select date,service,count(service) from tbl_OBD_CDRS
group by date,service'
PRINT #SQLS
EXEC SP_EXECUTESQL #SQLS
PRINT 'INSERTED SUCCESSFULLY'
END
Try to run this on SQL Server and post your result.
EXEC [dbo].[SP_INSERTCOLOUMNS]
ALTER PROC [dbo].[SP_INSERTCOLOUMNS]
AS
BEGIN
-- select date,service,count(service) from tbl_OBD_CDRS
-- group by date,service
insert into test(date,service,service_count)
select date,service,count(service) from tbl_OBD_CDRS Group by date,service
PRINT 'INSERTED SUCCESSFULLY'
END
use this if any error in table / datatype it throw directly

How do you check if IDENTITY_INSERT is set to ON or OFF in SQL Server?

I've searched for this, but threads in which it appeared tended to have answers from people who didn't understand the question.
Take the following syntax:
SET IDENTITY_INSERT Table1 ON
How do you do something more like this:
GET IDENTITY_INSERT Table1
I don't want to do anything whatsoever to the data in the database or to the settings to get this information though. Thanks!
Since SET IDENTITY_INSERT is a session sensitive, it is managed in buffer level without storing somewhere. This means we do not need to check the IDENTITY_INSERT status as we never use this key word in current session.
Sorry, no help for this.
Great question though :)
Source: Here
Update
There are ways maybe to do this, also seen in the site I linked, IMO, it is too much effort to be useful.
if
(select max(id) from MyTable) < (select max(id) from inserted)
--Then you may be inserting a record normally
BEGIN
set #I = 1 --SQL wants something to happen in the "IF" side of an IF/ELSE
END
ELSE --You definitely have IDENTITY_INSERT on. Done as ELSE instead of the other way around so that if there is no inserted table, it will run anyway
BEGIN
.... Code that shouldn't run with IDENTITY_INSERT on
END
In summary:
Nathan's solution is the fastest:
SELECT OBJECTPROPERTY(OBJECT_ID('MyTable'), 'TableHasIdentity');
when using an API wrapper, one can reduce the entire check to just checking for rows. For instance when using C#'s SqlDataReaders property HasRows and a query construct like:
SELECT CASE OBJECTPROPERTY(OBJECT_ID('MyTable'), 'TableHasIdentity')
WHEN 1 THEN '1' ELSE NULL END
Ricardo's solution allows more flexibility but requires the Column's identity name
SELECT * FROM sys.columns WHERE object_id = OBJECT_ID('MyTable', 'U')
AND name = 'MyTableIdentityColumnName';
Bogdan Bodanov solution, using try/catch would work as well, but additional checking should confine exception handling to cases of IDENTITY_INSERT is already ON for table 'MyTable'. Cannot perform SET operation for table 'MyTable';
You can discover whether or not identity_insert is on, and if so for what table using the code below.
declare #tableWithIdentity varchar(max) = '';
SET IDENTITY_INSERT ExampleTable ON
begin try
create table #identityCheck (id int identity(1,1))
SET IDENTITY_INSERT #identityCheck ON
drop table #identityCheck
end try
begin catch
declare #msg varchar(max) = error_message()
set #tableWithIdentity= #msg;
set #tableWithIdentity =
SUBSTRING(#tableWithIdentity,charindex('''',#tableWithIdentity,1)+1, 10000)
set #tableWithIdentity = SUBSTRING(#tableWithIdentity,1, charindex('''',#tableWithIdentity,1)-1)
print #msg;
drop table #identityCheck
end catch
if #tableWithIdentity<>''
begin
print ('Name of table with Identity_Insert set to ON: ' + #tableWithIdentity)
end
else
begin
print 'No table currently has Identity Insert Set to ON'
end
If you're attempting to turn off IDENTITY_INSERT for some other table to avoid getting an error when you want to set IDENTITY_INSERT on, the following may also work for you. As other have said on this thread IDENTITY_INSERT is a session setting with no direct visibility. However I made the interesting discovery that SET IDENTITY_INSERT OFF doesn't error out for any table that has an identity whether or not IDENTITY_INSERT is ON for that table. So it occurred to me that I could just call SET IDENTITY_INSERT ... OFF for every table with an identity in the database. It feels a bit like a brute force solution, but
I found that the following dynamic SQL block did the trick very nicely.
---- make sure IDENTITY_INSERT is OFF ----
DECLARE #cmd NVARCHAR(MAX)
SET #cmd = CAST((SELECT 'SET IDENTITY_INSERT ' +
QUOTENAME(OBJECT_SCHEMA_NAME(t.object_id)) + '.' +
QUOTENAME(t.name) + ' OFF' + CHAR(10)
FROM sys.columns c
JOIN sys.tables t ON t.object_id = c.object_id
WHERE c.is_identity = 1
ORDER BY 1 FOR XML PATH('')) AS NVARCHAR(MAX))
EXEC sp_executesql #cmd
Very good question. I Have same issue. May be you can try to reset IDENTITY_INSERT using TRY/CATCH? For example, you make the job but not sure if the job is finished and IDENTITY_INSERT is set to OFF.
Why you don't try:
BEGIN TRY
...
END TRY
BEGIN CATCH
SET IDENTITY_INSERT table OFF;
END CATCH;
Also I am not sure that this is working correctly but I see that adding only SET IDENTITY_INSERT ... OFF did not return error. So you can set just in case in the end SET IDENTITY_INSERT ... OFF.
If you want to know about the session variable... Good question, but I cant see where this information would be usefull. In normal execution to check a normal table response to an insert, this should work!
-- If you only want to know if there is identity insert on a given table:
select is_identity
from sys.columns
where object_id = OBJECT_ID('MyTable', 'U') and name = 'column_Name'
-- Or... Use this if you want to execute something depending on the result:
if exists (select *
from sys.columns
where object_id = OBJECT_ID('MyTable', 'U') and is_identity = 1)
... your code considering identity insert
else
... code that should not run with identity insert
Have fun!
Here is my solution. It is very similar to #jmoreno's answer.
You would call it like this
DECLARE #IdentityInsert VARCHAR(20)
EXEC dbo.GetIdentityInsert 'YourDb', 'YourSchema', 'YourTable', #IdentityInsert OUT
SELECT #IdentityInsert
This returns a 1-row recordset with column name IDENTITY_INSERT, that can be either ON, OFF, or NO_IDENTITY (if the given table doesn't have an identity column). It also sets the output parameter #IdentityInsert. So you can adjust the code to whichever method you prefer.
It would be nice to get this into a user-defined function, but unfortunately I couldn't find a way to avoid the TRY..CATCH block, which you cannot use in user-defined functions.
-- ================================================================================
-- Check whether the table specified has its IDENTITY_INSERT set to ON or OFF.
-- If the table does not have an identity column, NO_IDENTITY is returned.
-- Tested on SQL 2008.
-- ================================================================================
CREATE PROCEDURE dbo.GetIdentityInsert
#dbname sysname
, #schemaname sysname
, #table sysname
, #IdentityInsert VARCHAR(20) OUTPUT
AS
BEGIN
SET NOCOUNT ON
DECLARE #OtherTable nvarchar(max)
DECLARE #DbSchemaTable nvarchar(max)
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
DECLARE #ErrorNumber INT;
DECLARE #object_id INT;
SET #DbSchemaTable = #dbname + '.' + #schemaname + '.' + #table
SET #object_id = OBJECT_ID(#DbSchemaTable)
IF #object_id IS NULL
BEGIN
RAISERROR('table %s doesn''t exist', 16, 1, #DbSchemaTable)
RETURN
END
BEGIN TRY
SET #object_id = OBJECT_ID(#DbSchemaTable)
IF OBJECTPROPERTY(#object_id,'TableHasIdentity') = 0
BEGIN
SET #IdentityInsert = 'NO_IDENTITY'
END
ELSE
BEGIN
-- Attempt to set IDENTITY_INSERT on a temp table. This will fail if any other table
-- has IDENTITY_INSERT set to ON, and we'll process that in the CATCH
CREATE TABLE #GetIdentityInsert(ID INT IDENTITY)
SET IDENTITY_INSERT #GetIdentityInsert ON
SET IDENTITY_INSERT #GetIdentityInsert OFF
DROP TABLE #GetIdentityInsert
-- It didn't fail, so IDENTITY_INSERT on #table must set to OFF
SET #IdentityInsert = 'OFF'
END
END TRY
BEGIN CATCH
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE(),
#ErrorNumber = ERROR_NUMBER();
IF #ErrorNumber = 8107 --IDENTITY_INSERT is already set on a table
BEGIN
SET #OtherTable = SUBSTRING(#ErrorMessage, CHARINDEX(char(39), #ErrorMessage)+1, 2000)
SET #OtherTable = SUBSTRING(#OtherTable, 1, CHARINDEX(char(39), #OtherTable)-1)
IF #OtherTable = #DbSchemaTable
BEGIN
-- If the table name is the same, then IDENTITY_INSERT on #table must be ON
SET #IdentityInsert = 'ON'
END
ELSE
BEGIN
-- If the table name is different, then IDENTITY_INSERT on #table must be OFF
SET #IdentityInsert = 'OFF'
END
END
ELSE
BEGIN
RAISERROR (#ErrorNumber, #ErrorMessage, #ErrorSeverity, #ErrorState);
--THROW Use this if SQL 2012 or higher
END
END CATCH
SELECT [IDENTITY_INSERT] = #IdentityInsert
END
GO
you can also use the ObjectProperty method to determine if a table has an identity:
DECLARE #MyTableName nvarchar(200)
SET #MyTableName = 'TestTable'
SELECT CASE OBJECTPROPERTY(OBJECT_ID(#MyTableName), 'TableHasIdentity')
WHEN 1 THEN 'has identity'
ELSE 'no identity columns'
END as HasIdentity

Create sql trigger dynamically and rollback if error

I'm creating a stored procedure that will create 3 triggers (insert, update, delete) given a table name.
Here is an example to illustrate the issue I'm experiencing :
CREATE PROCEDURE [dbo].[sp_test]
AS
BEGIN
BEGIN TRAN
-- Create trigger 1
DECLARE #sql NVARCHAR(MAX) = 'CREATE TRIGGER test1 ON TableXML AFTER INSERT AS BEGIN END'
EXEC sp_executesql #sql
-- Create trigger 2, but this one will fail because Table_1 contain an ntext field.
SET #sql = 'CREATE TRIGGER test1 ON Table_1 AFTER INSERT AS
BEGIN
select * from inserted
END'
EXEC sp_executesql #sql
COMMIT TRAN
END
So I thought that wrapping the call in a transaction, the first trigger won't be created. Since the second will fail. BUT the first trigger is created anyway .... How can I prevent this from happening. I want the whole thing to be atomics.
Try this, with BEGIN TRY
CREATE PROCEDURE [dbo].[sp_test]
AS
BEGIN
BEGIN TRY
BEGIN TRAN
DECLARE #sql NVARCHAR(MAX) = 'CREATE TRIGGER test1 ON TableXML AFTER INSERT AS BEGIN END'
EXEC sp_executesql #sql
SET #sql = 'CREATE TRIGGER test1 ON Table_1 AFTER INSERT AS
BEGIN
select * from inserted
END'
EXEC sp_executesql #sql
COMMIT TRAN
END TRY
BEGIN CATCH
RAISERROR('Errormessage', 18, 1)
ROLLBACK TRAN
END CATCH
END
You have no error handling or rollback statement in your procedure.
In some databases, DDL statements such as CREATE TRIGGER will automatically commit themselves; if sql-server is one of them, you can't. (This is true of Oracle and MySQL; not true of RDB; not sure about sql-server.)
You don't have a Rollback call on an error.
Using SQL Server's Try/Catch, you could do something like what Vidar mentioned, or you if Sql Server automatically commits triggers (as Brian H mentioned as a posibility) you could instead have in your Catch block:
BEGIN CATCH
RAISERROR('Errormessage', 18, 1)
DROP Trigger test1
END CATCH