How to have database name as variable in an SP? - sql-server

We use stored procedures exclusively here, and that raises a little problem. We cross reference two different databases like dbdev..table1 in dev, dbqa..table1 in qa, and dbprod..table1 in production.
So every time we deploy to a different environment, we have to search and replace from dbdev to dbqa or dbprod.
Is there a way to use synonym or whatever sql server mechanics to solve problem?

Use sqlcmd variables, which are supported by sqlcmd deployment of .sql provisioning scripts,a s well as by VSDB projects. So your provisioning script looks like:
create procedure usp_myProc
as
select .. from [$(crossdb)]..table1;
go
When deploying it in production you run sqlcmd /E /I provisoning.sql /v crossdb=dbprod, while the QA deployment will be done via sqlcmd /E /I provisioning.sql /v crossdb=dbqa. See Using sqlcmd with Scripting Variables.
As a side note, I am working on a project that allows sqlcmd variables to be used from .Net SqlClient (SqlConnection, SqlCommand): the dbutilsqlcmd project.

SQL Server 2005 supports synonyms, so you can create synonym1 to refer to dbdev..table1 in dev environment, and to dbprod..table1 in prod environment. Your SP's (and probably views) just operate on the synonyms.
Update:
The easiest way to create synonyms:
exec sys.sp_MSforeachtable
'print ''CREATE SYNONYM '' + REPLACE(''?'', ''].['', ''].[syn_'') +
'' FOR [my_database].?
GO'''
(there is a line break before GO)
Run and paste result into new query window.

No.
It is not possible to create a Synonym for a database. That is a popular request though.
Is it really necessary to rename your databases for dbdev, dbqa, dbprod etc. though?

Dynamic sql
(forgive potential typos, but the concept is there)
Declare #dbname nvarchar(255), #sql nvarchar(max)
set #dbname = 'db1'
Set #sql = 'Select * From ' + #dbname + '.dbo.table1'
exec sp_executesql #sql

You can have the database name as a parameter of your stored procedure, then use Dynamic SQL to construct your queries.
Ex:
CREATE PROC MyStoredProcedure #DBName VARCHAR(50)
AS
DECLARE #SQL VARCHAR(MAX)
SET #SQL = 'SELECT * FROM ' + #DBName + '.dbo.table1'
EXEC sp_executesql #SQL
Then you would simply call your stored procedure with the appropriate DB Name:
EXEC MyStoredProcedure 'dbdev'

Related

How to get database name automatically into script to use in stored procedure execution?

I need to TRIM databases as per requirement. So, I'm using below script and giving database names manually. All I need is to automate the script to get database names automatically. Can anyone please suggest how to get the database name automatically.
Use [Sales_backup_2015_05_31_230001_7137975]
Exec [spMaint_TrimTestDB] 1
Go
for Eg:
instead of giving manually Sales_backup_2015_05_31_230001_7137975 I need to get db name automatically
Thanks.
There is a function DB_NAME() that would return the name of the current database if no parameters are passed. Check this.
I guess dynamic SQL might help you to run SP in different databases:
DECLARE #sql nvarchar(max)
SELECT #sql = (
SELECT N'Use '+QUOTENAME([name]) +' Exec [spMaint_TrimTestDB] 1;'
FROM sys.databases
WHERE database_id >= 5 AND [name] like 'Sales_backup%'
FOR XML PATH('')
)
EXEC sp_executesql #sql
This script will create and execute dynamic statement like:
Use [sales_backup_2015] Exec [spMaint_TrimTestDB] 1;
Use [sales_backup_2016] Exec [spMaint_TrimTestDB] 1;
etc...

Switching from one database to another within the same script

I would like to know how I can switch from one database to another within the same script. I have a script that reads the header information from a SQL Server .BAK file and loads the information into a test database. Once the information is in the temp table (Test database) I run the following script to get the database name.
This part works fine.
INSERT INTO #HeaderInfo EXEC('RESTORE HEADERONLY
FROM DISK = N''I:\TEST\database.bak''
WITH NOUNLOAD')
DECLARE #databasename varchar(128);
SET #databasename = (SELECT DatabaseName FROM #HeaderInfo);
The problem is when I try to run the following script nothing happens. The new database is never selected and the script is still on the test database.
EXEC ('USE '+ #databasename)
The goal is switch to the new database (USE NewDatabase) so that the other part of my script (DBCC CHECKDB) can run. This script checks the integrity of the database and saves the results to a temp table.
What am I doing wrong?
You can't expect a use statement to work in this fashion using dynamic SQL. Dynamic SQL is run in its own context, so as soon as it has executed, you're back to your original context. This means that you'd have to include your SQL statements in the same dynamic SQL execution, such as:
declare #db sysname = 'tempdb';
exec ('use ' + #db + '; dbcc checkdb;')
You can alternatively use fully qualified names for your DB objects and specify the database name in your dbcc command, even with a variable, as in:
declare #db sysname = 'tempdb';
dbcc checkdb (#db);
You can't do this because Exec scope is limited to dynamic query. When exec ends context is returned to original state. But context changes in Exec itself. So you should do your thing in one big dynamic statement like:
DECLARE #str NVARCHAR(MAX)
SET #str = 'select * from table1
USE DatabaseName
select * from table2'
EXEC (#str)

Multiple websites that uses same database structure

I have three websites which uses an abstract database structure with tables like: Items, Places, Categories, etc... and stored procedures like GetItemsByCategory, GetRelatedItems, etc... Actually im using exactly the same database structure for these 3 different websites.
From a code perspective im using the same code for all websites (except the HTML which is specific foreach one), and all the common code is in few projects used by all websites, so everytime that i detect a bug (which is in all websites) i just fix it on one place (the common part used by all) and automatically all websites get the fix.
Actually im using Asp.net MVC3 and Sql server.
Everytime i want to extend some funcionality, and i need a new table, stored procedure or something related with database, i have to do the modification in each database.
Do you know any approach that i could use to be able to have the same flexibility and do database modifications only one time for all websites?
Do you think I'm using a good approach or i should use something different in your opinion?
If the databases are on a single server, you could generate the script for the procedure from Management Studio, and make sure to use the option to "check for object existence" (Tools > Options > SQL Server Object Explorer > Scripting). This will yield something like this (most importantly it produces your stored procedure code as something you can execute using dynamic SQL):
USE DBName;
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF NOT EXISTS (...)
BEGIN
EXEC dbo.sp_executesql #statement = N'CREATE PROCEDURE dbo.whatever ...
'
END
GO
Now that you have this script, you can modify it to work across multiple databases - you just need to swipe the #statement = portion and re-use it. First you need to stuff the databases where you want this to work into a #table variable (or you can put this in a permanent table, if you want). Then you can build a command to execute in each database, e.g.
DECLARE #dbs TABLE (name SYSNAME);
INSERT #dbs(name) SELECT N'db1';
INSERT #dbs(name) SELECT N'db2';
INSERT #dbs(name) SELECT N'db3';
-- now here is where we re-use the create / alter procedure command from above:
DECLARE #statement NVARCHAR(MAX) = N'CREATE PROCEDURE dbo.whatever ...
';
-- now let's build some dynamic SQL and run it!
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + '
EXEC ' + QUOTENAME(name) + '.dbo.sp_executesql N''' + #statement + ''';'
FROM #dbs;
EXEC sys.sp_executesql #sql;
Alternatively, you could create a custom version of my sp_msforeachdb or sp_ineachdb replacements:
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
I used to use a tool called SQLFarms Combine for this, but the tool doesn't seem to exist anymore, or perhaps it has been swallowed up / re-branded by another company. Red Gate has since produced SQL Multi Script that has similar functionality.
If you added a column to all your tables called websiteId you could just have one database. Store the unique websiteId in each site's web.config and just pass it with each request for data. Obviously each site's data is stored with their websiteId so data can be queried per website.
It means a bit of refactoring in your db and any calls to your your db, but once done, you only have one database to maintain.
Of course this is assuming your databases are on the same server...

Sharing stored procedure across database using synonyms

I have two different SQL Server databases (on the same server - if it helps) that need to share the same stored procedure logic. The solution I'm trying to achieve looks like this:
Database1
Table: TestTable
Synonym: sp_MyProc pointing at SharedDatabase.dbo.sp_MyProc
Database2
Table: TestTable
Synonym: sp_MyProc pointing at SharedDatabase.dbo.sp_MyProc
SharedDatabase
Proc: sp_MyProc which runs queries against TestTable
My hope was to use the synonyms so that if I execute sp_MyProc while in the context of Database1, it would use Database2.TestTable. And if I execute sp_MyProc while in the context of Database2, it would go against Database2.TestTable. However, when I execute sp_MyProc through either of the synonyms, it ignores the context of the synonym and executes looking for a local copy of TestTable, which is not found.
Is there a way to implement a shared stored procedure that executes against different copies of tables in different databases, either through synonyms or some other mechanism?
Edit
I should mention that in my case I am looking to do this with a large set of existing tables and procs, so any solution that requires modifying the procs or tables themselves are not ideal.
Something like this would work for the definition of the procedure. Be sure to guard against SQL injection since this is built dynamically.
CREATE PROCEDURE [dbo].dosomething
#databaseName sysname,
#schema sysname,
#tableName sysname
as
declare #cmd as nvarchar(max)
set #cmd = N'select * from ' + quotename(#schema) + N'.' + quotename(#tableName)
exec sp_executesql #cmd
Then use it like this:
dosomething 'SampleDb', 'dbo', 'sampleTable'
If the stored proc is in the SharedDatabase, then it will always run in context of SharedDatabase. To accomplish what you are trying to do to centralize code, I would maybe pass in a parameter to designate which server it is coming from, so then you can execute the query against that specific TestTable. Basically, you will need to refer to each table using their fully qualified name - i.e. Database1.dbo.TestTable
USE SharedDatabase
CREATE PROCEDURE [dbo].sp_MyProc
#dbsource varchar(50)
as
if(#dbsource == 'DB1')
begin
select * from Database1.dbo.TestTable
end
else
begin
select * from Database2.dbo.TestTable
end
GO
The other alternative is to make a view in SharedDatabase, which will be called TestTableComposite, with an extra column to identify where the source data is. And then pass that in as the parameter, and your SP on SharedDatabase will always be in context of that DB.

Using one set of stored procedures for hundreds of identical databases

I inherited a project where each 'customer' has its own database. There are hundreds of databases. Stored procedures are currently not being used.
What are the best practices for consuming data here? Do I keep my stored procedures in the "master" database and use dynamic SQL to muck with data? It seems like there should be a much better way. I don't want to have a job running to push stored procedures around hundreds of DBs to keep all the stored procedures in sync.
This dynamic SQL is working, but I want a better way.
CREATE PROCEDURE [Users_SELECT]
#DataBase nvarchar(20),
#UserID uniqueidentifier
AS
BEGIN
DECLARE #sql nvarchar(max)
SET #sql = ''
SET #sql += 'SELECT * FROM ' + #DataBase + '.dbo.Users u '
SET #sql += 'WHERE u.UserID=#UserID '
EXEC sp_executesql #sql, N'#UserID uniqueidentifier', #UserID
END
I tried EXEC sp_executesql 'USE ' + #DataBase + '; GO' then running a SELECT but I couldn't get that working.
I don't see whats wrong with deploying the stored procedures to each and every database.
You have to deploy changes to the schema as well, so you hopefully have infrastructure in place to do it automatically. If not it is time build that infrastructure.
I figured it out. This will probably make SQL experts extremely mad, but I'm happy with the solution.
I created my stored procedures in 'master'. I know this is usually a no-no. I then used
USE master
EXEC sys.sp_MS_marksystemobject [sp_Users_SELECT]
GO
to mark each sproc as a system object. This way they can be called while under a different context. I can then do this:
USE DataBase200
EXEC sp_Users_SELECT
and it will run in the context of the DataBase200 db, without having to actually deploy the sproc to that database. It is working great so far. Remember that you have to prefix your stored procedures names with sp_ in order for them to be recognized as system objects.
I hope this helps someone.

Resources