Pass the Multiple quotes around #variable wherever single quote is there - sql

Below is my procedure. It is working fine.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
DECLARE #sql VARCHAR(MAX)
SET #sql = ' Select EmpID,CompanyName FROM Employee' + CHAR(13) + CHAR(10)
IF len(#CompanyName) > 0
BEGIN
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
END
PRINT #SQL
EXEC(#sql)
End
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''s'
I need Wherever single quote is there in companyname,if I pass 8 quotes in single quotes I need output.above procedure where do I need to change.
Eg:
exec spCompanyName #CompanyName='So Unique, formerly Sofia''''''''s'
exec spCompanyName #CompanyName='Absolute''''''''s,Strategy''''''''s'

Don't think "I'll throw away all of the useful features of using parameters to separate data from code and start manually trying to protect strings" - keep using parameters.
Rather than
EXEC(#sql)
Have:
EXEC sp_executesql #sql,N'#CompanyName varchar(100)',#CompanyName = #CompanyName
And change:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like ''' + #CompanyName + '%'') ' + char(13) + char(10)
to:
SET #sql = #sql + ' Where (RTRIM(LTRIM(CompanyName)) like #CompanyName + ''%'') ' + char(13) + char(10)
Or, in the alternative, consider just writing a normal query, no dynamic SQL:
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
WITH RECOMPILE
AS
Begin
Select EmpID,CompanyName FROM Employee
Where RTRIM(LTRIM(CompanyName)) like #CompanyName + '%' or
#CompanyName is null
End
Assuming you're using SQL Server 2008 (with particular patch levels) or later, as specified in Erland Sommarskog's Dynamic Search Conditions in T-SQL

If you are going to create a new stored procedure for this feature then you can have this in much better way then dynamic.
You should execute different select queries based on if CompanyName is passed or not.
You can simply the stored procedure as following.
Create PROCEDURE [dbo].[spCompanyName]
(
#CompanyName VARCHAR(100))
AS
Begin
IF LEN(#CompanyName) > 0
BEGIN
Select EmpID,CompanyName FROM Employee WHERE RTRIM(LTRIM(CompanyName)) like '' + #CompanyName + '%'
END
ELSE
BEGIN
Select EmpID,CompanyName FROM Employee
END
END
I am not sure why you need to pass so many single quotes in the parameter value but to me it looks like you can execute the procedure with following simple way.
EXEC spCompanyName #CompanyName='So Unique, formerly Sofia''s'
EXEC spCompanyName #CompanyName='Absolute''s,Strategy''s'
This should help you finding your solution.

Related

Passing output from dynamic SQL

Currently I have a working version of a dynamic SQL query without any variables except one (#ColumnHeader). And am able to get the desired result being a collection of column names in one line separated by comma.
select #ColumnHeader = COALESCE(#ColumnHeader+',','') + '''' + column_name + ''''
from databaseName.Information_Schema.Columns
where table_name = 'Dates'
I am trying to add variables for Database_information_schema and TableName.
DECLARE #ColumnHeader varchar(8000)
DECLARE #Database_Information_SchemaColumns varchar(8000) = 'DatabaseName2.Information_Schema.Columns'
DECLARE #TableName varchar(8000) = 'dates'
DECLARE #sqlQuery as nvarchar(max) = 'Select ' + #ColumnHeader + '= COALESCE(' + #ColumnHeader+ +''','','''')+ ''''''''+column_name+'''''''' from ' + #Database_Information_SchemaColumns + 'where table_name = '''+ #TableName + ''''
Print #sqlQuery
EXEC sp_executesql #sqlQuery;
I am getting Null values and am not sure whats wrong here.
What follows is some working code. I explain the changes required:
You are trying to assign a parameter using dynamic SQL, therefore the parameter needs to be part of the dynamic SQL string, not concatenated with it.
To assign a parameter using dynamic SQL you have to pass it in/out of sp_executesql because the context the dynamic SQL is running under cannot see the parameters declared.
While this is not necessary, I have renamed the internal parameter so that its clear which parameter belongs in which scope. However both could use the same name if desired.
You were missing a space before your where.
I recommend using varchar(max) and nvarchar(max) as there is no need to risk running into the 8k limit.
Use quotename for any database, schema, table or column names to protect against injection.
Use the sysname datatype where a system name is being stored
Split all system names into parts to allow the use of quotename
DECLARE #ColumnHeader varchar(max)
, #Database_Information_Database sysname = 'DatabaseName2'
, #Database_Information_Schema sysname = 'Information_Schema'
, #Database_Information_Columns sysname = 'Columns'
, #TableName sysname = 'dates';
DECLARE #sqlQuery nvarchar(max) = 'select #ColumnHeaderInteral = COALESCE(#ColumnHeaderInteral,'','','''') + '''''''' + column_name + '''''''' from '
+ quotename(#Database_Information_Database) + '.'
+ quotename(#Database_Information_Schema) + '.'
+ quotename(#Database_Information_Columns)
+ ' where quotename(table_name) = ''' + quotename(#TableName) + '''';
PRINT #sqlQuery;
EXEC sp_executesql #sqlQuery, N'#ColumnHeaderInteral varchar(max) output', #ColumnHeaderInteral = #ColumnHeader out;
SELECT #ColumnHeader;
Official Documentation

MSSQL Dynamic SQL for table creation from view fails only when executed through job

I have a problem with my recurring table creation.
I have a bunch of views that need to be written into tables.
My approach is, that for every view there is, a table should be created and therefore I realized it with dynamic SQL. So that I don't have to touch the job every time I add a view.
My problem is, the code works fine as long as I execute it myself within SSSM.
As soon as I put it in a job and it is executed by schedule or by myself it fails. If I replace the dynamic SQL with the code it produces, the job fails as well. I even put the code into a stored procedure and just executed that from the job and that produced the same result.
The error states that it couldn't convert a nvarchar type into a datetime type.
I checked every view and ran the code for every view/table singly as well as all at once and there was no error.
Has anyone an idea what's wrong here?
Here is the dynamic SQL code that I use:
DECLARE #SQL varchar(max);
SELECT #SQL = COALESCE(#SQL + ' ', '') + 'IF OBJECT_ID(''' + REPLACE(name, 'qry_', 'tbl_') + ''', ''U'') IS NOT NULL DROP TABLE ' + QUOTENAME(REPLACE(name, 'qry_', 'tbl_')) + '; SELECT * INTO ' + QUOTENAME(REPLACE(name, 'qry_', 'tbl_')) + ' FROM ' + QUOTENAME(name) + ';'
FROM sys.views
WHERE LEFT(name, 4) = 'qry_'
EXEC (#sql);
This SQL is untested, however, this should greatly help you. Notice that I do each execution separately. Firstly, this does (actually) mean that if the dynamic SQL fails that it won't propagate out (this may not be intended, however). I also, though, have added PRINT statements so you can inspect the logs and see what was actually done; and what view was failed out. PRINT #SQL; is commented out, as (if I recall correctly), the Agent logs can only hold 8000 or 4000 characters, so printing the value of #SQL would over bloat it.
DECLARE #SQL nvarchar(MAX), #ViewName sysname;
DECLARE qry_views CURSOR FOR
SELECT [name]
FROM sys.views
WHERE [name] LIKE 'qry[_]%'
OPEN qry_views;
FETCH NEXT FROM qry_views
INTO #ViewName;
WHILE ##FETCH_STATUS = 0 BEGIN
PRINT 'Creating table from View ' + QUOTENAME(#ViewName);
SET #SQL = N'IF OBJECT_ID(''' + REPLACE(#ViewName, N'qry_', N'tbl_') + N''', ''U'') IS NOT NULL DROP TABLE ' + QUOTENAME(REPLACE(#ViewName, N'qry_', N'tbl_')) + N';' + NCHAR(10) +
N'SELECT *' + NCHAR(10) +
N'INTO ' + QUOTENAME(REPLACE(#ViewName, N'qry_', N'tbl_')) + NCHAR(10) +
N'FROM ' + QUOTENAME(#ViewName) + N';';
--PRINT #SQL;
EXEC sp_executesql #SQL;
FETCH NEXT FROM qry_views
INTO #ViewName;
END
CLOSE qry_views;
DEALLOCATE qry_views;
In the job, do you call a stored proc by using date literals, like:
exec my_proc #my_param='20180524'
? You can't do this. Assing the values to a var first, then use the var:
declare #my_value date ='20180524'
exec my_proc #my_param=#my_value

dynamic SQL update command

I am using MSSQL 2016,
I need to be able to update a row on a table dynamically.
I got a stored procedure :
CREATE PROCEDURE sp_lookupData_UpdatelookupValues
(
#FullTableName nvarchar(50),
#Id nvarchar(10),
#Name nvarchar(50),
#Description nvarchar(50)
)
AS
BEGIN
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ' + #Name + ', Description = ' + #Description + ' WHERE ID = ' + #Id + '';
EXECUTE sp_executesql #Cmd;
END
The problem is that Name and Description values are passed into the #Cmd like this :
UPDATE TABLE_NAME SET Name = Private, Description = Default WHERE ID = 1
Instead of 'Private' and 'Default'.
The result is an error where Private is being counted as a column which doesnt exist ( because of the bad format ).
Invalid column name 'Private'.
Put the quotes yourself
Use single quotes around Private and Default.
And since you are using dynamic querying, you have to double the single quotes to escape them.
DECLARE #Cmd nvarchar(150) = N'UPDATE ' + #FullTableName + ' SET Name = ''' + #Name + ''', Description = ''' + #Description + ''' WHERE ID = ' + #Id + '';
Also make sure you try the next solution, since the first one is SQL Injection compatible.
Use sp_executesql parameters
You can also use the parameters inside your #Cmd without doing the concatenation yourself but by passing the parameters to sp_executesql
Also I suggest you to QUOTENAME the #FullTableName parameter in case of spaces inside table's name.
DECLARE #Cmd nvarchar(150) = N'UPDATE QUOTENAME(#FullTableName) SET Name = #Name, Description = #Description WHERE ID = #Id;'
EXEC sp_executesql #Cmd, #FullTableName, #Name, #Description, #Id;
The advantage doing so, is you avoid any parameters not checked by the application to be able to do SQL Injection.
Reference :
https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-executesql-transact-sql
You have to add the quotes yourself. I prefer to use QUOTENAME to keep all the quotes recognizable:
QUOTENAME(#FullTableName, '''')
You cannot use parameters for identifiers. But, you can use parameters for values:
DECLARE #Cmd nvarchar(150) = N'
UPDATE ' + #FullTableName + '
SET Name = #Name,
Description = #Description
WHERE ID = #Id';
EXECUTE sp_executesql #Cmd,
N'#Name nvarchar(50), #Description nvarchar(50), #Id nvarchar(10)',
#Name = #Name, #Description = #Description, #Id = #id;
Unfortunately, the dynamic table name still poses risks, both in terms of SQL injection and syntax errors. I am guessing this is "controlled" code, not code with user input, so the risks might be acceptable. However, you probably should use quotename().
That brings up another issue which is probably the crux of the problem. Why do you have multiple tables with the same columns? Could these -- should these -- all be stored in a single table? This type of code calls into question aspects of the data model.

How to Create DELETE Statement Stored Procedure Using TableName, ColumnName, and ColumnValue as Passing Parameters

Here is what i'm trying to do. I'm trying to create a stored procedure where I could just enter the name of the table, column, and column value and it will delete any records associated with that value in that table. Is there a simple way to do this? I don't know too much about SQL and still learning about it.
Here is what I have so far.
ALTER PROCEDURE [dbo].[name of stored procedure]
#TABLE_NAME varchar(50),
#COLUMN_NAME varchar(50),
#VALUE varchar(5)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #RowsDeleted int;
DECLARE #sql VARCHAR(500);
SET #sql = 'DELETE FROM (name of table).' + #TABLE_NAME + ' WHERE ' + #COLUMN_NAME + '=' + '#VALUE'
EXEC(#sql)
SET #RowsDeleted=##ROWCOUNT
END
GO
Couple issues
First, you don't need (name of table)
SET #sql = 'DELETE FROM ' + #TABLE_NAME + etc.
In general you should try to include the appropriate schema prefix
SET #sql = 'DELETE FROM dbo.' + #TABLE_NAME + etc.
And in case your table name has special characters perhaps it should be enclosed in brackets
SET #sql = 'DELETE FROM dbo.[' + #TABLE_NAME + ']' + etc.
Since #Value is a string, you must surround it with single quotes when computing the value for #SQL. To insert a single quote into a string you have to escape it by using two single quotes, like this:
SET #SQL = 'DELETE FROM dbo.[' + #TABLE_NAME + '] WHERE [' + #COLUMN_NAME + '] = '''' + #VALUE + ''''
If #VALUE itself contains a single quote, this whole thing will break, so you need to escape that as well
SET #SQL = 'DELETE FROM dbo.[' + #TABLE_NAME + '] WHERE [' + #COLUMN_NAME + '] = '''' + REPLACE(#VALUE,'''','''''') + ''''
Also, ##ROWCOUNT will not populate from EXEC. If you want to be able to read ##ROWCOUNT, use sp_ExecuteSQL instead
EXEC sp_ExecuteSql #SQL
And finally, let me editorialize for a minute--
This sort of stored procedure is not a great idea. I know it seems pretty cool because it is flexible, and that kind of thinking is usually smart when it comes to other languages, but in the database world this approach causes problems, e.g. there are security issues (e.g. injection, and the fact that you need elevated privileges to call sp_executeSql) and there issues with precompilation/performance (because the SQL isn't known ahead of time, SQL Server will need to generate a new query plan each and every time you call this) and since the caller can supply any value for table and column name you have no idea whether this delete statement will be efficient and use indexes or if it will cause a huge performance issue because the table is large and the column is not indexed.
The proper approach is to have a series of appropriate stored procedures with strongly-typed inputs that are specific to each data use case where you need to delete based on criteria. Database engineers should not be trying to make things flexible; you should be forcing people to think through what exactly they are going to need, and implement that and only that. That is the only way to ensure people are following the rules, keeping R/I intact, efficient use of indexes, etc.
Yes, this may seem like repetitive and redundant work, but c'est la vie. There are tools available to generate the code for CRUD operations if you don't like the extra typing.
In addition to some of the information John Wu provided you have to worry about data types and ##ROWCOUNT may not be accurate if there are triggers on your tables and things..... You can get around both of those issues though by casting to nvarchar() and using OUTPUT clause with a temp table to do the COUNT().
So just for fun here is a way you can do it:
CREATE PROCEDURE dbo.[ProcName]
#TableName SYSNAME
,#ColumnName SYSNAME
,#Value NVARCHAR(MAX)
,#RecordCount INT OUTPUT
AS
BEGIN
DECLARE #SQL NVARCHAR(1000)
SET #SQL = N'IF OBJECT_ID(''tempdb..#DeletedOutput'') IS NOT NULL
BEGIN
DROP TABLE #DeletedOutput
END
CREATE TABLE #DeletedOutput (
ID INT IDENTITY(1,1)
ColumnValue NVARCHAR(MAX)
)
DELETE FROM dbo.' + QUOTENAME(#TableName) + '
OUTPUT deleted.' + QUOTENAME(#ColumnName) + ' INTO #DeletedOutput (ColumnValue)
WHERE CAST(' + QUOTENAME(#ColumnName) + ' AS NVARCHAR(MAX)) = ' + CHAR(39) + #Value + CHAR(39) + '
SELECT #RecordCountOUT = COUNT(ID) FROM #DeletedOutput
IF OBJECT_ID(''tempdb..#DeletedOutput'') IS NOT NULL
BEGIN
DROP TABLE #DeletedOutput
END'
DECLARE #ParmDefinition NVARCHAR(200) = N'#RecordCountOUT INT OUTPUT'
EXECUTE sp_executesql #SQL, #ParmDefinition, #RecordCountOUT = #RecordCount OUTPUT
END
So the use of QOUTENAME will help against the injection attack but not be perfect. And I use CHAR(39) instead of the escape sequence for a single quote on value because I find it easier when string building at that point.... By using Parameter OUTPUT from sp_executesql you can still return your count.
Keep in mind just because you can do something in SQL doesn't always mean you should.

exec in stored procedure will get the benefits of stored procedure

if i put something
DECLARE #Query VARCHAR(8000)
SET #Query = 'select * from subscriber
where sbs_userid = ' + cast(#UserID as varchar) + '
and SBS_Status in (select statusFlag from #tmpStatusFlag)
and SBS_SourceFlag in (select sourceFlag from #tmpSourceFlag)'
IF (#FirstName !='')
SET #Query = #Query + ' and SBS_FirstName like ''%' + #FirstName + '%'''
IF(#LastName !='')
SET #Query = #Query + ' and SBS_LastName like ''%' + #LastName + '%'''
IF(#Phone !='')
SET #Query = #Query + ' and SBS_WorkPhone like ''%' + #Phone + '%'''
IF(#EmaiAdderess !='')
SET #Query = #Query + ' and SBS_EmailAddress like ''%' + #EmaiAdderess + '%'''
IF(#City !='')
SET #Query = #Query + ' and SBS_City like ''%' + #City + '%'''
IF(#SubListId !='-1')
SET #Query = #Query + ' and SBS_SubListId like ''%' + #SubListId + '%'''
SET #Query = #Query + ' order by SBS_CreationDate desc'
EXEC (#Query)
in my stored procedure .
my question is still i get the benefits of stored procedure or is it a wrong approach
i never use this but my Team Leader used this to speed up the stored proceure . is it ok ?
EDITED
IF we use sp_executesql instead of exec then we can take the benefits of stored procedure.and is this ok with this ?
No.
You would need to use EXEC sp_executesql to get the advantages of parameterisation (plan reuse - protection from SQL injection).
These leading wild card searches are going to be expensive anyway. Have you considered Full Text Indexing?
Following the edit
If we use sp_executesql instead of exec then we can take the benefits
of stored procedure.and is this ok with this ?
Not all the benefits of stored procedures. A couple of advantages that stored procedures would still have over sp_executesql would be that you could then use ownership chaining and only need to grant exec permissions on the stored procedure rather than select on the underlying objects. Additionally having static stored procedures rather than dynamic SQL strings can make it easier to find dependant objects when refactoring the database.
However in your case you would need 64 separate procedures for each combination of presence/absence for the 6 optional parameters and this number grows exponentially as more optional parameters are added.
See Dynamic Search Conditions in T-SQL Version for SQL 2005 and Earlier for a thorough discussion of this topic.