Transact SQL - Rollback, Variable Scope, Exiting - sql

The somewhat minimal minimal version of what I'm trying to do boils down to this:
declare #VERSION_TO NVARCHAR(255);
declare #VERSION_CURRENT NVARCHAR(255);
set #VERSION_TO = '3.0.4401';
select #VERSION_CURRENT = VERSION from T_SYSTEM_INFO;
print 'target version: ' + #VERSION_TO
print 'current version: ' + #VERSION_CURRENT
if not #VERSION_CURRENT IN ('3.0.4300')
begin
raiserror( 'Patch not possible - unknown predecessor "%s"', 17, 10, #VERSION_CURRENT)
return
end
alter table T_JOB add CREATED [datetime];
update T_JOB set CREATED = (select top 1 STEP_START from T_JOB_STEP where T_JOB_STEP.JOB = T_JOB.ID and T_JOB_STEP.ID not in (select STEP from T_JOB_STEP_DEPENDENCY));
update T_JOB set CREATED = '01-01-2000' where T_JOB.CREATED is null;
alter table T_JOB alter column CREATED [datetime] not null;
update T_SYSTEM_INFO set VERSION = #VERSION_TO;
go
but it fails on the update after the first ALTER TABLE:
Msg 207, Level 16, State 1, Line 0
Invalid column name 'CREATED'.
if I put a 'go' after the ALTER TABLE, it works, but fails on the last UPDATE:
Msg 137, Level 15, State 2, Line 7
Must declare the scalar variable "#VERSION_TO".
I understand the scope of variables is limited by the go statements. I only want to have one at the end, anyways, so changes are rolled back if anything goes wrong. And if none of this is possible, how can I exit the whole script if there is an error, not just the transaction?
could anybody google the answer for me, please? :)
thanks

Can you not just redefine #VERSION_TO and set it after your alter table DDL?
NOTE: transactions and DDL don't mix, i.e. you can't rollback an alter table.
Have you looked at sqsh? it is a SQL "shell" to replace isql that could help a lot here.

Related

Not Able to Create Trigger on SQL Table due to 'The specified event type(s) is/are not valid on the specified target object.'

I am trying to create a trigger on a table and I am encountering the issue above as a syntax error in SQL studios and get the below error when running the SQL statement:
Msg 102, Level 15, State 1, Procedure UpdatePo, Line 9 [Batch Start Line 18]
Incorrect syntax near 'INSERT'
Here is the trigger I am trying to create:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER dbo.tiTrigger
ON dbo.TABLE_ONE
AFTER [INSERT]
AS
SET NOCOUNT ON;
DECLARE #po_state VARCHAR(20)
DECLARE #po_number VARCHAR(255)
SELECT #po_state = order_state FROM inserted
SELECT #po_number = po_number FROM inserted
UPDATE TABLE_TWO
SET PO_Status = #po_state
WHERE PO_Number = #po_number
GO
The reason this failed is you need to remove the square brackets around INSERT, it is not an object name.
However, your trigger is broken. It has a MAJOR logical flaw that only one row will ever be inserted. Triggers in sql server fire once per operation. You need to rewrite this as a set based approach. This simple update can and should be done without any variables at all, just a simple join.
Doing this set based would be something like this.
CREATE TRIGGER dbo.tiTrigger
ON dbo.TABLE_ONE
AFTER INSERT
AS
SET NOCOUNT ON;
UPDATE t
set PO_Status = i.order_state
from TABLE_TWO t
join inserted i on i.po_number = t.PO_Number
GO

How to return multiple errors in a single Query

My goal is to catch error message from SQL query, log or print then pass it instead of letting it generate a real error. but I found it's not possible to catch multiple errors from the examining query; only the last error will be caught:
DECLARE #ErrorMessage varchar(1000)
BEGIN TRY
EXEC('SELECT AA,BB FROM TABLE')--neither column AA nor BB exists
END TRY
BEGIN CATCH
SET #ErrorMessage = 'ERRORMESSAGE: ' + Error_Message()
PRINT #ErrorMessage
END CATCH
The query will only give feedback that column BB cannot found, but cannot show that AA column also doesn't exist.
Or another example, by putting this query in TRY block
EXEC('CREATE SCHEMA abc AUTHORIZATION [dbo]') --schema abc already exists
It will acutally raise error 'schema already exists' first, then another error 'cannot create schema, see previous error', but now the 1st key error containing key information has been 'eaten'.
How to show all of the error messages then?
YOU CAN STILL USE RAISERROR INSIDE TRY-CATCH BLOCKS
Ivan is right about ERROR_MESSAGE and how TRY-CATCH may remove the robust nature of your query, however, this only occurs when the SEVERITY of the message is above 10 in a TRY block. So the trick is to set the severity under 11.
The error is returned to the caller if RAISERROR is run:
Outside the scope of any TRY block.
With a severity of 10 or lower in a TRY block.
With a severity of 20 or higher that terminates the database
connection.
MSDN - RAISERROR
RAISERROR can be used as a substitute for PRINT and allows for custom messages. Furthermore, you can set the STATE to different numbers to keep track of similar, but different errors in your code.
Since Fatal errors will be your bane, I suggest you test queries and DDL commands before running them. For example, instead of blindly attempting EXEC('CREATE SCHEMA abc AUTHORIZATION [dbo]'), you can try this ad-hoc message instead:
DECLARE #SCHEMA NVARCHAR(10)
DECLARE #Message NVARCHAR(255)
SET #SCHEMA = N'abc'
SET #Message = N'The Schema ' + #SCHEMA + ' already exists.'
IF SCHEMA_ID(#SCHEMA) IS NOT NULL
EXEC('CREATE SCHEMA abc AUTHORIZATION [dbo]')
ELSE RAISERROR(#Message, 10, 1)
--result: The Schema abc already exists.
There are many ways of checking the validity of dynamic SQL, DDL, and DML, including useful functions like OBJECT_ID, OBJECT_NAME, DATABASE_ID, etc where you test safely, and then run the appropriate RAISERROR message for each error.
Remove TRY-CATCH, if possible - divide script statements into many separate batches with GO.
TRY-CATCH reacts on first exception and breaks execution of TRY-block:
If an error occurs in the TRY block, control is passed to another
group of statements that is enclosed in a CATCH block.
https://msdn.microsoft.com/en-us/library/ms175976.aspx
So behaviour of TRY-CATCH is rather opposite to your intention.
GO sets the end of the batch. Many of errors don't even break the batch, because they have low severity, so for some cases there is no need even to split script into many batches.
As an example here is sample dummy script for testing or some utility purpose (not for production of course) that generates many errors:
create proc SomeProc as
begin
exec('select uknown from non_existent')
end
GO
drop table #test1
drop table #test2
GO
drop table #test3
GO
create table #test1 (id int primary key)
insert into #test1(id)
exec SomeProc
insert into #test
values (1)
insert into #test1
values (1)
GO
insert into #test1
values (11)
insert into #test1
values (11)
insert into #test
values (22)
GO
select * from #test1
GO
drop table #test
GO
drop table #test
drop proc SomeProc
select object_id('SomeProc', 'P')
GO
it does give the output of selects:
and all the messages:
Msg 3701, Level 11, State 5, Line 7 Cannot drop the table '#test2',
because it does not exist or you do not have permission.
Msg 3701,
Level 11, State 5, Line 9 Cannot drop the table '#test3', because it
does not exist or you do not have permission.
Msg 208, Level 16, State
1, Line 11 Invalid object name 'non_existent'.
(0 row(s) affected)
Msg 208, Level 16, State 0, Line 16 Invalid object
name '#test'.
(1 row(s) affected)
Msg 2627, Level 14, State 1, Line 25 Violation of
PRIMARY KEY constraint 'PK__#test1____3213E83FF35979C1'. Cannot insert
duplicate key in object 'dbo.#test1'. The duplicate key value is (11).
The statement has been terminated.
Msg 208, Level 16, State 0, Line 28
Invalid object name '#test'.
(1 row(s) affected)
Msg 3701, Level 11, State 5, Line 33 Cannot drop
the table '#test', because it does not exist or you do not have
permission.
Msg 3701, Level 11, State 5, Line 35 Cannot drop the table
'#test', because it does not exist or you do not have permission.
"My goal is to catch error message from SQL query, log or print then pass it instead of letting it generate a real error." - if "print" is ok then just remove TRY-CATCH.
Run the script through sqlcmd and redirect errors to a file:
How to get SQLCMD to output errors and warnings only.
sqlcmd -i Script.sql -E -r1 1> NUL

Create trigger error: 'Incorrect Syntax near 'dbo'

I'm currently learning some regarding SQL triggers. I am trying to create a column on a test table which will show the current date when the row is updated. The column datatype is type DateTime So far I have:
CREATE TRIGGER lastUpdated
ON [dbo].[ImportTest]
AFTER UPDATE
AS
BEGIN
IF NOT UPDATE(LAST_UPD)
BEGIN
SET [dbo].[ImportTest].[LAST_UPD] = CURRENT_TIMESTAMP
END
END
GO
However, I get the following error trying to execute:
Msg 102, Level 15, State 1, Procedure lastUpdated, Line 29
Incorrect syntax near 'dbo'.
Any help would be appreciated.
You cannot update a column like assigning value to variable. Try this
IF NOT UPDATE(LAST_UPD)
BEGIN
UPDATE IT
SET [LAST_UPD] = CURRENT_TIMESTAMP
FROM [dbo].[ImportTest] IT
WHERE EXISTS (SELECT 1
FROM inserted i
WHERE i.somecol = it.somecol
And ...)
END

Trouble updating new column after adding it

Given the following SQL:
IF EXISTS (SELECT * FROM sys.columns WHERE name = 'NewFieldName' AND object_id = OBJECT_ID('dbo.MyTableName'))
RETURN
-- Add NewFieldName column to part of the Summer 2012 release cycle.
ALTER TABLE dbo.[MyTableName] ADD
[NewFieldName] SmallINT NOT NULL
CONSTRAINT DF_MyTableName_NewFieldName DEFAULT (2)
UPDATE [MyTableName] SET NewFieldName = 1 WHERE [Name] = 'FindMe' --Update one specific value
Produces the following error message:
Msg 207, Level 16, State 1, Line 10 Invalid column name
'NewFieldName'.
I'm sure I'm missing something basic, but trying to put "GO" after the alter makes the UPDATE run everytime and I don't want to do that.
How can I structure this statement so that it will check to see if the column exists and, if it doesn't add it and then set the values as stated in my UPDATE statements?
You need the statement referencing the new column to be compiled after the new column is added. One way of doing this is to run it as a child batch with EXEC.
IF NOT EXISTS (SELECT *
FROM sys.columns
WHERE name = 'NewFieldName'
AND object_id = OBJECT_ID('dbo.MyTableName'))
BEGIN
-- Add NewFieldName column to part of the Summer 2012 release cycle.
ALTER TABLE dbo.[MyTableName]
ADD [NewFieldName] SMALLINT NOT NULL
CONSTRAINT DF_MyTableName_NewFieldName DEFAULT (2)
EXEC(' UPDATE [MyTableName] SET NewFieldName = 1 WHERE [Name] = ''FindMe''')
END
The reason it worked for you originally is presumably because the table itself did not exist when the batch was compiled thus meaning that all statements in it referencing the table are subject to deferred compile.

Stored Procedure consist Add column, Update data for that column, and Select all data from that table

I've written a stored procedure as following:
CREATE PROC spSoNguoiThan
#SNT int
AS
begin
IF not exists (select column_name from INFORMATION_SCHEMA.columns where
table_name = 'NhanVien' and column_name = 'SoNguoiThan')
ALTER TABLE NhanVien ADD SoNguoiThan int
else
begin
UPDATE NhanVien
SET NhanVien.SoNguoiThan = (SELECT Count(MaNguoiThan)FROM NguoiThan
WHERE MaNV=NhanVien.MaNV
GROUP BY NhanVien.MaNV)
end
SELECT *
FROM NhanVien
WHERE SoNguoiThan>#SNT
end
GO
Then I get the error :
Server: Msg 207, Level 16, State 1, Procedure spSoNguoiThan, Line 12
Invalid column name 'SoNguoiThan'.
Server: Msg 207, Level 16, State 1, Procedure spSoNguoiThan, Line 15
Invalid column name 'SoNguoiThan'.
Who can help me?
Thanks!
When the stored proc is parsed during CREATE the column does not exist so you get an error.
Running the internal code line by line works because they are separate. The 2nd batch (UPDATE) runs because the column exists.
The only way around this would be to use dynamic SQL for the update and select so it's not parsed until EXECUTE time (not CREATE time like now).
However, this is something I really would not do: DDL and DML in the same bit of code
I ran into this same issue and found that in addition to using dynamic sql I could solve it by cross joining to a temp table that had only one row. That caused the script compiler to not try to resolve the renamed column at compile time. Below is an example of what I did to solve the issue without using dynamic SQL
select '1' as SomeText into #dummytable
update q set q.ValueTXT = convert(varchar(255), q.ValueTXTTMP) from [dbo].[SomeImportantTable] q cross join #dummytable p