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.
Currently I am working on an audit trail using SQL Server triggers to identify inserts, updates and deletes on tables.
Tables can be created dynamically in the database, therefore when this happens I need to create the trigger dynamically.
Therefore at this point I call a stored procedure and pass in the table name.
CREATE PROCEDURE [dbo].[AUDIT_CreateTableTrigger]
#STR_TableName NVARCHAR(MAX)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #STR_Trig NVARCHAR(MAX) = ''
SET #STR_Trig = #STR_Trig + '
CREATE TRIGGER [dbo].[' + #STR_TableName + '_Audit] ON [dbo].[' + #STR_TableName + ']
WITH EXECUTE AS CALLER AFTER
INSERT, UPDATE, DELETE AS
BEGIN
-- do the insert stuff
-- update
-- + delete
END'
EXEC (#STR_Trig) -- then execute the sql
My issue is that I am noticing that the exec isn't reading the statement completely and cuts the procedure off.
I need a way of executing a long piece of SQL code (I have one solution, this involves splitting the dynamic SQL into 3 triggers i.e insert, update and delete to get around this, however would prefer to keep 1 trigger to handle all)
Any suggestions would be appreciated, Thanks
Got this issue fixed: Broke up the query see below for solution
DECLARE #sql1 NVARCHAR(4000) = '',
#sql2 NVARCHAR(4000) = '',
#sql3 NVARCHAR(MAX)
SET #sql1 += '
CREATE TRIGGER [dbo].[' + #STR_TableName + '_Audit] ON [dbo].[' + #STR_TableName + ']
WITH EXECUTE AS CALLER AFTER
INSERT, UPDATE, DELETE AS
BEGIN
BEGIN TRY
--sql query
'
SET #sql2 = '
--more sql query
END'
SET #sql3 = CAST(#sql1 AS nvarchar(MAX)) + CAST (#sql2 AS nvarchar(MAX))
EXEC sp_executesql #sql3
I'm having issues with a dynamic SQL script in particular this bit:EXEC('
if db_id(''' + $(db) + ''') is null
BEGIN
CREATE DATABASE ' + $(db) + '
END
The if statement part seems to work fine, I know this because if the database exists then the create database line is not run but when it needs to run I just get syntax errors near that line.
I have also tried:
CREATE DATABASE ''' + $(db) + '''
with no luck
Any Ideas?
DECLARE #DB_NAME NVARCHAR(128) = N'Test_DB'
DECLARE #Sql NVARCHAR(MAX);
IF DB_ID(#DB_NAME) IS NULL
BEGIN
SET #Sql = N' CREATE DATABASE ' + QUOTENAME(#DB_NAME)
EXECUTE sp_executesql #Sql
END
Important Note
Make sure your database name is in accordance with the Rules for Regular Identifiers
I have used this SP before. Now, I am trying to permission a user to 50 odd databases that start with the same letters, using the code below. It looks like it does not like "GO" in the code. Why is that ? and what is the work around?
Thanks for your time.. :)
RM
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN
Use [?]
Go
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts]
GO
EXEC sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts''
GO
END
'
I just explained this in another question yesterday (here). This essence is this: GO isn't a SQL statement, it's an SSMS/SQLCMD command that is used to separate batches (groups of SQL statements that are compiled together). So you cannot use it in things like stored procedures or Dynamic SQL. Also, very few statement contexts can cross over a GO boundary (transactions and session-level temp tables are about it).
However, because both stored procedures and Dynamic SQL establish their own separate batches/execution contexts, you can use these to get around the normal need for GO, like so:
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN
Use [?]
EXEC(''
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts]
'')
EXEC('' EXEC sp_addrolemember N''''db_owner'''', N''''MYDOMAIN\Analysts'''' '')
END
'
The word GO is a batch separator and is not a SQL keyword. In SSMS you can go to options and change it to anything - COME for example.
Try this:
exec sp_MSForEachDB
'
IF ''?'' LIKE ''MYDBNames%''
BEGIN;
Use [?];
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts];
EXEC sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts'';
END;'
Just stop using GO, it's not necessary (and it's not even T-SQL; it's just a batch separator for Management Studio).
Stop using sp_MSForEachDB. Oh, the problems (if you want proof, see here, here and here).
Here is code that does this without using any stored procedure:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'USE ' + QUOTENAME(name) + ';
CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts];
EXEC sys.sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts'';
-- actually should use ALTER ROLE now
'
FROM sys.databases
WHERE state = 0 -- online
AND name LIKE N'MyDBName%';
PRINT #sql;
-- EXEC sys.sp_executesql #sql;
Like #RBarryYoung's answer, an embedded exec() can be used to avoid needing a "go" when the command carries the requirement that it must be the "first within a batch." I don't think the question's example needs a "go" but the headline itself may lead people here.
Here is a basic SP to wrap this that support's DDL statements like "create proc" and provides some error messages. I'm probably the ~5th person to do this and this one isn't the end all.
Blessing/Curse: There is a call to Common.ufn_UsvToNVarcharKeyTable() which you may need to patch in your own CSV splitter or remove:
alter proc [Common].[usp_ForEachDatabase]
#pSql nvarchar(max),
#pDatabaseNameLikeOpt nvarchar(300) = null, -- Optional pattern to match with like
#pDatabaseNameCsvOpt nvarchar(max) = null, -- Optional list of DBs
#pJustPrintDbNames bit = 0, -- Don't exec, just print database names
#pDebug bit = 1 -- may add additional print statements
as
/*-----------------------------------------------------------------------------------------------------------------
Purpose: Execute SQL on each database. Replacement for the standard sp_MSForEachDB which has a size character
limit and requires an exec() within for DDL commands which must be the first in a batch.
Sources: Ideas from http://www.experts-exchange.com/Database/MS-SQL-Server/SQL-Server-2005/Q_26337388.html
http://stackoverflow.com/questions/20125430
http://stackoverflow.com/questions/1819095/
Modified By Description
---------- ---------- -----------------------------------------------------------------------------------
2014.10.14 crokusek Initial version. Pieces from internet.
--------------------------------------------------------------------------------------------------------------*/
begin try
declare databaseCursor cursor local forward_only static for
select IsNull(sd.Name, ud.Name) as Name,
--
-- If a list was specified, flag when a name was not found
--
convert(bit, iif(sd.Name is null, 1, 0)) as IsNotFound
from
(
select Name from sys.databases
where Name not in ('master', 'tempdb', 'model', 'msdb')
and is_distributor = 0 -- http://stackoverflow.com/questions/1819095/
) sd
full outer join ( select Value as Name from Common.ufn_UsvToNVarcharKeyTable(#pDatabaseNameCsvOpt, ',' ) ) ud
on ud.Name = sd.Name
where (#pDatabaseNameLikeOpt is null or IsNull(sd.Name, ud.Name) like #pDatabaseNameLikeOpt)
and (#pDatabaseNameCsvOpt is null or ud.Name is not null)
order by IsNull(sd.Name, ud.Name);
declare
#matchingDatabaseNames nvarchar(max),
#databaseName nvarchar(300),
#isNotFound bit,
#errorCount int = 0,
#successCount int = 0,
--
-- Use an embedded exec() to place the user command(s) in a separate context to avoid errors like:
-- CREATE/ALTER PROCEDURE must be the first statement in a query batch.
--
#sqlExec nvarchar(max) = N'exec (''' + replace(#pSql, '''', '''''') + ''')';
open databaseCursor
fetch next from databaseCursor into #databaseName, #isNotFound;
while ##fetch_status = 0
begin
if (#isNotFound = 1)
begin
print 'Error: Database ' + #databaseName + ' was not found.';
set #errorCount += 1;
end
else
begin
set #matchingDatabaseNames = coalesce(#matchingDatabaseNames + ',', '') + #databaseName; -- Create Csv
print 'Database: ' + #databaseName;
if (#pJustPrintDbNames = 0)
begin
declare
#execSql nvarchar(max) = 'use ' + #databaseName + ';' + char(10) + #sqlExec;
begin try
exec (#execSql)
set #successCount += 1;
end try
begin catch
select #databaseName as [Database],
error_number() as ErrorNumber,
error_severity() as ErrorSeverity,
error_state() as ErrorState,
error_procedure() as ErrorProcedure,
error_line() as ErrorLine,
error_message() as ErrorMessage;
set #errorCount += 1;
end catch
end
end
fetch next from databaseCursor into #databaseName, #isNotFound;
end
if (#pJustPrintDbNames = 1)
print #matchingDatabaseNames; -- this can then be used as input
else
print 'Completed with ' + convert(varchar(30), #errorCount) + ' Errors ' +
'and ' + convert(varchar(30), #successCount) + ' Successes.';
end try
begin catch
if (xact_state() = -1)
rollback;
-- Log / Rethrow
end catch
go
Usage:
declare
#sql nvarchar(max) = N'
create proc usp_ReplaceEachSingleQuoteWithTwoSingleQuotesInTheDefinition
...';
exec [Common].[usp_ForEachDatabase]
#pSql = #sql,
#pDatabaseNameLikeOpt = null,
#pDatabaseNameCsvOpt = null,
#pJustPrintDbNames = 0,
#pDebug = 1;
Why not use a cursor?
They are great for management tasks like this. As long as the record set is small, they perform well.
The code below creates a SQL statement for each database that uses the ; to combine multiple tasks.
Unlike the code above, it remove any existing users before creating a new one. Avoids a possible issue.
Then it executes the code. If you want, you could even log these adhoc requests to a internal table and add error checking.
Good luck.
--
-- EXEC same statements against several like databases
--
-- Declare local variables
DECLARE #STMT NVARCHAR(4000);
-- Allocate cursor, return table names
DECLARE MYTRG CURSOR FAST_FORWARD FOR
SELECT
' use [' + ltrim(rtrim(d.name)) + ']; ' +
' IF EXISTS (SELECT * FROM sys.database_principals WHERE name = N''[MYDOMAIN\Analysts]'') DROP USER [MYDOMAIN\Analysts]; ' +
' CREATE USER [MYDOMAIN\Analysts] FOR LOGIN [MYDOMAIN\Analysts] WITH DEFAULT_SCHEMA=[DBO]; ' +
' sp_addrolemember N''db_owner'', N''MYDOMAIN\Analysts''; ' as STMT
FROM master.dbo.sysdatabases d
WHERE d.name like 'MYDBNames%'
ORDER BY d.name;
-- Open cursor
OPEN MYTRG;
-- Get the first row
FETCH NEXT FROM MYTRG INTO #STMT;
-- While there is data
WHILE (##FETCH_STATUS = 0)
BEGIN
-- Show detail database info
EXEC sp_executesql #STMT;
-- Get the first row
FETCH NEXT FROM MYTRG INTO #STMT;
END;
-- Close the cursor
CLOSE MYTRG;
-- Release the cursor
DEALLOCATE MYTRG;
I have a cursor which works fine but when it gets to this part of the script, it seems to still run the update even though the table doesn't exists:
SET #sql = 'IF (EXISTS (SELECT * FROM ps_vars_' + #datasetid + '))
BEGIN
UPDATE ps_vars_' + #datasetid + '
SET programming_notes = replace(programming_notes, ''Some of the variables listed are source variables.'')
END';
EXEC SP_EXECUTESQL #sql
What am I missing? The #datasetid variable gets passed in correctly too.
DECLARE #tablename sysname
SET #tablename = 'ps_vars' + #datasetid
IF (OBJECT_ID(#tablename, 'U') IS NOT NULL)
BEGIN
SET #sql = ' UPDATE ' + QUOTENAME(#tablename) + '
SET programming_notes = replace(programming_notes, ''Some of the variables listed are source variables.'') ';
EXEC sp_executesql #sql
END
When you use the EXISTS with the table name to see if the table exists you're actually trying to access the table - which doesn't exist. That's why you're getting an error, not because of your UPDATE statement.
Try this instead:
SET #sql = 'IF (OBJECT_ID(''ps_vars_' + #datasetid + ''') IS NOT NULL)
BEGIN
UPDATE ...
END'
Then think about what might be wrong with your database design that requires you to use dynamic SQL like this. Maybe your design is exactly how it needs to be, but in my experience 9 out of 10 times (probably much more) this kind of code is a symptom of a poor design.