Views as Stored Procedures in Schema Change script - sql

I have writing a Change script to update the schema in the Production DB. I have to drop a View and recreate them since one of the column in that view is removed now.
I'm planning to use,
DECLARE #sql NVARCHAR(MAX);
SET #sql = '<View Definition>';
EXEC sp_executesql #sql;
If any, What are the pitfalls of using this approach ? What are the alternatives that you would suggest ?
I'm having the whole script under a transaction with a Try catch block. So when there is no error the script will be committed else rolled back.I tried to use CREATE VIEW Command as below, but I get error after introduction TRASACTION and TRY-CATCH BLOCKS.This reason why am going for Dynamic SQL statement - EXEC sp_executesql.
The error is "CREATE VIEW MUST BE THE ONLY STATEMENT IN THE BATCH"
IF EXISTS ( SELECT * FROM sys.views WHERE name = 'VwViewName')
BEGIN
DROP VIEW VwViewName
END
IF NOT EXISTS ( SELECT * FROM sys.views WHERE name = 'VwViewName')
BEGIN
CREATE VIEW VwViewName
<VIEW DEFINITION>
END

Since you are running schema update, you should comply minimal things like
make it repeatable
have it in your versioning system
and test it in your testing environment before running in production.
In order to make it re-runable it's common practice to check if the object exists and drop it, and then to create it again.
Dynamic SQL will not add any functionality that the IF clause has.
For example:
IF EXISTS(SELECT * FROM sys.views WHERE name = 'MyView')
BEGIN
DROP VIEW [MyView]
END
GO
CREATE VIEW MyView AS
SELECT [Columns list ]
FROM [MyTable]
GO
The only case that I know which justifies dynamic SQL is if you want to preserve grant / deny, in such case you can use following syntax.
IF NOT EXISTS(SELECT * FROM sys.views WHERE name = 'MyView')
BEGIN
EXEC sp_executesql N'CREATE VIEW MyView AS SELECT 1 AS Dummy'
END
GO
ALTER VIEW MyView AS
SELECT [Columns list ]
FROM [MyTable]
go
GRANT ....
I wouldn't create a Dynamic SQL to fill the few times that I have to repeat the names in any of the scripts below. IMHO the deployment script should be as plain as possible.

Related

Dynamically run SQL queries

I want to run SQL queries which retrieve from a database according to user input.
Lets assume there is a table named Queries and the fields of the table are Index, Query, Name.
query - select * from Student
name - GetStudents
Index - 1
When user clicks a button an index will be passed into the server and a query match with that index will be triggered. Assume there are no inputs into queries.
Lets say there are 5 rows in the table and when user pass 3,the third query will be run. When user pass 4 the fourth query will be run.
I think I can simply do this by storing the query as a string on table retrieving the query and run. But I'm not sure whether this workaround is efficient.
Please help me with these points.
Is this approach is okay or is there any better workaround that I can follow.
Is it okay to store query as a string in a table.
Is there any workaround that I can create Stored Procedures pragmatically using asp.net in SQL server management studio.
I'm using ASP.Net and SQL server.
Note that here I can't use Stored Procedures to do this task. Because there is another front-end where user can insert queries into table that I have mentioned above. User has no access to use SQL server management studio.
In theory, yes you certainly could store the query string and then use sp_executesql to run that particular query string.
However, CAUTION. If you have a front end that allows a user to write and submit a query then how are you sanitizing that input? Is there anything to prevent the user submitting 'DROP DATABASE' as the query or event introducing other SQL injection attacks?
A better approach would be to create the procedures (assuming that the activities are all standard tasks) and allowing the user to select which procedure to execute.
You could check the integrity of the dynamic select statement by executing it under the most restrictive security context (a dbuser that has readonly permissions). To take it a bit further, you could also wrap the dynamic select statement into an ever changing dynamic container/string (ever changing part + dynamic query + ever changing part) and suppress any errors that happen during the validation/integrity check.
You cannot rely solely on sanitization because you'll end up in a never-ending catching-up struggle.
use mydbxyz;
go
--create a readonly dbuser
create user readonlydbuser without login;
alter role db_datareader add member readonlydbuser;
alter role db_denydatawriter add member readonlydbuser;
go
--procedure to execute dynamic select (no cte, no variables, just selects)
create or alter procedure execute_simpleselect #sqlinput nvarchar(max) = null
with execute as owner
as
begin
set nocount on;
if nullif(#sqlinput, '') is null
begin
--nothing to execute
return;
end
--check if sql input is a valid/simple select query
declare #foocontrol tinyint;
declare #tblalias sysname = quotename(newid());
declare #sqlcheck nvarchar(max) = N'
select #var = 1;
begin transaction;
begin try
select top (0) #var = '+ #tblalias + '.mycol
from
(
select 1 as mycol
where exists
(
'
+ #sqlinput +
'
)
) as '+ #tblalias + N'
end try
begin catch
select #var = 2;
end catch
rollback transaction;
';
/*
create user readonlydbuser without login;
alter role db_datareader add member readonlydbuser;
alter role db_denydatawriter add member readonlydbuser;
*/
--catch errors
begin try
--change context to a readonlyuser
execute as user='readonlydbuser'; --if this dbuser does not exist, nothing executes
exec sys.sp_executesql #stmt = #sqlcheck, #params = N'#var tinyint output', #var = #foocontrol output;
end try
begin catch
--do nothing, suppress errors
end catch
revert;
--if #foocontrol is not 1, the query cannnot be executed (syntactically incorrect, violation under minimal permissions etc)
if isnull(#foocontrol, 2) = 2
begin
raiserror('what are you trying to do?', 16, 1);
return;
end
--change to the callers security context
exec as caller;
exec sys.sp_executesql #stmt = #sqlinput;
end
--test
exec execute_simpleselect #sqlinput = 'select * from sys.objects';
exec execute_simpleselect #sqlinput = 'create table dbo.testtbl(id int)';
exec execute_simpleselect #sqlinput = 'drop table dbo.tablexzy';
exec execute_simpleselect #sqlinput = 'select user_name()';

how to have dynamically create & alter in the sql script?

how to have dynamically create & alter in the sql script?
Instead of having
if exits - drop
we are looking to have
if exits - alter.
How to handle such scenario.
To clarify my comments above, SQL Server 2016 SP1 released a CREATE OR ALTER statement that will either create an object that doesn't already exists or modify the object if it does. This is only allowed on certain objects such as stored procedures, triggers, functions, and views. Tables, indexes, and other objects that are allocated storage cannot be used in by the CREATE OR ALTER statement. Also note that since they're persisted on disk, indexes views are not permitted to be used by this. A basic example of the syntax is below.
CREATE OR ALTER PROCEDURE SP_TestStoredProcedure
AS
BEGIN
SELECT
Column1,
Column2,
Column3
FROM YourTable
END
Here is a trick I've used.
-- for testing, not needed for real -- DROP PROCEDURE dbo.uspDoSomething
GO
IF NOT EXISTS ( SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE = 'PROCEDURE' and ROUTINE_NAME = 'uspDoSomething' )
BEGIN
EXEC ( 'CREATE PROCEDURE dbo.uspDoSomething(#i INT) AS BEGIN RAISERROR (''Stubbed version'' , 16, -1) END' )
END
GO
--test only
EXEC dbo.uspDoSomething 0
GO
ALTER PROCEDURE dbo.uspDoSomething(#PatientKey INT)
AS
BEGIN
SELECT ##VERSION
END
GO
--test only
EXEC dbo.uspDoSomething 0
GO
Remember, an ALTER does not change all the PERMISSIONS you have on the script.
A DROP/ADD needs permissions reapplied.
Note, you did not originally mention your sql-server version. This trick works with 2014 and before. Obviously, newer versions with CREATE OR ALTER would be preferred over EXEC with dynamic sql.

running a stored proc from a different db

so I have a sproc in a db.. lets call this db A. This db makes use of tables (t1, t2) in another db. Lets call this db B.
okay, so the way i call it right now is: A.dbo.My_Proc but i get another error:
Invalid object name 'dbo.t1'.
so how i tried supplying a parameter. In my Sproc i do, select * from #dbname.dbo.t1
however that results in an error. I can't put the sproc in db B.
While it is sufficient to hardcode it (if there is a way), db B changes every year, so it would be nice to "supply" a database.
I tried using use B; go but it gives me error saying can't have that in a sproc.
You could create a synonym:
EDIT: I see now that the synonym is needed for the table, not the proc. Got them switched. So you could create a synonym in database A for the table in database B:
USE A;
CREATE SYNONYM dbo.t1 FOR B.dbo.t1;
Then your procedure in A could simply say:
SELECT * FROM dbo.t1;
Without having to manually supply the database name at all, the query knows (based on the synonym) to get the data from the table in database B. When the database B changes to C, you can simply:
DROP SYNONYM dbo.t1;
CREATE SYNONYM dbo.t1 FOR C.dbo.t1;
If you used "real" database names in your narrative as opposed to arbitrary A/B names, it might lead to easier comprehension. Just a suggestion. :-)
/EDIT
The other option is to pass in the database name and construct via dynamic SQL. E.g. instead of select * from #dbname.dbo.t1 (which will never work), you could do:
DECLARE #sql NVARCHAR(MAX) = N'SELECT * FROM ' + QUOTENAME(#dbname) + '.dbo.t1;';
EXEC sp_executesql #sql;
But if this other database name really only changes once a year, I suggest that the synonym route is better overall.
When you execute exec someDB.dbo.SomeProc the execution context switches to someDB. So if the procedure issues a SELECT FROM dbo.t1 then dbo.t1 must be in someDB. IF you want the procedure to select from a 'supplied' database then the procedure must use dynamic-SQL:
create procedure someProc
#dbname sysname
as
begin
...
set #sql = N'SELECT ... FROM ' + quotename(#dbname) +N'.dbo.t1';
exec sp_executesql #sql;
...
end

Dynamic Datasource in SQL Server Stored Procudure

I have a SQL Server that houses Several Databases. I have a Main Database that holds several tables with entities and ID numbers. Then, each one of those entities has a correlating database (not a table, but database) with all of its information. For example, if the an entity in the MAIN database has an ID number of 1, there would be an SubDatabase1 Database on the same SQL Server.
Essentially, what I am trying to do is create a stored procedure in the MAIN Database, that collects data from the SUB Database, but the SUB database I collect from should be determined based on the ID number passed to the Proc.
I know this is totally incorrect, but I am wondering if someone can shine some light on this for me.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetInstallationCount
-- Add the parameters for the stored procedure here
#installId int=0
AS
BEGIN
SET NOCOUNT ON;
//Trying to make the DatabaseName dynamic here!!
select count(*) from dbo.Installation#installId.Names
END
GO
Thanks - J
Read up on how to create dynamic SQL, particularly sp_executesql. This should get you started:
DECLARE #theSql varchar(1000)
DECLARE #installId int
SET #installId = 1
SET #theSql = 'SELECT COUNT(*) FROM dbo.Installation' + CAST(#installId as nvarchar) + '.Names'
EXEC (#theSql)
You have to use dynamic SQL to do that. Table names and database names cannot be resolved at runtime in any other way.
Here is a good introduction to this technique by Scott Mitchell.
As often, the answer to such a question is dynamic SQL:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE GetInstallationCount
-- Add the parameters for the stored procedure here
#installId int=0
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql nvarchar(MAX)
SET #sql = 'select count(*) from dbo.Installation' + Cast(#installId as nvarchar) + '.Names'
EXECUTE dbo.sp_executesql #sql
END
GO
Definately could be done by building up the select string dynamically and executing but it would be nasty.
You could get very flashy and try create synonyms of the fly, use them in the queries and then drop them but I'm not sure it would be worth it.
Use synonyms. For example this sets synonym dbo.MySpecialTable to point to table dbo.SomeTable in database DB_3.
IF object_id(N'SN', N'dbo.MySpecialTable') IS NOT NULL
DROP SYNONYM dbo.MySpecialTable
CREATE SYNONYM dbo.MySpecialTable FOR [DB_3].[dbo].[SomeTable]
With this in place, write all your queries to use synonyms instead of real table names. Synonyms have DB scope, so manage "target switching" at one place, maybe in a stored procedure.

How to check if a stored procedure exists before creating it

I have a SQL script that has to be run every time a client executes the "database management" functionality. The script includes creating stored procedures on the client database. Some of these clients might already have the stored procedure upon running the script, and some may not. I need to have the missing stored procedures added to the client database, but it doesn't matter how much I try to bend T-SQL syntax, I get
CREATE/ALTER PROCEDURE' must be the first statement in a query batch
I've read that dropping before creating works, but I don't like doing it that way.
IF EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND name = 'MyProc')
DROP PROCEDURE MyProc
GO
CREATE PROCEDURE MyProc
...
How can I add check for the existence of a stored procedure and create it if it doesn't exist but alter it if it does exist?
I realize this has already been marked as answered, but we used to do it like this:
IF NOT EXISTS (SELECT * FROM sys.objects WHERE type = 'P' AND OBJECT_ID = OBJECT_ID('dbo.MyProc'))
exec('CREATE PROCEDURE [dbo].[MyProc] AS BEGIN SET NOCOUNT ON; END')
GO
ALTER PROCEDURE [dbo].[MyProc]
AS
....
Just to avoid dropping the procedure.
You can run procedural code anywhere you are able to run a query.
Just copy everything after AS:
BEGIN
DECLARE #myvar INT
SELECT *
FROM mytable
WHERE #myvar ...
END
This code does exactly same things a stored proc would do, but is not stored on the database side.
That's much like what is called anonymous procedure in PL/SQL.
Update:
Your question title is a little bit confusing.
If you only need to create a procedure if it not exists, then your code is just fine.
Here's what SSMS outputs in the create script:
IF EXISTS ( SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'myproc')
AND type IN ( N'P', N'PC' ) )
DROP …
CREATE …
Update:
Example of how to do it when including the schema:
IF EXISTS ( SELECT *
FROM sysobjects
WHERE id = object_id(N'[dbo].[MyProc]')
and OBJECTPROPERTY(id, N'IsProcedure') = 1 )
BEGIN
DROP PROCEDURE [dbo].[MyProc]
END
In the example above, dbo is the schema.
Update:
In SQL Server 2016+, you can just do
CREATE OR ALTER PROCEDURE dbo.MyProc
If you're looking for the simplest way to check for a database object's existence before removing it, here's one way (example uses a SPROC, just like your example above but could be modified for tables, indexes, etc...):
IF (OBJECT_ID('MyProcedure') IS NOT NULL)
DROP PROCEDURE MyProcedure
GO
This is quick and elegant, but you need to make sure you have unique object names across all object types since it does not take that into account.
I know you want to "ALTER a procedure if it exists and create it if it does not exist", but I believe it is simpler to:
Drop the procedure (if it already exists) and then
Re-create it.
Like this:
IF OBJECT_ID('MyProcedure', 'P') IS NOT NULL
DROP PROCEDURE MyProcedure
GO
CREATE PROCEDURE MyProcedure AS
BEGIN
/* ..... */
END
GO
The second parameter tells OBJECT_ID to only look for objects with object_type = 'P', which are stored procedures:
AF = Aggregate function (CLR)
C = CHECK constraint
D = DEFAULT (constraint or stand-alone)
F = FOREIGN KEY constraint
FN = SQL scalar function
FS = Assembly (CLR) scalar-function
FT = Assembly (CLR) table-valued function
IF = SQL inline table-valued function
IT = Internal table
P = SQL Stored Procedure
PC = Assembly (CLR) stored-procedure
PG = Plan guide
PK = PRIMARY KEY constraint
R = Rule (old-style, stand-alone)
RF = Replication-filter-procedure
S = System base table
SN = Synonym
SO = Sequence object
TF = SQL table-valued-function
TR = Trigger
You can get the full list of options via:
SELECT name
FROM master..spt_values
WHERE type = 'O9T'
As of SQL SERVER 2016 you can use the new DROP PROCEDURE IF EXISTS.
DROP { PROC | PROCEDURE } [ IF EXISTS ] { [ schema_name. ] procedure } [ ,...n ]
Reference :
https://msdn.microsoft.com/en-us/library/ms174969.aspx
I know it is a very old post, but since this appears in the top search results hence adding the latest update for those using SQL Server 2016 SP1 -
create or alter procedure procTest
as
begin
print (1)
end;
go
This creates a Stored Procedure if does not already exist, but alters it if exists.
Reference
DROP IF EXISTS
is a new feature of SQL Server 2016
https://blogs.msdn.microsoft.com/sqlserverstorageengine/2015/11/03/drop-if-exists-new-thing-in-sql-server-2016/
DROP PROCEDURE IF EXISTS dbo.[procname]
I had the same error. I know this thread is pretty much dead already but I want to set another option besides "anonymous procedure".
I solved it like this:
Check if the stored procedure exist:
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='my_procedure') BEGIN
print 'exists' -- or watever you want
END ELSE BEGIN
print 'doesn''texists' -- or watever you want
END
However the "CREATE/ALTER PROCEDURE' must be the first statement in a query batch" is still there. I solved it like this:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE -- view procedure function or anything you want ...
I end up with this code:
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID('my_procedure'))
BEGIN
DROP PROCEDURE my_procedure
END
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].my_procedure ...
Here's a method and some reasoning behind using it this way. It isn't as pretty to edit the stored proc but there are pros and cons...
UPDATE: You can also wrap this entire call in a TRANSACTION. Including many stored procedures in a single transaction which can all commit or all rollback. Another advantage of wrapping in a transaction is the stored procedure always exists for other SQL connections as long as they do not use the READ UNCOMMITTED transaction isolation level!
1) To avoid alters just as a process decision. Our processes are to always IF EXISTS DROP THEN CREATE. If you do the same pattern of assuming the new PROC is the desired proc, catering for alters is a bit harder because you would have an IF EXISTS ALTER ELSE CREATE.
2) You have to put CREATE/ALTER as the first call in a batch so you can't wrap a sequence of procedure updates in a transaction outside dynamic SQL. Basically if you want to run a whole stack of procedure updates or roll them all back without restoring a DB backup, this is a way to do everything in a single batch.
IF NOT EXISTS (select ss.name as SchemaName, sp.name as StoredProc
from sys.procedures sp
join sys.schemas ss on sp.schema_id = ss.schema_id
where ss.name = 'dbo' and sp.name = 'MyStoredProc')
BEGIN
DECLARE #sql NVARCHAR(MAX)
-- Not so aesthetically pleasing part. The actual proc definition is stored
-- in our variable and then executed.
SELECT #sql = 'CREATE PROCEDURE [dbo].[MyStoredProc]
(
#MyParam int
)
AS
SELECT #MyParam'
EXEC sp_executesql #sql
END
In Sql server 2008 onwards, you can use "INFORMATION_SCHEMA.ROUTINES"
IF EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'MySP'
AND ROUTINE_TYPE = 'PROCEDURE')
**The simplest way to drop and recreate a stored proc in T-Sql is **
Use DatabaseName
go
If Object_Id('schema.storedprocname') is not null
begin
drop procedure schema.storedprocname
end
go
create procedure schema.storedprocname
as
begin
end
Here is the script that I use. With it, I avoid unnecessarily dropping and recreating the stored procs.
IF NOT EXISTS (
SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[uspMyProcedure]')
)
BEGIN
EXEC sp_executesql N'CREATE PROCEDURE [dbo].[uspMyProcedure] AS select 1'
END
GO
ALTER PROCEDURE [dbo].[uspMyProcedure]
#variable1 INTEGER
AS
BEGIN
-- Stored procedure logic
END
why don't you go the simple way like
IF EXISTS(SELECT * FROM sys.procedures WHERE NAME LIKE 'uspBlackListGetAll')
BEGIN
DROP PROCEDURE uspBlackListGetAll
END
GO
CREATE Procedure uspBlackListGetAll
..........
In addition to the answer from #Geoff I've created a simple tool which generates a SQL-file which statements for Stored Procedures, Views, Functions and Triggers.
See MyDbUtils # CodePlex.
I wonder! Why i don't write the whole query like
GO
create procedure [dbo].[spAddNewClass] #ClassName varchar(20),#ClassFee int
as
begin
insert into tblClass values (#ClassName,#ClassFee)
end
GO
create procedure [dbo].[spAddNewSection] #SectionName varchar(20),#ClassID int
as
begin
insert into tblSection values(#SectionName,#ClassID)
end
Go
create procedure test
as
begin
select * from tblstudent
end
i already know that first two procedures are already exist sql will run the query will give the error of first two procedures but still it will create the last procedure
SQl is itself taking care of what is already exist this is what i always do to all my clients!
CREATE Procedure IF NOT EXISTS 'Your proc-name' () BEGIN ... END