"Invalid column name" thrown inside unreachable script block - sql-server

I have a large sql script designed so that it is 'safe' to run on different instances, or on the same instance multiple times without causing any bad data or errors
When writing such scripts I have always depended on syntax like:
if not exists (select 1 from SYS.FOREIGN_KEYS where NAME = 'FK_BAR')
begin
alter table [MyTable]
add constraint FK_BAR foreign key (some_id) references Other_Table(some_id)
end
go
This usually works pretty well. However, I recently encountered a scenario where I am unable to prevent errors from being thrown during execution. In the below code, the column "deprecated_column" has been dropped from table Foo:
if exists (select 1 from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'Foo' and COLUMN_NAME = 'deprecated_column')
and exists (select 1 from [Foo] where new_column is null)
begin
declare #updated int set #updated = 1;
while #updated > 0
begin
update top (500000)
Foo
set new_column = deprecated_column
where new_column is null
;
set #updated = ##rowcount;
end
end
go
If I run the two exist's within the if independently of this program, they return "no results" as expected, which means the inner code will never be executed. However, when I run the script, the server throws the error: Invalid column name 'deprecated_column'., and the script is marked as completing with error, which causes an alert to be raised on our system (ie, the DBA is notified and has to check), which is causing some unnecessary overhead on what should be a simple automated task.
Is there some syntax that I have overlooked that would allow this code to run without error in all cases?

As I explained in the comments, T-SQL is a compiled language and "Invalid Column" is a compiler error, not an execution error. However, since most T-SQL is only compiled right before execution, that's when you usually see it.
Since T-SQL tries to compile all of the code regardless of IF branches, the only immediate way around this is with dynamic SQL. Like so:
if exists (select 1 from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = 'Foo' and COLUMN_NAME = 'deprecated_column')
and exists (select 1 from [Foo] where new_column is null)
begin
EXEC('
declare #updated int set #updated = 1;
while #updated > 0
begin
update top (500000)
Foo
set new_column = deprecated_column
where new_column is null
;
set #updated = ##rowcount;
end
');
end
go

Related

The database generated a key that is already in use, caused by Trigger?

I have a legacy production software that uses LINQ to SQL, and it's own database. I wanted to create a trigger on one of the tables in that database and have it do a few joins and keep a field in another database current with its values. What has happened now is with the trigger I get this error in my legacy application:
"The database generated a key that is already in use."
If I remove the trigger, everthing works as per usual.
Here is the trigger:
BEGIN
SET NOCOUNT ON;
DECLARE #action as char(1);
if exists(SELECT * from inserted) and exists (SELECT * from deleted)
begin
SET #action = 'UPDATE';
INSERT INTO ste.dbo.Logs([LogEvent],[RaisedBy],[LogTime])
SELECT
itemID,
'ListLineItem (' + ListLineItemID + '): ' + #action,
GETDATE()
FROM INSERTED
end
If exists (Select * from inserted) and not exists(Select * from deleted)
begin
SET #action = 'INSERT';
update soli
set soli.itemRefNumber = wo.RefNum,
soli.itemIssueDate = wo.IssueDate
from ste.dbo.ListLineItems soli
join INSERTED woli on soli.ListLineTxnID = woli.ListLineItemID
join cse.dbo.item wo on woli.itemID = wo.IDKey
SELECT
itemID,
'ListLineItem (' + ListLineItemID + '): ' + #action,
GETDATE()
FROM INSERTED
end
If exists(select * from deleted) and not exists(Select * from inserted)
begin
SET #action = 'DELETE';
update soli
set soli.itemRefNumber = null,
soli.itemIssueDate = '19000101'
from ste.dbo.ListLineItems soli
join deleted woli on soli.ListLineTxnID = woli.ListLineItemID
join cse.dbo.item wo on woli.itemID = wo.IDKey
SELECT
itemID,
'ListLineItem (' + ListLineItemID + '): ' + #action,
GETDATE()
FROM deleted
end
END
Thoughts?
In place of auto generated primary key code should use guuid. As I think your application is running in multi threaded mode with a race condition between multiple thread leading to this issue.
Learn to post useful information. Why do you cut off the actual declaration of the trigger name, table, and type? You also cut off the last part of the trigger code. Next, post the complete text of all errors - not your interpretation. Finally, read all you can about triggers and problems the inexperienced find trying to write them.
Your problems? In your insert and delete blocks, you attempt to return a resultset - DON'T. Did you copy/paste incorrectly? And what did you intend when you defined #action as a single character string? You don't really use it as a variable so why add the complexity of assigning a multi-character string to it when a literal can be used?
Lastly, the error message you posted sounds like it was generated by your application. The most likely explanation is that the app "sees" the resultset of your trigger and misinterprets the information.
And one last comment - what happens when a merge statement statement causes the trigger to execute? Will it work correctly and record the appropriate information? Be careful what you assume and how you test.

Can't seem to compare to varchar in SQL Server

I'm trying to make sure that when people create tables it starts with the prefix tbl
Here is what I did as of now:
CREATE TRIGGER trg_tbl ON DATABASE
FOR CREATE_TABLE
AS
DECLARE #name VARCHAR(25)
SET #name = (SELECT TOP 1 name
FROM sys.tables
ORDER BY create_date DESC)
IF (SELECT SUBSTRING(#name, 1, 3) != 'tbl'
PRINT 'Tables must begin with the prefix tbl'
ROLLBACK
GO
The problem is it doesn't let me use != operator. I tried using = <> or LIKE but nothing seems to work it keeps telling me that the syntax is incorrect please help I looked everywhere online and everybody says that = or LIKE work. :(
Though the other answer explain the issue in your code. I will suggest you to use Eventdata() function to retrieve table name
Also your DDL trigger will rollback every Create Table action even though the table name starts with tb1. You need to apply rollback only when table name not starts with tb1 move the rollback command inside the IF condition.
Use BEGIN-END block when IF condition has more than one statement else the first statement alone will considered inside the IF condition.
CREATE TRIGGER trg_tbl
ON DATABASE
FOR CREATE_TABLE
AS
BEGIN
SET NOCOUNT ON
DECLARE #TABLE_NAME SYSNAME
SELECT #TABLE_NAME = Eventdata().value('(/EVENT_INSTANCE/ObjectName)[1]', 'SYSNAME')
IF LEFT (#TABLE_NAME, 3) != 'tbl'
BEGIN
PRINT 'Tables must begin with the prefix tbl'
ROLLBACK
END
END
GO
Error is because of a missing closing bracket at:
IF (SELECT SUBSTRING(#name,1,3) != 'tbl'
can be fixed by adding the missing closing bracket:
IF (SELECT SUBSTRING(#name,1,3)) != 'tbl'
However, you don't need to do a select to perform substring.
You can do this:
IF SUBSTRING(#name,1,3) != 'tbl'

How do I catch a specific exception type from within a stored procedure?

I am writing a stored procedure which iterates over all of the databases on the server and populates a table variable with an aggregate of the data from some of the different databases. Some databases I'm not interested in as they are irrelevant. The problem is when my CURSOR iterates through those databases I don't care about, a SELECT statement is issued on a table that doesn't exist. How can I ignore the Invalid object name exception and continue with my processing?
Edit:
Here is how I was attempting to skip over databases that were irrelevant:
DECLARE db_cursor CURSOR FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name NOT IN ('master','model','msdb','tempdb')
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #currentDatabaseName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'SELECT COUNT(Name) FROM ' + #currentDatabaseName + '.sys.Tables WHERE Name = ''SomeTableICareAbout'''
INSERT INTO #tableSearchResult
EXEC sp_executesql #sql
SET #tableCount = (SELECT COUNT(*) FROM #tableSearchResult WHERE TableCount = 1)
--If the table I care about was found, then do the good stuff
IF #tableCount > 0
...
The problem with this approach is if the executing user (in my case a service account) does not have access to SELECT on the table, then I never know about that error. If the user doesn't have SELECT access, I want that exception to be raised. But, even if the user doesn't have SELECT access, it can SELECT on the sys.Tables view.
You can't catch error 208 directly because it's a name resolution error that is raised at compilation time and before the code is actually executed. The behaviour is documented: see the section called "Errors Unaffected by a TRY…CATCH Construct" for an explanation, and the answers to this question have some interesting comments.
In addition to the 'solution' in the documentation, you can use dynamic SQL; the error will be caught in this example:
begin try
exec('select * from dbo.ThisTableDoesNotExist');
end try
begin catch
select error_number();
end catch;
If you're looping through all databases, there's a good chance you're using dynamic SQL somewhere anyway, so this might suit your case better.
You can catch the error if you are doing it inside a stored procedure (Example documented Here: http://msdn.microsoft.com/en-us/library/ms175976.aspx
Also you can change your dynamic sql to do something like this
SET #sql = '
If Exists(Select Name From ' + #currentDatabaseName + '.sys.Tables
WHERE Name = ''SomeTableICareAbout'')' --+
--Add Whatever the Good Stuff is
EXEC sp_executesql #sql
But checking if the table exists first, instead of doing the select count(1) from the table, will prevent that error from being raised.

Stored Procedure Does Not Fire Last Command

On our SQL Server (Version 10.0.1600), I have a stored procedure that I wrote.
It is not throwing any errors, and it is returning the correct values after making the insert in the database.
However, the last command spSendEventNotificationEmail (which sends out email notifications) is not being run.
I can run the spSendEventNotificationEmail script manually using the same data, and the notifications show up, so I know it works.
Is there something wrong with how I call it in my stored procedure?
[dbo].[spUpdateRequest](#packetID int, #statusID int output, #empID int, #mtf nVarChar(50)) AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE #id int
SET #id=-1
-- Insert statements for procedure here
SELECT A.ID, PacketID, StatusID
INTO #act FROM Action A JOIN Request R ON (R.ID=A.RequestID)
WHERE (PacketID=#packetID) AND (StatusID=#statusID)
IF ((SELECT COUNT(ID) FROM #act)=0) BEGIN -- this statusID has not been entered. Continue
SELECT ID, MTF
INTO #req FROM Request
WHERE PacketID=#packetID
WHILE (0 < (SELECT COUNT(ID) FROM #req)) BEGIN
SELECT TOP 1 #id=ID FROM #req
INSERT INTO Action (RequestID, StatusID, EmpID, DateStamp)
VALUES (#id, #statusID, #empID, GETDATE())
IF ((#mtf IS NOT NULL) AND (0 < LEN(RTRIM(#mtf)))) BEGIN
UPDATE Request SET MTF=#mtf WHERE ID=#id
END
DELETE #req WHERE ID=#id
END
DROP TABLE #req
SELECT #id=##IDENTITY, #statusID=StatusID FROM Action
SELECT TOP 1 #statusID=ID FROM Status
WHERE (#statusID<ID) AND (-1 < Sequence)
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
END ELSE BEGIN
SET #statusID = -1
END
DROP TABLE #act
END
Idea of how the data tables are connected:
From your comments I get you do mainly C# development. A basic test is to make sure the sproc is called with the exact same arguments you expect
PRINT '#packetID: ' + #packetID
PRINT '#statusID: ' + #statusID
EXEC spSendEventNotificationEmail #packetID, #statusID, 'http:\\cpweb:8100\NextStep.aspx'
This way you 1. know that the exec statement is reached 2. the exact values
If this all works than I very good candidate is that you have permission to run the sproc and your (C#?) code that calls it doesn't. I would expect that an error is thrown tough.
A quick test to see if the EXEC is executed fine is to do an insert in a dummy table after it.
Update 1
I suggested to add PRINT statements but indeed as you say you cannot (easily) catch them from C#. What you could do is insert the 2 variables in a log table that you newly create. This way you know the exact values that flow from the C# execution.
As to the why it now works if you add permissions I can't give you a ready answer. SQL security is not transparent to me either. But its good to research yourself a but further. Do you have to add both guest and public?
It would also help to see what's going inside spSendEventNotificationEmail. Chances are good that sproc is using a resource where it didn't have permission before. This could be an object like a table or maybe another sproc. Security is heavily dependent on context/settings and not an easy problem to tackle with a Q/A site like SO.

SQL script runs fine on one database, errors on another

We have a script that must allow for being re-run several times.
We have an MS-SQL script that updates a table if a (now obsolete) column exists, then deletes the column. To ensure that the script can be run several times, it first checks for the existence of a column before performing the updates.
The script works as expected on our dev database, updating the data on the first run, then displaying the message 'Not updating' on subsequent runs.
On our test database the script runs fine on the first run, but errors with "Invalid column name 'OldColumn'" on subsequent runs; if I comment out the UPDATE and ALTER statements it runs as expected.
Is there a way to force the script to run even if there's a potential error, or is it something to do with how the database was set-up? (fingers crossed I'm not looking like a complete noob!)
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'OldColumn')
BEGIN
PRINT 'Updating and removing old column...'
UPDATE MyTable SET NewColumn='X' WHERE OldColumn=1;
ALTER TABLE MyTable DROP COLUMN OldColumn;
END
ELSE
PRINT 'Not updating'
GO
As a work around you could do
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable' AND COLUMN_NAME = 'OldColumn')
BEGIN
PRINT 'Updating and removing old column...'
EXEC ('UPDATE MyTable SET NewColumn=''X'' WHERE OldColumn=1;');
ALTER TABLE MyTable DROP COLUMN OldColumn;
END
ELSE
PRINT 'Not updating'

Resources