Use database inside a stored procedure - sql-server

I need to make a stored procedure which creates a user in more than one database. Something like this:
USE [database1]
CREATE USER [userLogin] FOR LOGIN [userLogin]
USE [database2]
CREATE USER [userLogin] FOR LOGIN [userLogin]
Since the CREATE USER statement does his job in the current database I need to use the USE statement to change between databases, but it can't be used inside stored procedures.
How can I do this?

Dynamic SQL
CREATE PROCEDURE spTestProc
AS
EXEC ('USE [database1]; CREATE USER [userLogin] FOR LOGIN [userLogin]')
EXEC ('USE [database2]; CREATE USER [userLogin] FOR LOGIN [userLogin]')
GO

SQL Server gives us a system stored procedure to do this. My understanding is that the recommended method would be to use sys.sp_grantdbaccess:
CREATE PROCEDURE usp_CreateTwoUSers
AS
BEGIN
-- Create a user for a login in the current DB:
Exec sp_grantdbaccess [userLogin], [name_in_db];
-- Create a user for a login in an external DB:
Exec ExternalDatabaseName.sys.sp_grantdbaccess [userLogin], [name_in_db];
END

I did it like below:
Alter Procedure testProc
#dbName varchar(50)
As
declare #var varchar(100)
set #var = 'Exec(''create table tableName(name varchar(50))'')'
Exec('Use '+ #dbName + ';' + #var)
Exec testProc 'test_db'

CREATE PROCEDURE spTestProc
AS
BEGIN
EXECUTE sp_executesql N'USE DB1 SELECT * FROM TABLE1'
EXECUTE sp_executesql N'USE DB2 SELECT * FROM Table2'
END
exec spTestProc
now it is worked.

If you're writing dynamic SQL with EXEC sp_executesql ('query1') or EXEC ('query2') this will return correct db which you want. If you're writing static SQL or your query outside of dynamic SQL quotes or parantheses it will work on master (where you create stored procedure(default is master)).
CREATE PROCEDURE master.dbo.mysp1
AS
EXEC ('USE model; SELECT DB_NAME()') -- or sp_executesql N'USE model; SELECT DB_NAME()'
--this returns 'model'
GO
CREATE PROCEDURE master.dbo.mysp2
AS
EXEC ('USE model;') -- or sp_executesql N'USE model;'
SELECT DB_NAME()
-- this returns 'master'
GO

It should be noted that if you want to use single quotes within a EXEC command, you will need to double the amount of single quotes
e.g.
EXEC ('USE [database1]; select * from Authors where name = ''John'' ')
In this example, John has 2 single quotes before and after it.
You cannot use double quotes for this type of query.

Using sp_executesql seems to work, for more info see http://msdn.microsoft.com/en-us/library/ms175170.aspx
I tested it using this and it worked fine:
CREATE PROCEDURE spTestProc
AS
BEGIN
EXECUTE sp_executesql N'USE DB1;'
SELECT * FROM TABLE1
EXECUTE sp_executesql N'USE DB2;'
SELECT * FROM Table2
END
exec spTestProc

Related

Create database from a template by a script

I'm running a robot test on one of our test environments, and every night, before the test runs, I would like to dump the database, and create it again from a template.
I know that PSQL has a solution for it:
CREATE DATABASE name
[ TEMPLATE [=] template ]
Is there a way I can do this in SQL Server, and write a script for it?
You can write any kind of functionality as text and then execute it as a variable. That way you can store your template as text, where you would define the database.
declare #example nvarchar(max)
set #example = 'create database examplebase'
exec sp_executesql #example
You can use this in a stored procedure where you can set database name ect. as a parameter.
create PROCEDURE sp_recreateDB
-- Add the parameters for the stored procedure here
#databasename nvarchar(max)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
declare #example nvarchar(max)
SET #example = N'CREATE DATABASE ' + QUOTENAME(#Databasename) + N';'
-- ect...
exec sp_executesql #example
END
GO

sp_addrolemember with WHERE clause - SQL Server

Is there a way to add a WHERE clause within the sp_addrolemember script so that I don't have to create the stored procedure in every single database?
For example in my stored procedure:
BEGIN
DECLARE #SQL NVARCHAR(MAX)
DECLARE #GrantSql INT
EXEC #GrantSql = sp_addrolemember 'db_owner', #LoginName WHERE DatabaseName = 'DBName'
IF #GrantSQL = 0
BEGIN
INSERT INTO TableName....
END
END
In documentation:
Adds a database user, database role, Windows login, or Windows group
to a database role in the CURRENT database
But you can try:
EXEC #GrantSql = DBName..sp_addrolemember 'db_owner', #LoginName
If DBName is a parameter, you should use parametrized dynamic sql
DECLARE #Sql NVARCHAR(MAX) =
N'EXEC #GrantSql = ' + QUOTENAME(#DBName) + '..sp_addrolemember #RoleName, #LoginName'
EXEC sp_executesql #Sql, N'#RoleName NVARCHAR(MAX), #LoginName NVARCHAR(255)', #RoleName, #LoginName
P.S.
Here is a good article about dynamic sql:
http://www.sommarskog.se/dynamic_sql.html
I think everyone who are about to write dynamic sql MUST read it

SQL Server / Create view from stored procedure

I am trying to create a view out of a stored procedure and am perplexed to see two opposing results from a very similar approach.
Example 1
CREATE PROCEDURE cv AS
GO
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
Whereas this example creates the view once the procedure is created for the 1st time, it will not recreate the view when I execute the procedure at a later stage using:
EXEC cv
Example 2
CREATE PROCEDURE cv
#table SYSNAME
AS
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW '+ #table +' AS SELECT * FROM someOtherTable'
This one instead does not create the view when the procedure is created for the first time but creates the view afterwards every time it is called by:
EXEC #sql;
Why is this the case? I think this is really confusing and does not make sense or does it?
For your 1st statement
CREATE PROCEDURE cv AS
GO --<-- This GO here terminates the batch
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
the GO batch terminator create the procedure and the EXECUTES the following statement straightaway. So it appears to you as you have created a procedure which created the view for you.
Infact these are two statements in two batches.
--BATCH 1
CREATE PROCEDURE cv AS
GO
--BATCH 2
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
Batch 1 Creates a procedure which has nothing inside it, but it creates a procedure object for you with no functionality/Definition at all.
Statement after the key word GO is executed separately and creates the view for you.
My Suggestion
Always check for an object's existence before you create it. I would write the procedure something like this..
CREATE PROCEDURE cv
AS
BEGIN
SET NOCOUNT ON;
IF OBJECT_ID ('test', 'V') IS NOT NULL
BEGIN
DROP VIEW test
END
DECLARE #sql nvarchar(MAX)
SET #sql = 'CREATE VIEW test AS SELECT * FROM someOtherTable'
exec (#sql)
END
In Example 1 - you are creating a view with a hard-coded name of test. On subsequent runs of your proc, as the view already exists, SQL Server will throw an error because you are trying to create a view with the same name as one that already exists.
In Example 2, you are passing in the name of the view as a parameter. This will always create a new view unless the #table value you pass in corresponds to an existing view.
Just wondering - why are you creating a view with a stored proc? This is not something you would normally do in SQL.
You need to change
EXEC #sql to EXEC (sql)
EXEC can be use to run stored procedure, not dynamic sql
eg.
declare #dd varchar(100)='Select ''A'''
exec (#dd) ->will work fine
exec #dd -> Error
Read more about The Curse and Blessings of Dynamic SQL
Create view statement cannot be used with a stored procedure
EXEC ('CREATE VIEW ViewName AS SELECT * FROM OPENQUERY(Yourservername,''EXECUTE [databasename].[dbo].[sp_name] ''''' +Parameter + ''''''')')

Use Stored Procedure To Create View

I want to create a stored procedure which I can pass a parameter to for the database name and it will create a view for me.
I am just trying to save some time by not writing the same statement over and over and over for a create vew.
Below is my syntax - how could this be modified to run in a stored procedure accepting a parameter?
Alter View dbo.ForceClose
As
SELECT DISTINCT(SessionID) As CountofSessionID
FROM Database1
WHERE forceClosed IS NOT NULL
AND stillOpen IS NULL
and (userName is not null or userName IN ('JJones', 'MHill', 'RMort'))
Go
And in the stored procedure accept a parameter as the database name (I know this isn't valid just trying to show an example) -- call the stored procedure like so
exec dbo.Procedure 'DBName'
And the stored procedure would then look like
#DBName varchar(100)
Select blah blah FROM' + #DBName + '
You mean this?
CREATE PROCEDURE dbo.ForceClose
(
#DBNAME NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #SQL AS NVARCHAR(255)
#SQL='SELECT DISTINCT(SessionID) As CountofSessionID
FROM ['+#DBNAME+'].[SCHEMA].[TABLE_NAME]
WHERE forceClosed IS NOT NULL
AND stillOpen IS NULL
and (userName is not null or userName IN (''JJones'', ''MHill'', ''RMort''))'
EXEC (#SQL)
END
And you can run it by:
EXEC ForceClose <DBNAME>
With the View:
CREATE PROCEDURE dbo.ForceClose
(
#DBNAME NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #SQL AS NVARCHAR(255)
#SQL='CREATE VIEW VIEW_'+DBNAME+' AS SELECT DISTINCT(SessionID) As CountofSessionID
FROM ['+#DBNAME+'].[SCHEMA].[TABLE_NAME]
WHERE forceClosed IS NOT NULL
AND stillOpen IS NULL
and (userName is not null or userName IN (''JJones'', ''MHill'', ''RMort''))'
EXEC (#SQL)
END
And you can run it by:
SELECT * FROM VIEW_<DBNAME>
Im not sure what the create view is doing; the basic premise you want is dynamic SQL (and there are lots of good questions and answers on the subject. A really simple answer would be
declare #myparameter nvarchar(100)= 'master'
declare #myquery nvarchar(1000)
select #myquery = 'select * from ' + #myparameter + '.dbo.sysdatabases'
select #myquery
exec sp_executeSQL #myquery
Running this returns a list of the databases on your server.
If you want to create a view; would you not need to know the table to query? The basic technique is the same
declare #myparameter nvarchar(100)= 'master'
declare #myquery nvarchar(1000)
select #myquery = 'Create View myschema.vw' + #myparameter + ' as select * from ' + #myparameter + '.dbo.sysdatabases'
select #myquery
exec sp_executeSQL #myquery
If this is in an application the user permissions to do this would be very high for a general application; you might want to wrap it with an execute as permission to stop people doing too much damage.

sys.database_principals is not executed in current database if called from sp procedure (stored in master)

I would like to print out database users of an actual database in a SP procedure (see the code of sp_PrintUsers below), however, for some reason it print out database users of master. It seems that it is a general behavior of SP procedure for all database-level views despite the fact that any database-level SQL statement is executed in the actual database. If we print out the DB_NAME that it is clearly not master, so what is wrong?
Is there any workaround?
use [master]
go
create procedure sp_PrintUsers
as
begin
SELECT DB_NAME() AS DataBaseName
select name from sys.database_principals;
end
go
use [actual_database]
go
exec sp_PrintUsers
Try executing the select dynamically as in:
EXEC('select name from sys.database_principals;');
If that does not help build the query to reference the catalog view with a three part name.
Try this :
use [master]
go
create procedure sp_PrintUsers
as
begin
declare #dbname varchar(30) = DB_NAME()
EXEC ('select name from ' + #dbname + '.sys.database_principals');
end
go
use [actual_database]
go
exec sp_PrintUsers

Resources