combine #sql query with temp table in SQL [duplicate] - sql-server

In my stored procedure I declared two table variables on top of my procedure. Now I am trying to use that table variable within a dynamic sql statement but I get this error at the time of execution of that procedure. I am using Sql Server 2008.
This is how my query looks like,
set #col_name = 'Assoc_Item_'
+ Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update #RelPro set '
+ #col_name
+ ' = (Select relsku From #TSku Where tid = '
+ Convert(nvarchar(2), #curr_row1) + ') Where RowID = '
+ Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
And I get the following errors,
Must declare the table variable "#RelPro".
Must declare the table variable "#TSku".
I have tried to take the table outside of the string block of dynamic query but to no avail.

On SQL Server 2008+ it is possible to use Table Valued Parameters to pass in a table variable to a dynamic SQL statement as long as you don't need to update the values in the table itself.
So from the code you posted you could use this approach for #TSku but not for #RelPro
Example syntax below.
CREATE TYPE MyTable AS TABLE
(
Foo int,
Bar int
);
GO
DECLARE #T AS MyTable;
INSERT INTO #T VALUES (1,2), (2,3)
SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T
EXEC sp_executesql
N'SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T',
N'#T MyTable READONLY',
#T=#T
The physloc column is included just to demonstrate that the table variable referenced in the child scope is definitely the same one as the outer scope rather than a copy.

Your EXEC executes in a different context, therefore it is not aware of any variables that have been declared in your original context. You should be able to use a temp table instead of a table variable as shown in the simple demo below.
create table #t (id int)
declare #value nchar(1)
set #value = N'1'
declare #sql nvarchar(max)
set #sql = N'insert into #t (id) values (' + #value + N')'
exec (#sql)
select * from #t
drop table #t

You don't have to use dynamic SQL
update
R
set
Assoc_Item_1 = CASE WHEN #curr_row = 1 THEN foo.relsku ELSE Assoc_Item_1 END,
Assoc_Item_2 = CASE WHEN #curr_row = 2 THEN foo.relsku ELSE Assoc_Item_2 END,
Assoc_Item_3 = CASE WHEN #curr_row = 3 THEN foo.relsku ELSE Assoc_Item_3 END,
Assoc_Item_4 = CASE WHEN #curr_row = 4 THEN foo.relsku ELSE Assoc_Item_4 END,
Assoc_Item_5 = CASE WHEN #curr_row = 5 THEN foo.relsku ELSE Assoc_Item_5 END,
...
from
(Select relsku From #TSku Where tid = #curr_row1) foo
CROSS JOIN
#RelPro R
Where
R.RowID = #curr_row;

You can't do this because the table variables are out of scope.
You would have to declare the table variable inside the dynamic SQL statement or create temporary tables.
I would suggest you read this excellent article on dynamic SQL.
http://www.sommarskog.se/dynamic_sql.html

Well, I figured out the way and thought to share with the people out there who might run into the same problem.
Let me start with the problem I had been facing,
I had been trying to execute a Dynamic Sql Statement that used two temporary tables I declared at the top of my stored procedure, but because that dynamic sql statment created a new scope, I couldn't use the temporary tables.
Solution:
I simply changed them to Global Temporary Variables and they worked.
Find my stored procedure underneath.
CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
-- Add the parameters for the stored procedure here
#PRODUCT_SKU nvarchar(15) = Null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..##RelPro', 'U') IS NOT NULL
BEGIN
DROP TABLE ##RelPro
END
Create Table ##RelPro
(
RowID int identity(1,1),
ID int,
Item_Name nvarchar(max),
SKU nvarchar(max),
Vendor nvarchar(max),
Product_Img_180 nvarchar(max),
rpGroup int,
Assoc_Item_1 nvarchar(max),
Assoc_Item_2 nvarchar(max),
Assoc_Item_3 nvarchar(max),
Assoc_Item_4 nvarchar(max),
Assoc_Item_5 nvarchar(max),
Assoc_Item_6 nvarchar(max),
Assoc_Item_7 nvarchar(max),
Assoc_Item_8 nvarchar(max),
Assoc_Item_9 nvarchar(max),
Assoc_Item_10 nvarchar(max)
);
Begin
Insert ##RelPro(ID, Item_Name, SKU, Vendor, Product_Img_180, rpGroup)
Select distinct zp.ProductID, zp.Name, zp.SKU,
(Select m.Name From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID),
'http://s0001.server.com/is/sw11/DG/' +
(Select m.Custom1 From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID) +
'_' + zp.SKU + '_3?$SC_3243$', ep.RoomID
From Product zp(nolock) Inner Join RF_ExtendedProduct ep(nolock) On ep.ProductID = zp.ProductID
Where zp.ActiveInd = 1 And SUBSTRING(zp.SKU, 1, 2) <> 'GC' AND zp.Name <> 'PLATINUM' AND zp.SKU = (Case When #PRODUCT_SKU Is Not Null Then #PRODUCT_SKU Else zp.SKU End)
End
declare #curr_row int = 0,
#tot_rows int= 0,
#sku nvarchar(15) = null;
IF OBJECT_ID('tempdb..##TSku', 'U') IS NOT NULL
BEGIN
DROP TABLE ##TSku
END
Create Table ##TSku (tid int identity(1,1), relsku nvarchar(15));
Select #curr_row = (Select MIN(RowId) From ##RelPro);
Select #tot_rows = (Select MAX(RowId) From ##RelPro);
while #curr_row <= #tot_rows
Begin
select #sku = SKU from ##RelPro where RowID = #curr_row;
truncate table ##TSku;
Insert ##TSku(relsku)
Select distinct top(10) tzp.SKU From Product tzp(nolock) INNER JOIN
[INTRANET].raf_FocusAssociatedItem assoc(nolock) ON assoc.associatedItemID = tzp.SKU
Where (assoc.isActive=1) And (tzp.ActiveInd = 1) AND (assoc.productID = #sku)
declare #curr_row1 int = (Select Min(tid) From ##TSku),
#tot_rows1 int = (Select Max(tid) From ##TSku);
If(#tot_rows1 <> 0)
Begin
While #curr_row1 <= #tot_rows1
Begin
declare #col_name nvarchar(15) = null,
#sqlstat nvarchar(500) = null;
set #col_name = 'Assoc_Item_' + Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update ##RelPro set ' + #col_name + ' = (Select relsku From ##TSku Where tid = ' + Convert(nvarchar(2), #curr_row1) + ') Where RowID = ' + Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
set #curr_row1 = #curr_row1 + 1;
End
End
set #curr_row = #curr_row + 1;
End
Select * From ##RelPro;
END
GO

I don't think that is possible (though refer to the update below); as far as I know a table variable only exists within the scope that declared it. You can, however, use a temp table (use the create table syntax and prefix your table name with the # symbol), and that will be accessible within both the scope that creates it and the scope of your dynamic statement.
UPDATE: Refer to Martin Smith's answer for how to use a table-valued parameter to pass a table variable in to a dynamic SQL statement. Also note the limitation mentioned: table-valued parameters are read-only.

Here is an example of using a dynamic T-SQL query and then extracting the results should you have more than one column of returned values (notice the dynamic table name):
DECLARE
#strSQLMain nvarchar(1000),
#recAPD_number_key char(10),
#Census_sub_code varchar(1),
#recAPD_field_name char(100),
#recAPD_table_name char(100),
#NUMBER_KEY varchar(10),
if object_id('[Permits].[dbo].[myTempAPD_Txt]') is not null
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
CREATE TABLE [Permits].[dbo].[myTempAPD_Txt]
(
[MyCol1] char(10) NULL,
[MyCol2] char(1) NULL,
)
-- an example of what #strSQLMain is : #strSQLMain = SELECT #recAPD_number_key = [NUMBER_KEY], #Census_sub_code=TEXT_029 FROM APD_TXT0 WHERE Number_Key = '01-7212'
SET #strSQLMain = ('INSERT INTO myTempAPD_Txt SELECT [NUMBER_KEY], '+ rtrim(#recAPD_field_name) +' FROM '+ rtrim(#recAPD_table_name) + ' WHERE Number_Key = '''+ rtrim(#Number_Key) +'''')
EXEC (#strSQLMain)
SELECT #recAPD_number_key = MyCol1, #Census_sub_code = MyCol2 from [Permits].[dbo].[myTempAPD_Txt]
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]

Using Temp table solves the problem but I ran into issues using Exec so I went with the following solution of using sp_executesql:
Create TABLE #tempJoin ( Old_ID int, New_ID int);
declare #table_name varchar(128);
declare #strSQL nvarchar(3072);
set #table_name = 'Object';
--build sql sting to execute
set #strSQL='INSERT INTO '+#table_name+' SELECT '+#columns+' FROM #tempJoin CJ
Inner Join '+#table_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID'
**exec sp_executesql #strSQL;**

Related

how to dynamically find and replace the function text

I have 800+ functions in my database. I would need to modify their source databases dynamically and create snapshots.
example of the function:
create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)
I want to change the script as:
create or alter function [schema1].[funTest1] (#param1 varchar(50))
returns table as return
(
select * from [new_database2].[schema1].[funTest1](#param1)
union
select * from [new_database3].[schema1].[funTest1](#param1)
)
basically, I got all the functions script using the sys.syscomments. I'm looking for an option to find and replace the database dynamically to create the snapshots.
How can I get it? Thank you!
Here is the sample code that I have developed for sharing. All the database in the functions starts with the same text(for ex. "curr"). Please share your thoughts. Thanks in advance!
create or alter proc test_proc as
begin
set nocount on
-- this piece of code has the new databases
if object_id('tempdb..#dbNames') is not null drop table #dbNames
create table #dbNames (dbName varchar(1000), id int)
insert into #dbNames(dbName, id) values ('new_database2', 1),('new_database3', 2)
insert into #dbNames(dbName, id) values ('new_database8', 3),('new_database9', 4)
-- this one has the sample functions
if object_id('tempdb..#dbFunctions') is not null drop table #dbFunctions
create table #dbFunctions (funText nvarchar(max))
insert into #dbFunctions (funText) values('create function [schema1].[funTest1] (#param1 varchar(50))
returns table as
return
(
select * from [curr_database1].[schema1].[funTest1](#param1)
union
select * from [curr_database2].[schema1].[funTest1](#param1)
)'),
('create function [schema2].[funTest2] (#param1 varchar(50), #param2 varchar(100))
returns table as
return
(
select * from [curr_database4].[schema2].[funTest2](#param1, #param2)
union
select * from [curr_database5].[schema2].[funTest2](#param1, #param2)
)')
-- declare variables and assign value for #frmStr variable (for testing purposes)
declare #str nvarchar(max)
declare #dbName varchar(100)
declare #frmStr varchar(100) = '[curr_database1]'
-- get the total count of the databases and the functions to iterate and replace the string
declare #dbCnt int = (select count(id) from #dbNames)
declare #fnCnt int = (select count(*) from #dbFunctions)
while #dbCnt > 0
begin
set #dbname = (select dbname from #dbnames where id = #dbcnt)
while #fnCnt > 0
begin
-- this is where I would need to replace the code
select #str = replace(funText, #frmStr, #dbName) from #dbFunctions
select #str
set #fnCnt = #fnCnt - 1
end
set #dbCnt = #dbCnt - 1
end
end
Your actual goal isn't clear, but to answer the question you asked, you can use REPLACE functions in the query to syscomments that you used to get the code in the first place:
REPLACE(
REPLACE([FunctionTextColumn],'curr_database1','new_database2')
,'curr_database2','new_database3'
)

How to use table valued parameter with the IN keyword

Edit:
I tried to replace:
#LocationIDs NVARCHAR(MAX) = null,
with
#LocationIDs LocationIdArray READONLY,
but now I get an error saying:
Must declare the scalar variable "#LocationIDs".
--END EDIT--
I have this stored procedure that I need to address:
CREATE PROCEDURE [dbo].[spAP_GetTechnician_Door_Unlock]
#LocationIDs NVARCHAR(MAX) = NULL,
#AlarmDateFrom DATETIME = NULL,
#AlarmDateTo DATETIME = NULL,
#TechnicianID INT = NULL,
#LocationId INT = NULL
AS
BEGIN
IF (#LocationIDs = 'x')
BEGIN
SELECT #LocationIDs = dbo.fn_GetAll_Location_Id()
END
DECLARE #query NVARCHAR(MAX);
SET #query = 'WITH CTE AS (SELECT ROW_NUMBER() OVER(ORDER BY al.Alarm_Log_ID desc)AS RowNumber,
isnull(t.Technician_ID,'''')[Technician_ID], (isnull(t.Last_Name,'''') +'' ''+ isnull(t.Name,'''')) TechnicianName,isnull(t.Emailid,'''') as EmailID,isnull(t.phone,'''') as Phone,dbo.fNAP_DateFormat(al.Alarm_date) as Alarm_date,
Al.Site_ID,s.Name as SiteName,al.point_Address,l.location_Name,l.Location_ID ,shs.StatusData
from z_EntityMast_Alarm_Log al
left join z_EntityMast_Technician t on al.Technician_ID=t.Technician_id
left join z_EntityMast_Site s on s.Site_ID=al.Site_ID
left join z_EntityMast_Location l on s.Location_ID=l.Location_id
left join z_EntityMast_Site_Hardware_Status shs on s.site_id=shs.siteid
left join z_SysVar_Alarm_Type_00004 at on al.Alarm_Type=at.ID
where at.Is_Linkable=1 and al.Alarm_Type !=70'
if(isnull(#LocationId,0)!=0)
set #query=#query+' and s.Location_ID ='+convert(varchar(12),#LocationId);
else
set #query=#query+' and s.Location_ID in ('+#LocationIDs+')';
if(isnull(#AlarmDateFrom,0)!=0 and #AlarmDateFrom !='')
set #query=#query+'and (DATEDIFF(DAY,'''+convert(varchar(30),#AlarmDateFrom)+''', al.Alarm_Date)>=0 and DATEDIFF(DAY,'''+convert(varchar(30),#AlarmDateTo)+''',al.Alarm_Date)<=0)';
if(isnull(#TechnicianID,0)!=0)
set #query=#query+'and t.Technician_ID ='+ convert(varchar(10),#TechnicianID);
set #query=#query + ')';
set #query=#query +'select * from CTE ';
-- PRINT #query
EXEC (#query)
END
I need to optimize it and I have to use table valued parameters for the LocationIds parameter, instead of it using NVARCHAR(MAX).
The problem is in this line of code:
SET #query = #query + ' and s.Location_ID in ('+#LocationIDs+')';
My question is: how does one replace that line of code and replace it with a table valued parameter in such a way that the concatenation would still work?
Thanks!
SET #query = #query + ' and s.Location_ID in ('+#LocationIDs+')';
My question is: how does one replace that line of code and replace it
with a table valued parameter in such a way that the concatenation
would still work?
Suppose your LocationIdArray has this definition:
create type LocationIdArray as table (LocationId int);
Then your IN should look like this:
and s.Location_ID in (select LocationId from #LocationIDs)
This won't work within your exec because #LocationID is in the outer scope respect to exec, you can pass it as a parameter in sp_executesql but the best you can do is to rewrite your dynamic query to static one as there is no reason to use dynamic code here.

Replicate a table record with a new id value using dynamic SQL

I have an application with a requirement that the user can "copy" a record. This will duplicate the record in the table, and the associated records in any child tables.
I will use a trigger to execute a stored procedure to do the copy. The issue I am facing is that I want to increment the ID field for the copied record, which is also the FK in the child tables. The ID field is not a standard format, so using an increment won't work. In order to make this future-proof, I was going to use dynamic SQL to pull the columns for each table so that I don't need to modify the code if I add a new field to one of the tables. The client's system admin can also add columns to the table via the GUI, but they have no access to the SQL backend so they would need to contact us to modify the code (not ideal).
Example:
Declare #ColumnNames varchar(2000)
Declare #BLDGCODE char(4)
set #BLDGCODE = '001'
select #ColumnNames = COALESCE(#ColumnNames + ', ', '') + COLUMN_NAME
from
INFORMATION_SCHEMA.COLUMNS
where
TABLE_NAME='FMB0'
Declare #DynSqlStatement varchar(max);
set #DynSqlStatement = 'Insert into dbo.FMB0('+ #ColumnNames + ')
select * from dbo.FMB0 where BLDGCODE= ' + cast(#BLDGCODE as char(4));
print(#DynSqlStatement);
This solves the issue for a new column being added to one of the tables. However, how can I increment the ID (BLDGCODE in this example). Is my only solution to script out the columns by name so I can increment the ID, or is there a function I am overlooking?
Hopefully this made sense. I am an intermediate SQL user at best, so forgive the naivete if there's an obvious solution.
UPDATE
So I've decided to use #temp tables to hold the record that was changed, modify the id there, and then insert back into the main table from the #temp table. This is working pretty well, with one exception. I get the following error:
The column "FLOORID_" cannot be modified because it is either a computed column or is the result of a UNION operator.
Below is my stored procedure. I've investigated a STUFF approach, but not sure where to insert that code. Using STUFF with calculated column. I am now back to thinking I need to call out the columns specifically for the one table with the computed column, and if we add a new field, I just need to modify this stored procedure. Anyone have any other ideas?
ALTER PROCEDURE [dbo].[LDAC_BLDGCOPY]
#BLDGCODE CHAR(4)
AS
BEGIN
SET NOCOUNT ON;
--COPY BUILDING RECORD
BEGIN
DECLARE #ColumnNamesB0 VARCHAR(2000);
SELECT *
INTO #TEMPB0
FROM FMB0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPB0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
BLDGCOPY = 0;
SELECT #ColumnNamesB0 = COALESCE(#ColumnNamesB0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMB0';
DECLARE #DynSqlStatementB0 VARCHAR(MAX);
SET #DynSqlStatementB0 = 'Insert into dbo.FMB0('+#ColumnNamesB0+')
select * from #TEMPB0';
EXEC (#DynSqlStatementB0);
END;
--COPY FLOOR RECORDS
BEGIN
DECLARE #ColumnNamesL0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPL0
FROM FML0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPL0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey();
UPDATE #TEMPL0
SET FLOORID_ = auto_key;
SELECT #ColumnNamesL0 = COALESCE(#ColumnNamesL0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FML0';
DECLARE #DynSqlStatementL0 VARCHAR(MAX);
SET #DynSqlStatementL0 = 'Insert into dbo.FML0('+#ColumnNamesL0+')
select * from #TEMPL0';
EXEC (#DynSqlStatementL0);
END;
--COPY ROOM RECORDS
BEGIN
DECLARE #ColumnNamesA0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPA0
FROM FMA0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPA0
SET
BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
FLOORID = #TEMPL0.FLOORID_
FROM #TEMPA0
INNER JOIN #TEMPL0 ON CONVERT(CHAR(4), (CAST((#BLDGCODE) AS
INT) + 100)) = #TEMPL0.BLDGCODE;
SELECT #ColumnNamesA0 = COALESCE(#ColumnNamesA0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMA0';
DECLARE #DynSqlStatementA0 VARCHAR(MAX);
SET #DynSqlStatementA0 = 'Insert into dbo.FMA0('+#ColumnNamesA0+')
select * from #TEMPA0';
EXEC (#DynSqlStatementA0);
DROP TABLE #TEMPB0;
DROP TABLE #TEMPL0;
DROP TABLE #TEMPA0;
END;
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.

declaring temp table in both branches of a sql server stored proc conditional

I want to have a construct in a stored procedure that defines a temporary table one way in one branch of an if-else and another way in the other branch, but this won't compile b/c it says the table is already created. I am imagining something like the following:
if
begin
IF OBJECT_ID(N'tempdb..#tbl') IS NOT NULL DROP TABLE #tbl
create table #tbl (A int, B int)
end
else
begin
IF OBJECT_ID(N'tempdb..#tbl') IS NOT NULL DROP TABLE #tbl
create table #tbl (A int, B int, C int)
end
which seems like it should be well defined.
Is there a better way to do this than declaring it empty and then repeatedly altering it to add the columns I want in the branches? (this is just kind of ugly)
Yes, apparently you can't do that. I've learned something new today.
It's a bit difficult to propose a solution without knowing more about what you need to do, where your primary key for the temp table would be, or what your conditional logic is based on.
If you are just adding additional non-key columns, you can use a single temp table with the additional columns allowing nulls.
Later, you can use the additional columns or not, based upon the same logic you used to populate the temp table.
If you are changing the primary key of the table, you'll need to use two separate tables.
Depending on the logic in your procedure, you could possibly use the SELECT INTO syntax to create the temp table based on a query result.
I haven't tried it, but you could probably use a dynamic SQL statement to create the table. I can't recommend doing this though, unless there is no other solution.
Example dynamic sql pivot:
CREATE PROCEDURE dbo.MyProc
#StartMonth INT = 1
,#StartYear INT
,#EndMonth INT = 12
,#EndYear INT
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQLString VARCHAR(MAX)
DECLARE #CalendarMonths VARCHAR(MAX)
SET #CalendarMonths = ''
IF #StartYear IS NULL
SET #StartYear = DATEPART(yyyy, getdate())
IF #EndYear IS NULL
SET #EndYear = DATEPART(yyyy, getdate())
DECLARE #YearCounter INT
DECLARE #MonthCounter INT
SET #YearCounter = #StartYear
SET #MonthCounter = #StartMonth
WHILE #YearCounter <= #EndYear
BEGIN
IF #YearCounter = #StartYear
SET #MonthCounter = #StartMonth
ELSE
SET #MonthCounter = 1
WHILE (#YearCounter = #EndYear AND #MonthCounter <= #EndMonth) OR (#YearCounter <> #EndYear AND #MonthCounter <= 12)
BEGIN
SET #CalendarMonths = #CalendarMonths + '[' + CAST(#MonthCounter AS VARCHAR) + '/' + CAST(#YearCounter AS VARCHAR) + '],'
SET #MonthCounter = #MonthCounter + 1
END
SET #YearCounter = #YearCounter + 1
END
SET #CalendarMonths = #CalendarMonths + ',#' -- add token to mark last ',' location
SET #CalendarMonths = REPLACE(#CalendarMonths, ',,#', '') -- remove last ','
SET NOCOUNT OFF
SET #SQLString =
'
SELECT
*
FROM
(
SELECT Units, CAST(CalendarMonth AS VARCHAR) + ''/'' + CAST(CalendarYear AS VARCHAR) AS Calendar, GroupID
FROM dbo.MyTable
) ptg
PIVOT
(
SUM(Units)
FOR Calendar IN
(
'
+ CHAR(9)
+ CHAR(9)
+ #CalendarMonths +
'
)
) pvt
'
--PRINT(#SQLString)
EXEC(#SQLString)
END
GO

Resources