Strange "There is already an object in the database." error - sql-server

I have the following SQL code:
IF OBJECT_ID( 'tempdb..#PropList') IS NOT NULL
DROP TABLE #PropList
DECLARE #Split CHAR(1), #propList NVARCHAR(MAX), #PropListXml XML
SET #Split = ','
SET #propList = 'NAME,DESCRIPTION'
-- SET #propList = ''
IF (#propList IS NOT NULL AND #propList != '')
BEGIN
SET #PropListXml = CONVERT(XML,'<root><s>' + REPLACE(#propList, #Split, '</s><s>') + '</s></root>')
SELECT SystemName = T.c.VALUE('.','nvarchar(36)')
INTO #PropList
FROM #PropListXml.nodes('/root/s') T(c)
END
ELSE
BEGIN
SELECT SystemName
INTO #PropList -- Stops here
FROM tblProperty
END
SELECT * FROM #PropList
Regardless of the value of #propList, this code always stops at the indicated line with this error:
There is already an object named '#PropList' in the database.
My expectation was that only one of the two IF blocks is executed, therefore there should be only one attempt to create the table with the SELECT... INTO statement. Why is this failing?

As per comment, you'll need to explicitly define your #temp table before the IF statement, then change your
SELECT ... INTO #temp
to be
INSERT INTO #temp SELECT ...
This is because when SQL Server validates the query it ignores any control flow statements. For more detail on this see the following SO question:
T-Sql appears to be evaluating "If" statement even when the condition is not true

Related

SQL Server Conditional Select Into Issue [duplicate]

I have a T-Sql script where part of this script checks to see if a certain column exists in the a table. If so, I want it to execute a routine... if not, I want it to bypass this routine. My code looks like this:
IF COL_LENGTH('Database_Name.dbo.Table_Name', 'Column_Name1') IS NOT NULL
BEGIN
UPDATE Table_Name
SET Column_Name2 = (SELECT Column_Name3 FROM Table_Name2
WHERE Column_Name4 = 'Some Value')
WHERE Column_Name5 IS NULL;
UPDATE Table_Name
SET Column_Name6 = Column_Name1
WHERE Column_Name6 IS NULL;
END
My problem is that even when COL_LENGTH of Column_Name1 is null (meaning it does not exist) I am still getting an error telling me "Invalid column name 'Column_Name1'" from the 2nd UPDATE statement in the IF statement. For some reason this IF condition is still being evaluated even when the condition is FALSE and I don't know why.
SQL Server parses the statement and validates it, ignoring any if conditionals. This is why the following also fails:
IF 1 = 1
BEGIN
CREATE TABLE #foo(id INT);
END
ELSE
BEGIN
CREATE TABLE #foo(id INT);
END
Whether you hit Execute or just Parse, this results in:
Msg 2714, Level 16, State 1
There is already an object named '#foo' in the database.
SQL Server doesn't know or care which branch of a conditional will be entered; it validates all of the statements in a batch anyway. You can do things like (due to deferred name resolution):
IF <something>
BEGIN
SELECT foo FROM dbo.Table_That_Does_Not_Exist;
END
But you can't do:
IF <something>
BEGIN
SELECT column_that_does_not_exist FROM dbo.Table_That_Does;
END
The workaround, typically, is to use dynamic SQL:
IF <something>
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT column_that_does_not_exist FROM dbo.Table_That_Does;';
EXEC sp_executesql #sql;
END
Make your statement a string. And if column exists, execute it
IF COL_LENGTH('Database_Name.dbo.Table_Name', 'Column_Name1') IS NOT NULL
BEGIN
DECLARE #sql VARCHAR(MAX)
SET #sql = 'UPDATE Table_Name
SET Column_Name2 = (SELECT Column_Name3 FROM Table_Name2
WHERE Column_Name4 = ''Some Value'')
WHERE Column_Name5 IS NULL;
UPDATE Table_Name
SET Column_Name6 = Column_Name1
WHERE Column_Name6 IS NULL;'
EXEC(#sql)
END

Stored Procedure only fully works in SSMS

I have a very simple check procedure that takes in a comma delimited string and verifies that records with that key have the correct status. At the end of the procedure an audit is created with the comma delimited string,a count of items in the string and a count of updates made. The audit always reflects 0 updates. However when I run the procedure in SSMS with the same login as the job that runs the procedure I get updates. I know there is something simple occurring but cannot see it. The routine is specified below
BEGIN
SET NOCOUNT ON;
declare #loads Table(shipmentnumber varchar(20))
declare #passinfo Table (ID int, fieldname varchar(50), field nvarchar(max))
select #info = replace(#info, '"','')
declare #cnt1 int, #cnt2 int, #error varchar(100)
insert into #loads
select rtrim(ltrim(splitdata)) from dbo.fnsplitstring(#info, ',')
set #cnt1 = ##rowcount
update loads
set loadstatus = 102
where shipmentnumber in (select shipmentnumber from #loads)
and loadstatus <> 102
set #cnt2 = ##rowcount
INSERT INTO [dbo].[ShipperSPAudit]
([SP]
,[parm1]
,[parm2],parm3,parm4
,[requesteddate]
, userid)
VALUES
('CPCheckCancel'
,cast(#shipper as varchar)
,#info, cast(#cnt1 as varchar), cast(#cnt2 as varchar)
,getdate()
,#userid)
END
I dont have a rational explanation to why it would work in SSMS and not as a scheduled job. Changed the line of code that reads select rtrim(ltrim(splitdata)) from dbo.fnsplitstring(#info, ',') to select substring(splitdata,1,8) from dbo.fnsplitstring(#info, ',') as I know the incoming comma delimited fields should be 8 characters in length. The code now works when running as a job and when running in SSMS

Show a few columns in a stored procedure based on a parameter

I have a parameter in my stored procedure called internal. If "internal" = yes then I want to display an additional 2 columns in my results. If it's no I don't want to display these columns.
I can do a case statement and then I can set the column to be empty but the column name will still be returned in the results.
My questions are:
Is there a way not to return these 2 columns in the results at all?
Can I do it in one case statement and not a separate case statement for each column?
Thank you
No, CASE is a function, and can only return a single value.
And According to your comment:-
The issue with 2 select statements are that it's a major complicated
select statement and I really don't want to have to have the whole
select statement twice.
so you can use the next approach for avoid duplicate code:-
Create procedure proc_name (#internal char(3), .... others)
as
BEGIN
declare #AddationalColumns varchar(100)
set #AddationalColumns = ''
if #internal = 'Yes'
set #AddationalColumns = ',addtionalCol1 , addtionalCol2'
exec ('Select
col1,
col2,
col3'
+ #AddationalColumns +
'From
tableName
Where ....
' )
END
Try IF Condition
IF(internal = 'yes')
BEGIN
SELECT (Columns) FROM Table1
END
ELSE
BEGIN
SELECT (Columns With additional 2 columns) FROM Table1
END
You can do something like this solution. It allows you to keep only one copy of code if it's so important but you will have to deal with dynamic SQL.
CREATE TABLE tab (col1 INT, col2 INT, col3 INT);
GO
DECLARE #internal BIT = 0, #sql NVARCHAR(MAX)
SET #sql = N'SELECT col1 ' + (SELECT CASE #internal WHEN 1 THEN N', col2, col3 ' ELSE N'' END) + N'FROM tab'
EXEC sp_executesql #sql
GO
DROP TABLE tab
GO
Another option is to create a 'wrapper' proc. Keep your current one untouched.
Create a new proc which executes this (pseudo code):
create proc schema.wrapperprocname (same #params as current proc)
as
begin
Create table #output (column list & datatypes from current proc);
insert into #output
exec schema.currentproc (#params)
if #internal = 'Yes'
select * from #output
else
select columnlist without the extra 2 columns from #output
end
This way the complex select statement remains encapsulated in the original proc.
Your only overhead is keeping the #output table & select lists in in this proc in sync with any changes to the original proc.
IMO it's also easier to understand/debug/tune than dynamic sql.

T-Sql appears to be evaluating "If" statement even when the condition is not true

I have a T-Sql script where part of this script checks to see if a certain column exists in the a table. If so, I want it to execute a routine... if not, I want it to bypass this routine. My code looks like this:
IF COL_LENGTH('Database_Name.dbo.Table_Name', 'Column_Name1') IS NOT NULL
BEGIN
UPDATE Table_Name
SET Column_Name2 = (SELECT Column_Name3 FROM Table_Name2
WHERE Column_Name4 = 'Some Value')
WHERE Column_Name5 IS NULL;
UPDATE Table_Name
SET Column_Name6 = Column_Name1
WHERE Column_Name6 IS NULL;
END
My problem is that even when COL_LENGTH of Column_Name1 is null (meaning it does not exist) I am still getting an error telling me "Invalid column name 'Column_Name1'" from the 2nd UPDATE statement in the IF statement. For some reason this IF condition is still being evaluated even when the condition is FALSE and I don't know why.
SQL Server parses the statement and validates it, ignoring any if conditionals. This is why the following also fails:
IF 1 = 1
BEGIN
CREATE TABLE #foo(id INT);
END
ELSE
BEGIN
CREATE TABLE #foo(id INT);
END
Whether you hit Execute or just Parse, this results in:
Msg 2714, Level 16, State 1
There is already an object named '#foo' in the database.
SQL Server doesn't know or care which branch of a conditional will be entered; it validates all of the statements in a batch anyway. You can do things like (due to deferred name resolution):
IF <something>
BEGIN
SELECT foo FROM dbo.Table_That_Does_Not_Exist;
END
But you can't do:
IF <something>
BEGIN
SELECT column_that_does_not_exist FROM dbo.Table_That_Does;
END
The workaround, typically, is to use dynamic SQL:
IF <something>
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'SELECT column_that_does_not_exist FROM dbo.Table_That_Does;';
EXEC sp_executesql #sql;
END
Make your statement a string. And if column exists, execute it
IF COL_LENGTH('Database_Name.dbo.Table_Name', 'Column_Name1') IS NOT NULL
BEGIN
DECLARE #sql VARCHAR(MAX)
SET #sql = 'UPDATE Table_Name
SET Column_Name2 = (SELECT Column_Name3 FROM Table_Name2
WHERE Column_Name4 = ''Some Value'')
WHERE Column_Name5 IS NULL;
UPDATE Table_Name
SET Column_Name6 = Column_Name1
WHERE Column_Name6 IS NULL;'
EXEC(#sql)
END

How to detect interface break between stored procedure

I am working on a large project with a lot of stored procedures. I came into the following situation where a developer modified the arguments of a stored procedure which was called by another stored procedure.
Unfortunately, nothing prevents the ALTER PROC to complete.
Is there a way to perform those checks afterwards ?
What would be the guidelines to avoid getting into that kind of problems ?
Here is a sample code to reproduce this behavior :
CREATE PROC Test1 #arg1 int
AS
BEGIN
PRINT CONVERT(varchar(32), #arg1)
END
GO
CREATE PROC Test2 #arg1 int
AS
BEGIN
DECLARE #arg int;
SET #arg = #arg1+1;
EXEC Test1 #arg;
END
GO
EXEC Test2 1;
GO
ALTER PROC Test1 #arg1 int, #arg2 int AS
BEGIN
PRINT CONVERT(varchar(32), #arg1)
PRINT CONVERT(varchar(32), #arg2)
END
GO
EXEC Test2 1;
GO
DROP PROC Test2
DROP PROC Test1
GO
Sql server 2005 has a system view sys.sql_dependencies that tracks dependencies. Unfortunately, it's not all that reliable (For more info, see this answer). Oracle, however, is much better in that regard. So you could switch. There's also a 3rd party vendor, Redgate, who has Sql Dependency Tracker. Never tested it myself but there is a trial version available.
I have the same problem so I implemented my poor man's solution by creating a stored procedure that can search for strings in all the stored procedures and views in the current database. By searching on the name of the changed stored procedure I can (hopefully) find EXEC calls.
I used this on sql server 2000 and 2008 so it probably also works on 2005. (Note : #word1, #word2, etc must all be present but that can easily be changed in the last SELECT if you have different needs.)
CREATE PROCEDURE [dbo].[findWordsInStoredProceduresViews]
#word1 nvarchar(4000) = null,
#word2 nvarchar(4000) = null,
#word3 nvarchar(4000) = null,
#word4 nvarchar(4000) = null,
#word5 nvarchar(4000) = null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- create temp table
create table #temp
(
id int identity(1,1),
Proc_id INT,
Proc_Name SYSNAME,
Definition NTEXT
)
-- get the names of the procedures that meet our criteria
INSERT #temp(Proc_id, Proc_Name)
SELECT id, OBJECT_NAME(id)
FROM syscomments
WHERE OBJECTPROPERTY(id, 'IsProcedure') = 1 or
OBJECTPROPERTY(id, 'IsView') = 1
GROUP BY id, OBJECT_NAME(id)
-- initialize the NTEXT column so there is a pointer
UPDATE #temp SET Definition = ''
-- declare local variables
DECLARE
#txtPval binary(16),
#txtPidx INT,
#curText NVARCHAR(4000),
#counterId int,
#maxCounterId int,
#counterIdInner int,
#maxCounterIdInner int
-- set up a double while loop to get the data from syscomments
select #maxCounterId = max(id)
from #temp t
create table #tempInner
(
id int identity(1,1),
curName SYSNAME,
curtext ntext
)
set #counterId = 0
WHILE (#counterId < #maxCounterId)
BEGIN
set #counterId = #counterId + 1
insert into #tempInner(curName, curtext)
SELECT OBJECT_NAME(s.id), text
FROM syscomments s
INNER JOIN #temp t
ON s.id = t.Proc_id
WHERE t.id = #counterid
ORDER BY s.id, colid
select #maxCounterIdInner = max(id)
from #tempInner t
set #counterIdInner = 0
while (#counterIdInner < #maxCounterIdInner)
begin
set #counterIdInner = #counterIdInner + 1
-- get the pointer for the current procedure name / colid
SELECT #txtPval = TEXTPTR(Definition)
FROM #temp
WHERE id = #counterId
-- find out where to append the #temp table's value
SELECT #txtPidx = DATALENGTH(Definition)/2
FROM #temp
WHERE id = #counterId
select #curText = curtext
from #tempInner
where id = #counterIdInner
-- apply the append of the current 8KB chunk
UPDATETEXT #temp.definition #txtPval #txtPidx 0 #curtext
end
truncate table #tempInner
END
-- check our filter
SELECT Proc_Name, Definition
FROM #temp t
WHERE (#word1 is null or definition LIKE '%' + #word1 + '%') AND
(#word2 is null or definition LIKE '%' + #word2 + '%') AND
(#word3 is null or definition LIKE '%' + #word3 + '%') AND
(#word4 is null or definition LIKE '%' + #word4 + '%') AND
(#word5 is null or definition LIKE '%' + #word5 + '%')
ORDER BY Proc_Name
-- clean up
DROP TABLE #temp
DROP TABLE #tempInner
END
You can use sp_refreshsqlmodule to attempt to re-validate SPs (this also updates dependencies), but it won't validate this particular scenario with parameters at the caller level (it will validate things like invalid columns in tables and views).
http://www.mssqltips.com/tip.asp?tip=1294 has a number of techniques, including sp_depends
Dependency information is stored in the SQL Server metadata, including parameter columns/types for each SP and function, but it isn't obvious how to validate all the calls, but it is possible to locate them and inspect them.

Resources