I have tried to update a stored procedure which worked fine without the need to use sp_executesql. I now want to have the table name as a parameter as I have a number of tables with the same structure and don't want to create new stored procedures for each of them.
The problem I have is that this version seems to require all the parameters, while the previous one accepted any number of parameters. For instance, if I remove all the WHERE parameters and just have the #TableName parameter it works fine. I;ve tried looking for an example, but I cannot find anything like this. All the examples of parsing the table name have only that parameter.
CREATE PROCEDURE cafgTenantNamesTEST2
#TableName sysname,
#Square nvarchar(100) = null,
#Location nvarchar(100) = null,
#Name nvarchar(100) = null,
#NormalizedName nvarchar(100) = null,
#SharedLand int = 0,
#FieldNumber int = 0,
#Description nvarchar(255) = null,
#Dwelling nvarchar(100) = null
AS
BEGIN
DECLARE #sql AS NVARCHAR(MAX)
SET #sql = 'SELECT * FROM [' + #TableName + ']' +
'WHERE ([Square] LIKE ''' + #Square + ''' OR ''' + #Square + ''' IS NULL)' +
'AND ([Location] = ''' + #Location + ''' OR ''' + #Location + ''' IS NULL)' +
...
...
--PRINT #sql
EXEC sp_executesql #sql
END
Suggestions please.
Suggestion 1:
Use QUOTENAME() to handle proper escaping of the table name.
Suggestion 2: You are inserting the value of the parameter into #sql. Don't do that. Instead you should use pameterized the sql.
Suggestion 3: Eliminate the OR logic by conditionally building the query's WHERE clause.
CREATE PROCEDURE cafgTenantNamesTEST2
#TableName sysname,
#Square nvarchar(100) = null,
#Location nvarchar(100) = null,
#Name nvarchar(100) = null,
#NormalizedName nvarchar(100) = null,
#SharedLand int = 0,
#FieldNumber int = 0,
#Description nvarchar(255) = null,
#Dwelling nvarchar(100) = null
AS
BEGIN
DECLARE #sql AS NVARCHAR(MAX)
SET #sql = N'SELECT * FROM ' + QUOTENAME(#TableName ) +
' WHERE 1=1 '
IF #Square IS NOT NULL
SET #sql = #sql + ' AND ([Square] LIKE #Square )' -- still patameterized
IF #Location IS NOT NULL
SET #sql = #sql + N' AND ([Location] = #Loc )'
...
...
--PRINT #sql
EXEC sp_executesql #sql, N'#Square nvarchar(100), #Loc nvarchar(100)...', #square=#square, #loc=#location -- the param names can be the same or different, sp_executesql has it's own scope.
END
Sp_executesql can execute parameterized sql in addition to plain sql. It is the underlying system stored procedure that is used by client libraries to execute parameterized code. For example, System.Data.SqlClient.SqlCommand will call sp_executesql if you have added any parameters. It is atypical in that it accepts a variable number of parameters. The msdn docs on sp_executesql provide some good information, but isn't clear. Capturing activity in SQL profiler is the easiest way to see sp_executesql in action.
Related
I'm trying to store DDLs of some views and stored procedures in a separate table in the Dump database. There are too many similar databases on the server. But some objects are redundant. They will be listed in the table and then dropped from the database. But their DDLs will be backed up if somebody will need them later.
The procedure works fine when the views are small, but if the size of the code exceeds some value - I'm get an error:
XML parsing: line 120, character 31, incorrect CDATA section syntax
Maybe that's I'm using the dbo.sp_sqlexec procedure, but I'm not sure. Will appreciate any ideas.
Definition of the table where those views will be firstly listed and then stored:
CREATE TABLE [dbo].[ViewList_Explicit]
(
[ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[ServerName] [sysname] NOT NULL,
[DatabaseName] [sysname] NOT NULL,
[SchemaName] [sysname] NOT NULL,
[ViewName] [sysname] NOT NULL,
[DefinitionText] [xml] NULL,
[IsTransferred] [bit] NOT NULL,
[DateTransferred] [datetime] NULL
);
INSERT INTO [dbo].[ViewList_Explicit] ([ServerName], [DatabaseName], [SchemaName], [ViewName], [DefinitionText], [IsTransferred], [DateTransferred])
VALUES ('dbserver', 'reco', 'dbo', 'v_redundant_toDrop', NULL, 0, NULL)
This is the code of the procedure:
CREATE OR ALTER PROCEDURE [dbo].[sp_moveViews2Dump]
(#DatabaseName SYSNAME)
AS
BEGIN
SET NOCOUNT ON
DECLARE #Serv SYSNAME = ##SERVERNAME;
DECLARE #SQLstringToDrop NVARCHAR(MAX), #SQLstringForDefinition NVARCHAR(MAX);
DECLARE #SchemaName SYSNAME, #ViewName SYSNAME, #ExplicitID INTEGER;
DECLARE #DDLview XML;
DECLARE #Buffer TABLE(line XML);
DECLARE Schedule_cursor CURSOR LOCAL FOR
SELECT ID, SchemaName, ViewName
FROM [Dump].dbo.ViewList_Explicit
WHERE DatabaseName = #DatabaseName
AND ServerName = #Serv
AND IsTransferred = 0
OPEN Schedule_cursor
FETCH NEXT FROM Schedule_cursor INTO #ExplicitID, #SchemaName, #ViewName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQLstringForDefinition = 'SELECT CONCAT(''<query><![CDATA['', VIEW_DEFINITION, '']]></query>'') FROM ['
+ #DatabaseName + '].INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = '''+ #ViewName + ''' AND TABLE_SCHEMA = ''' + #SchemaName + ''';'
--PRINT #SQLstringForDefinition
INSERT #Buffer EXECUTE dbo.sp_sqlexec #SQLstringForDefinition
SELECT #DDLview = line FROM #Buffer
SELECT #SQLstringToDrop = 'USE [' + #DatabaseName + ']
DROP VIEW IF EXISTS [' + #SchemaName + '].[' + #ViewName + ']'
--EXECUTE dbo.sp_sqlexec #SQLstringToDrop -- Commented out to avoid the deletion
UPDATE [Dump].dbo.ViewList_Explicit
SET [DefinitionText] = #DDLview, IsTransferred = 1, DateTransferred = GETDATE()
WHERE ID = #ExplicitID
DELETE FROM #Buffer
FETCH NEXT FROM Schedule_cursor INTO #ExplicitID, #SchemaName, #ViewName
END
CLOSE Schedule_cursor
DEALLOCATE Schedule_cursor
SET NOCOUNT OFF
END
Not sure why you're storing module definitions as XML but you should be able to do this in one step, without the cursor, unsupported system procedures from decades ago, and INFORMATION_SCHEMA which is generally garbage (<-- see the section on Module Definitions):
DECLARE #exec nvarchar(1024) = QUOTENAME(#DatabaseName)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N';WITH vws AS
(SELECT SchemaName = s.name, ViewName = v.name,
DefinitionText = CONCAT(''<query><![CDATA['',
OBJECT_DEFINITION(v.[object_id]), N'']]></query>'')
FROM sys.schemas AS s
INNER JOIN sys.views AS v
ON s.[schema_id] = v.[schema_id]
)
UPDATE vle
SET vle.DefinitionText = sps.DefinitionText,
vle.IsTransferred = 1,
vle.DateTransferred = GETDATE()
FROM [Dump].dbo.ViewList_Explicit AS vle
INNER JOIN vws
ON vle.SchemaName = vws.SchemaName
AND vle.ViewName = vws.ViewName
WHERE vle.DatabaseName = #db
AND vle.ServerName = ##SERVERNAME
AND vle.IsTransferred = 0;';
EXEC #exec #sql, N'#db sysname', #DatabaseName;
The main problem was mentioned in a comment already: VIEW_DEFINITION is limited to 4,000 characters.
The purpose of #SQLstringToDrop is unclear. If you're on a modern enough version of SQL Server, you could instead inject CREATE OR ALTER into the definition, or generate that only at time of actual execution / deployment ... that doesn't change per view so there's no reason to store the entire IF EXISTS / DROP sequence for each and every view.
If you want to drop the views after you've backed up their definitions (though, really, why you aren't using proper version control system for this is a mystery), you can simply use the same technique (all in one shot instead of in a loop):
DECLARE #exec nvarchar(1024) = QUOTENAME(#DatabaseName)
+ N'.sys.sp_executesql';
DECLARE #sql nvarchar(max) = N'';
SELECT #sql += CONCAT(N'DROP VIEW IF EXISTS ',
QUOTENAME(SchemaName), N'.',
QUOTENAME(ViewName), N';')
FROM [Dump].dbo.ViewList_Explicit
WHERE DatabaseName = #DatabaseName
AND ServerName = ##SERVERNAME;
EXEC #exec #sql;
As an additional tip, don't ever put square brackets around names manually (e.g. [' + #SchemaName + ']) - this does not protect you against SQL injection. And while it's unlikely someone put nefarious object names into the system you're working against now, it sets a bad example.
This is my stored procedure in SQL Server 2016:
CREATE PROCEDURE [USA_PHILIPS].[usp_stock]
#VCM INT,
#ID VARCHAR,
#SCHEMA_NAME VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DROP TABLE IF EXISTS [USA_PHILIPS].[stock]
SELECT STOCKNUMBER,STOCKBOOKS
INTO [USA_PHILIPS].[stock]
FROM [USA_PHILIPS].[DMARTSTOCK]
WHERE VCM = #VCM
AND ID = #ID
END
How can I pass schema name as a parameter #SCHEMA_NAME?
And execute these statements as dynamic SQL:
DROP TABLE IF EXISTS [USA_PHILIPS].[stock]
Please help.
I would personally do it this way, injecting the value directing into the dynamic query. I also fix some of your data types:
CREATE PROCEDURE [USA_PHILIPS].[usp_stock] #VCM int,
#ID varchar(25), --Always define your varchar lengths
#SCHEMA_NAME sysname --Correct data type for object names
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10)
SET #SQL = N'DROP TABLE IF EXISTS ' + QUOTENAME(#SCHEMA_NAME) + N'.[stock];' + #CRLF + #CRLF +
N'SELECT STOCKNUMBER,STOCKBOOKS' + #CRLF +
N'INTO ' + QUOTEMANE(#SCHEMA_NAME) + N'.[stock]' + #CRLF +
N'FROM [USA_PHILIPS].[DMARTSTOCK]' + #CRLF +
N'WHERE VCM=#VCM' + #CRLF +
N' AND ID = #ID;';
EXEC sys.sp_executesql #SQL, N'#VCM int, #ID varchar(25)', #VCM, #ID;
END;
All the code needs to be dynamic if an identifier is dynamic -- and you have to munge query strings:
CREATE PROCEDURE [USA_PHILIPS].[usp_stock] (
#VCM INT,
#ID VARCHAR(255),
#SCHEMA_NAME VARCHAR(50)
) AS
BEGIN
DECLARE #sql = NVARCHAR(MAX);
SET #sql = 'DROP TABLE IF EXISTS [SCHEMA].[stock]';
SET #sql = REPLACE(#sql, '[SCHEMA]', QUOTENAME(#SCHEMA_NAME));
EXEC sp_executeSQL #sql;
SET #sql = '
SELECT STOCKNUMBER, STOCKBOOKS
Into [SCHEMA].[stock]
from [USA_PHILIPS].[DMARTSTOCK]
WHERE VCM=#VCM AND ID = #ID';
SET #sql = REPLACE(#sql, '[SCHEMA]', QUOTENAME(#SCHEMA_NAME));
EXEC sp_executesql #sql,
N'#vcm INT, #id VARCHAR(255)',
#vcm=#vcm, #id=#id;
END;
Note some important changes to the query:
#ID has a length as an argument. This is important because the default varies by context and it might (well probably isn't) long enough for what you want.
I assume that you want the same table referenced in the DELETE as the INTO.
Pass the constant values as parameters.
I have some dynamic SQL that I use to see if a certain property for a certain client is in a table. If so, it will return the value, if not, it will return null. However, I would like it to return a default value if the property is not found.
The table:
CREATE TABLE [test].[customers](
[customer] [nvarchar](256) NULL,
[key_name] [nvarchar](256) NULL,
[key_value] [nvarchar](256) NULL
)
GO
INSERT INTO [test].[customers]
([customer]
,[key_name]
,[key_value])
VALUES
('JohnDoe'
,'periodlength'
,'3')
GO
The Dynamic SQL:
declare #customer as nvarchar(256) = 'JohnDoe'
declare #table as nvarchar(256) = 'test.customers'
declare #sql as nvarchar(4000)
set #sql = 'select [key_value] from ' + #table + ' where [key_name]= ''periodlength'' and customer= ''' + #customer + ''''
exec sp_executesql #sql
So when Qqerying for John Doe you get result 3 which is perfect. However, I would like to reurn 1 for Jane Doe. So I was thinking along the lines
IF exec sp_executesql #sql IS NULL 1 ELSE exec sp_executesql #sql
but that doesn't work.
How to change my dynamic query so that is returns a default value if the property is not found?
Could you try the following query but this usage only return a single value;
declare #customer as nvarchar(256) = 'JohnDoe'
declare #table as nvarchar(256) = 'test.customers'
DECLARE #ValReturn nvarchar(50)
declare #sql as nvarchar(4000)
set #sql = 'select #ValOutPut=[key_value] from ' + #table + ' where [key_name]= ''periodlength'' and customer= ''' + #customer + ''''
exec sp_executesql #sql , N'#ValOutPut nvarchar(25) OUTPUT' ,#ValOutPut = #ValReturn OUTPUT
BEGIN
IF #ValReturn IS NULL
SELECT NULL
ELSE
SELECT #ValReturn
END
For the last couple days I am pulling my hair out because of a problem I have.
In a stored procedure, I want to use the column name as parameter to update a value in the table.
I have the following code
ALTER PROCEDURE [dbo].[Item_Update_Single]
#Id nvarchar(15),
#ColumnName nvarchar(80),
#NewValue nvarchar(80)
AS
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'UPDATE [Item] SET [' + QUOTENAME(#ColumnName) + ']' + '= ' + QUOTENAME(#NewValue) +' WHERE [Id] = ' + #Id
PRINT #sql
The stored procedure is running fine, no errors, but the table is not updated. If I run the #SQL string in query window, the data is updated.
I am a sort of newbie but what did I do wrong here?
You never execute your dynamic statement. Your use of QUOTENAME is also wrong. '[' + QUOTENAME(#ColumnName) + ']' would result in [[ColumnName]] and QUOTENAME(#NewValue) would refer to a column with the name of what ever value is in #NewValue, not a string literal. You should be parametrising the statement and properly injecting the dynamic object:
ALTER PROCEDURE [dbo].[Item_Update_Single] #Id int, --Guess this is actually an int
#ColumnName sysname, --Corrected data type
#NewValue nvarchar(80) --I assume this is correct
AS
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'UPDATE dbo.[Item] SET ' + QUOTENAME(#ColumnName) + ' = #NewValue WHERE [Id] = #ID;'
EXEC sys.sp_executesql #SQL, N'#NewValue nvarchar(80),#Id int', #NewValue, #ID;
END
This, however, seems like an XY Problem. Solutions like this are almost always a bad idea and often infer a significant design flaw.
I need to write a stored procedure which will return the value of a parameter, acquired from a Linked Server.
I have tried declaring the variable both inside and outside of the dynamic sql but it fails with a must declare variable error.
declare #srvr nvarchar(100)
declare #dbn nvarchar(50)
set #srvr = 'ServerName'
set #dbn = 'DatabaseName'
Declare #sql nvarchar(max)
set #sql = 'declare #param nvarchar(50) set #param = (Select X from [' + #srvr + '].[' + #dbn + '].[TableName])'
exec (#sql)
print #param
This will form the framework for multiple procedures which reside in a central database, these procedures will be called when restoring other databases into the environment forming part of 'prep script' of sorts
Any ideas please?
Thanks very much
You need to declare the variable twice. Once for the inner context, once for the outer context. They need not use the same names inside and out:
declare #srvr nvarchar(100)
declare #dbn nvarchar(50)
set #srvr = N'ServerName'
set #dbn = N'DatabaseName'
Declare #sql nvarchar(max)
declare #parms nvarchar(max)
set #sql = N'set #param = (Select X from [' + #srvr + '].[' + #dbn + '].[TableName])'
set #parms = N'#param nvarchar(50) output'
declare #param2 nvarchar(50)
exec sp_executesql #sql,#parms,#param = #param2 output
print #param2