Is there a way to make a procedure to search and replace a specific string in all procedures?
Maybe it would be easier to fix the application. Why does the software care about comments? Does it also barf on extended properties?
Assuming the comment is always the same, run this in Management Studio with results to text:
SET NOCOUNT ON;
SELECT 'GO
' + REPLACE([definition], '/* offending comment */', '')
FROM sys.sql_modules
WHERE [definition] LIKE '%/* offending comment */%';
This will yield a series of CREATE PROCEDURE commands separated by GO. You can't necessarily do a search/create for CREATE PROCEDURE and change it to ALTER in case you have actual CREATE PROCEDURE strings within the stored procedure body. So instead you can pull these results into your favorite text editor and do a search and replace for:
GO
CREATE PROCEDURE
Replacing it with:
GO
ALTER PROCEDURE
(Though this assumes that you don't have comments before the CREATE PROCEDURE line. If you do, you'll need to get more creative with your search and replace.)
If you don't need to worry about procedures that contain a valid string CREATE PROCEDURE - and assuming that your syntax is actually CREATE PROCEDURE and not CREATE PROC or random spacing between CREATE and PROC and that your system isn't case sensitive and you used create proc or Create Proc...
SET NOCOUNT ON;
SELECT 'GO
' + REPLACE(REPLACE([definition], '/* offending comment */', ''),
'CREATE PROCEDURE', 'ALTER PROCEDURE')
FROM sys.sql_modules
WHERE [definition] LIKE '%/* offending comment */%';
If you don't care about the existing tangible things associated with your stored procedures, e.g. permissions, you could also do it this way:
SELECT 'DROP PROCEDURE '
+ QUOTENAME(OBJECT_SCHEMA_NAME([object_id]))
+ '.' + QUOTENAME(OBJECT_NAME([object_id])) + ';
' + 'GO
' + REPLACE([definition], '/* offending comment */', '')
FROM sys.sql_modules
WHERE [definition] LIKE '%/* offending comment */%';
This will generate a script that is much closer to ready to go, since it just drops the procedure and re-creates it (so doesn't need to change CREATE to ALTER), but it is not very common that you can just drop and re-create objects because of permissions and/or dependencies.
CREATE OR REPLACE or something similar in DDL would make this much easier to script out. If you feel this would be a valuable addition to SQL Server, please vote for the following suggestion (and add your use case, or why you think this would be valuable, to the comments):
http://connect.microsoft.com/SQLServer/feedback/details/127219/create-or-replace
How about Generating the Scripts of ALL those stored procedure as a .sql file and then replacing the comment in that generated file with the help of CTRL + H. Isn't that the easier option?
Related
I have inherited a bunch of stored procedures basically as a shell and inside the quotes is this huge dynamic SQL with lots of conditions, calculations and case statements, however the table name in the FROM clause within this dynamic SQL changes every quarter.
Now before I get flamed, I like to simply say that I inherited them, how it was designed was before me. So each quarter when a call is made out to these stored procedures, it comes with the actual table name passed as a parameter and then the dynamic SQL concatenates the table name.
The problem with this approach is that, with each run over time, the prior designers simply tacked on more criteria as conditions and calculations. But the dynamic SQL string has a length limit to it. Further it becomes quite difficult to maintain and debug.
CREATE PROCEDURE .....
#dynSQL1 = 'SELECT......
FROM' + strTblName + '
WHERE.....
GROUP BY....'
...
EXEC #dynSQL1
GO
However, I like to ask you all, is there a way to turn this stored procedure with this huge dynamic SQL string into a plain vanilla stored procedure based on a parameterized table name?
My main goal is two fold, one, get away from the long string as dynamic SQL and two, easier maintenance and debugging. I would like to think in the more current version of SQL Server from SQL Server 2016/2017 and on, this issue is addressed.
Your thoughts and suggestions is greatly appreciated.
~G
So each quarter when a call is made out to these stored procedures, it comes with the actual table name passed as a parameter and then the dynamic SQL concatenates the table name.
You could change the procedure to codegen other stored procedures instead of running dynamic SQL. EG:
CREATE PROCEDURE admin.RegenerateProcedures #tableName
as
begin
declare #ddl nvarchar(max) = '
create or alter procedure dbo.SomeProc
as
begin
SELECT......
FROM dbo.' + quotename(#tableName) + '
WHERE.....
GROUP BY....
end
'
EXEC ( #ddl )
. . .
end
GO
While working on a database documentation topic, I encountered a situation and got stuck. Thanks in advance for potential help. Here are the facts:
I am trying to obtain only the body of certain stored procedures in my database.
Anything else, such as SP parameters or options - I don't need.
Googled around and all I've found is ways to obtain the entire SP text - most of them already known.
I've put together a solution as you can see below but it's not covering all the cases and it's not pretty.
Having defined this test SP:
CREATE PROCEDURE dbo.returnDay
#addTheseDays SMALLINT = 0
AS
-- This is just a test SP that retrieves
-- the current date if #addTheseDays isn't defined,
-- otherwise the current day + #addTheseDays
SELECT GETDATE() + #addTheseDays;
GO
What didn't help:
-- This doesn't help since it retrieves all SP text (including parameters and options part)
EXEC sp_helptext 'dbo.returnDay';
-- The ROUTINE_DEFINITION column also holds the entire SP text.
SELECT *
FROM INFORMATION_SCHEMA.ROUTINES;
Workaround I've done and works, with exceptions:
DECLARE #spText VARCHAR(MAX)
SELECT #spText = object_definition(object_id('dbo.returnDay'))
SELECT SUBSTRING(#spText, CHARINDEX('AS', #spText, 0) + 2 , LEN(#spText)) AS spBody
This "ugly" string manipulation workaround works but only when the SP does not have "WITH EXECUTE AS CALLER" option or the parameters don't have "AS" as part of their name. In these cases then I get extra, unneeded info regarding the SP (again, only need SP body - only what is between the AS and batch terminator).
Also tried to use the first BEGIN and last END in the SP body (and get what's between) but since these are not mandatory in SQL Server and some SPs don't have them then I can't rely on them.
Any ideas and/or suggestions on how can I get only the SP body (code and comments) in a better way?
As a quick and dirty one-off you can look for AS as a single word, taking into account the single EXECUTE AS exception.
This will fail for any comments containing -AS- of course.
SELECT SUBSTRING(
#spText,
PATINDEX('%[ ' + CHAR(13) + CHAR(10) + CHAR(9) + ']AS[ ' + CHAR(13) + CHAR(10) + CHAR(9) + ']%', REPLACE(#spText, 'WITH EXECUTE AS CALLER', 'WITH EXECUTE ?? CALLER')) + 3,
LEN(#spText))
You will need parse the T-SQL sproc to obtain just the body. Have a look at RegEx to parse stored procedure and object names from DDL in a .sql file C#. There are several CLR assemblies that give you regex access within SQL, or perform in your application language if not purely SQL for your application.
I want to write a TSQL stored procedure that creates a database with a specified name, and pre-populates it with some schema.
So I use lots of EXEC statements:
EXEC('CREATE TABLE ' + #dbName + '.dbo.MyTable (...)');
etc, along with some CREATE PROCEDURE, CREATE FUNCTION etc. However, the problem comes from when I want to create a type, as CREATE TYPE statements can't have the database specified, and you can't have USE #dbName within the stored procedure.
How can I create a type in another database in a stored procedure?
There are certain commands that can't use used as ssarabando suggests, among them is CREATE SCHEMA, which throws Msg 111 when used in with that technique.
The work around is to nest dynamic SQL blocks as follows:
exec('use tempdb; exec sp_executesql N''create schema test'' ')
The outer block does nothing except change the database, so that the inner block has the correct context when it is executed.
Notice that the inner parameter to sp_executesql needs two single quotes.
You may want to take a look at sp_addtype instead. You can execute this in the database you want.
You could also use this, for example:
EXEC('use ' + #dbName + ';create type somename from int not null;')
That'll select the correct database before creating the type.
I am new to Transact SQL programming.
I have created a stored procedure that would drop and create an existing synonym so that it will point to another table. The stored procedure takes in 2 parameters:
synonymName - an existing synonym
nextTable - the table to be point at
This is the code snippet:
...
BEGIN TRAN SwitchTran
SET #SqlCommand='drop synonym ' + #synonymName
EXEC sp_executesql #SqlCommand
SET #SqlCommand='create synonym ' + #synonymName + ' for ' + #nextTable
EXEC sp_executesql #SqlCommand
COMMIT SwitchTran
...
We have an application that would write data using the synonym regularly.
My question is would I run into a race condition where the synonym is dropped, while the application try to write to the synonym?
If the above is a problem, could someone give me suggestion to the solution.
Thanks
Yes, you'd have a race condition.
One way to manage this is to have sp_getapplock after BEGIN TRAN in Transaction mode and trap/handle the return status as required. This will literally serialise (in the execution sense, not isolation) callers so only one SPID executes at any one time.
I'm fairly certain you will indeed get race conditions. Synonym Names are intended to be used for shortening the name of an object and aren't supposed to change any more often than other objects. I'm guessing by your description that you are using it for code reuse. You are probably better off using Dynamic SQL instead, which incidentally you already are.
For more information on Dynamic SQL you might want to consider a look at this article on by Erland Sommarskog that OMG Poinies references in a lot of his answers. Particularly the section on Dealing with Dynamic Table and Column Names which I've quotes below
Dealing with Dynamic Table and Column
Names
Passing table and column names as
parameters to a procedure with dynamic
SQL is rarely a good idea for
application code. (It can make
perfectly sense for admin tasks). As
I've said, you cannot pass a table or
a column name as a parameter to
sp_executesql, but you must
interpolate it into the SQL string.
Still you should protect it against
SQL injection, as a matter of routine.
It could be that bad it comes from
user input.
To this end, you should use the
built-in function quotename() (added
in SQL 7). quotename() takes two
parameters: the first is a string, and
the second is a pair of delimiters to
wrap the string in. The default for
the second parameter is []. Thus,
quotename('Orders') returns [Orders].
quotename() takes care of nested
delimiters, so if you have a really
crazy table name like Left]Bracket,
quotename() will return
[Left]]Bracket].
Note that when you work with names
with several components, each
component should be quoted separately.
quotename('dbo.Orders') returns
[dbo.Orders], but that is a table in
an unknown schema of which the first
four characters are d, b, o and a dot.
As long as you only work with the dbo
schema, best practice is to add dbo in
the dynamic SQL and only pass the
table name. If you work with different
schemas, pass the schema as a separate
parameter. (Although you could use the
built-in function parsename() to split
up a #tblname parameter in parts.)
While general_select still is a poor
idea as a stored procedure, here is
nevertheless a version that summarises
some good coding virtues for dynamic
SQL:
CREATE PROCEDURE general_select #tblname nvarchar(128),
#key varchar(10),
#debug bit = 0 AS DECLARE #sql nvarchar(4000)
SET #sql = 'SELECT col1, col2, col3
FROM dbo.' + quotename(#tblname) + '
WHERE keycol = #key' IF #debug = 1
PRINT #sql EXEC sp_executesql #sql, N'#key varchar(10)', #key = #key
- I'm using sp_executesql rather than EXEC().
- I'm prefixing the table name with dbo.
- I'm wrapping #tblname in quotename().
- There is a #debug parameter.
I want to write a stored proc which will use a parameter, which will be the table name.
E.g:
#tablename << Parameter
SELECT * FROM #tablename
How is this possible?
I wrote this:
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetAllInterviewQuestions]
#Alias varchar = null
AS
BEGIN
Exec('Select * FROM Table as ' #Alias)
END
But it says incorrect syntax near #Alias.
Well, firstly you've omitted the '+' from your string. This way of doing things is far from ideal, but you can do
DECLARE #SQL nvarchar(max)
SELECT #SQL = 'SELECT * FROM ' + QuoteName(#Alias)
Exec(#SQL)
I'd strongly suggest rethinking how you do this, however. Generating Dynamic SQL often leads to SQL Injection vulnerabilities as well as making it harder for SQL Server (and other DBs) to work out the best way to process your query. If you have a stored procedure that can return any table, you're really getting virtually no benefit from it being a stored procedure in the first place as it won't be able to do much in the way of optimizations, and you're largely emasculating the security benefits too.
You'll have to do it like this:
exec('select * from '+#tablename+' where...')
But make sure you fully understand the risks, like SQL injection attacks. In general, you shouldn't ever have to use something like this if the DB is well designed.
Don't you mean
Exec('SELECT * FROM ' + #tableName)
Also, the error you get is because you've forgotten a + before #Alias.
Often, having to parameterize the table name indicates you should re-think your database schema. If you are pulling interview questions from many different tables, it is probably better to create one table with a column distinguishing between the questions in whatever way the different tables would have.
Most implementations of SQL do not allow you to specify structural elements - table names, column names, order by columns, etc. - via parameters; you have to use dynamic SQL to parameterize those aspects of a query.
However, looking at the SQL, you have:
Exec('SELECT * FROM Table AS ' #Alias)
Surely, this would mean that the code will only ever select from a table called 'Table', and you would need to concatenate the #Alias with it -- and in many SQL dialects, concatenation is indicated by '||':
Exec('SELECT * FROM Table AS ' || #Alias)
This still probably doesn't do what you want - but it might not generate a syntax error when the procedure is created (but it would probably generate an error at runtime).