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...
Related
I need to pass linked server name as variable to stored procedure right now after testing and research they all suggest to using dynamic sql and open query which I am using now. however I am not comfortable using it(sql injection) plus I need to call other user defined function to the query. I am looking for a more secure and direct call. Here is my SP
ALTER PROCEDURE [dbo].[GetBackUpStatus]
-- Add the parameters for the stored procedure here
#linkedServerName AS VARCHAR(100),
#exemptDB as VARCHAR(100)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
DECLARE #Sql varchar(8000)
SET NOCOUNT ON;
set #Sql = 'select * from openquery (' + #linkedServerName + ' , ''SELECT SERVERPROPERTY(''''SERVERNAME'''') AS "SERVERNAME",
T1.NAME AS DatabaseName,
MAX(T2.backup_finish_date) AS last_db_backup_date,
CAST(COALESCE(DATEDIFF(second, MAX(T2.backup_finish_date) , CURRENT_TIMESTAMP)/ 3600.0, 9999.0) as NUMERIC(6,2)) AS [Hours Since Backup]
FROM master.sys.databases T1
LEFT OUTER JOIN msdb.dbo.backupset T2 ON T2.database_name = T1.NAME
WHERE T1.NAME NOT IN (''''tempdb'''')
GROUP BY T1.NAME
ORDER BY T1.NAME'')'
Exec (#Sql)
END
the purpose of this query is to get the server status and its database, I don't like this because of that confusing single quotes, this query will eventually grow as I develop and add function calls.
I tried this and something like this is what I wanted, since it is direct query and cleaner without those quotes. That's how I typically use linked server.
Select * from [' + #linkedservername + '].[schema].table
thanks
Solution for a large scale data model with hundreds of tables / objects.
Dynamic modification and cloning of a stored procedure for every linked-server.
It is kinda hiding any dynamic SQL under the hood.
How to
Create a stored procedure which interacts with an existing linked-server.
During a database deployment process:
Obtain the source code of the stored procedure.
Replace the name of the linked-server in the code.
If you want to create a new stored procedure (cloned), replace the name of the initial stored procedure in the code.
Create a cloned stored procedure or modify the current.
Repeat all steps for each required linked-server.
There are another variations for it.
Now, any external logic may decide which procedure to use.
You can check the existence of a linked-server or its related stored procedure.
For modifications and cloning, it is possible to use SQL Server or external tools, such as C#, CMD, etc.
For creation under SQL Server.
For decades i've used VIEWs as a synonym:
CREATE VIEW dbo.Banks AS
SELECT *
FROM OtherDatabase.dbo.Banks
i do this so i can abstract where the "real" table is. And when it changes, it's as simple as altering the view:
And this works well. It's doesn't cause the optimizer any issues, and i have been able to edit the view as required.
Synonyms
Starting with SQL Server 2005, Microsoft introduced synonyms:
CREATE SYNONYM dbo.Banks FOR OtherDatabase.dbo.Banks
It seems to work identically to the VIEW approach. Every execution plan i've looked at behaves identically.
Unfortunately it seems that synonyms are unable to provide one of their basic functions, functionality i need:
Provides a layer of abstraction that protects a client application from changes made to the name or location of the base object
You are not able to change where a synonym points. Because there is no ALTER SYNONYM statement, you first have to drop the synonym and then re-create the synonym with the same name, but point the synonym to the new location.
Do they have any redeeming quality?
Practically speaking, this isn't going to happen. i will just never do it. i won't use a mechanism that requires me to drop objects from a database in order to change a setting. i'm certainly not going to delete all the easily alterable VIEWs, replacing them with SYNONYMs, and have to explain to everyone why making everything harder is "better".
So my question is, is there anything i am losing by using views?
every execution plan looks identical to synonyms
i can easily change the "view synonym" at any time
Is there a virtue to a table or view synonym that i'm missing?
Aside from having to call RefreshAllViews in case i forgot that i made a table change somewhere
Even stored procedures
i don't even use synonyms for stored procedures:
CREATE PROCEDURE dbo.GetUSDNoonRateAsOf #tradeDate datetime AS
EXECUTE OtherDatabase.dbo.GetUSDNoonRateAsOf #tradeDate
Is there a value in synonyms that i am missing?
Update: RefreshAllViews procedure
We have a standard procedure in every database. Reordering, or inserting, columns wreaks havoc on views; so they have to be "refreshed".
CREATE PROCEDURE [dbo].[RefreshAllViews] AS
-- This sp will refresh all views in the catalog.
-- It enumerates all views, and runs sp_refreshview for each of them
SET NOCOUNT ON
DECLARE abc CURSOR FOR
SELECT TABLE_NAME AS ViewName
FROM INFORMATION_SCHEMA.VIEWS
ORDER BY newid()
OPEN abc
DECLARE #ViewName varchar(128)
--DECLARE #ParmDefinition NVARCHAR(500)
-- Build select string once
DECLARE #SQLString nvarchar(2048)
--SET #SQLString = N'EXECUTE sp_RefreshView #View'
--SET #ParmDefinition = N'#View nvarchar(128)'
FETCH NEXT FROM abc
INTO #ViewName
WHILE ##FETCH_STATUS = 0
BEGIN
IF #ViewName <> 'IndexServerNodes'
BEGIN
SET #SQLString = 'EXECUTE sp_RefreshView '+#ViewName
PRINT #SQLString
EXECUTE sp_ExecuteSQL #SQLString--, #ParmDefinition, #View = #ViewName
END
FETCH NEXT FROM abc
INTO #ViewName
END
CLOSE abc
DEALLOCATE abc
God knows why SQL Server doesn't do it for me.
A synonym is a much more transparent redirect. I prefer them over views because views need to be maintained. When you use SELECT * especially.
I'm not sure I buy that the lack of ALTER SYNONYM is a real blocker. The drop/create of a synonym is a very simple metadata operation, and will be very fast. Omitting error handling for brevity:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;
DROP SYNONYM ...
CREATE SYNONYM ...
COMMIT TRANSACTION;
Similarly, for stored procedures, if your base stored procedure interface changes (say, you add a parameter), you have to also change the wrapper procedure - not so with a synonym.
One downside is that you can create, say, an instead of trigger on a view, but you can't on a synonym. There are other operations you can't perform via a synonym (mostly DDL). And of course IntelliSense may not function correctly, depending on version.
Not being able to memorize the syntax seems like a made-up excuse to me. There are no fancy options or with clauses; just a 2-part name for the synonym, and a 2-, 3- or 4-part name for the object it refers to:
CREATE SYNONYM dbo.Something FOR Server.Database.dbo.SomethingElse;
If you can't memorize that, how did you create the synonym in the first place?
I also have a suggestion to thoroughly simplify your stored procedure (and prevent it from failing when any view is not in the dbo schema, or the procedure is executed by someone whose default schema is not the same as the view's schema, or the view has an ' or space in its name, or otherwise breaks any of the rules for identifiers (you can find them on this page)):
CREATE PROCEDURE [dbo].[RefreshAllViews]
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += '
EXEC sp_refreshview ' + CHAR(39)
+ QUOTENAME(REPLACE(s.name,'''',''''''))
+ '.' + QUOTENAME(REPLACE(v.name,'''','''''')) + CHAR(39) + ';'
FROM sys.views AS v
INNER JOIN sys.schemas AS s
ON v.[schema_id] = s.[schema_id];
PRINT #sql;
EXEC sp_executesql #sql;
END
GO
At the very least, if you're going to keep the cursor, stop using the terrible default options (declare the cursor as LOCAL FAST_FORWARD), and use sys.views instead of INFORMATION_SCHEMA.
God knows why SQL Server doesn't do it for me.
Because SQL Server is software, and it isn't perfect - especially when it comes to dependencies. The main problem is that you are violating a best practice by using SELECT * in your views in the first place. shrug If you would accept your hang-ups about synonyms, you won't have to worry about that.
If a view references a table, and you subsequently add columns to that table, you must modify the view in order to “pick up” the new column—even if you use SELECT *. Synonyms will “pick up” those columns automatically. Here’s a sample script:
-- Set things up
CREATE TABLE Foo
(
Id int not null
,data varchar(10) not null
)
GO
INSERT Foo values (1,'one'),(2,'Two')
GO
CREATE SYNONYM synFoo for Foo
GO
CREATE VIEW vFooDelim as select Id, Data from Foo
GO
CREATE VIEW vFooStar as select * from Foo
GO
select * from Foo
select * from synFoo
select * from vFooDelim
select * from vFooStar
then,
-- Add a column
ALTER TABLE Foo
add MoreData datetime default getdate()
GO
select * from Foo
select * from synFoo
select * from vFooDelim
select * from vFooStar
GO
(don’t forget to)
-- Clean things up
DROP Synonym synFoo
DROP VIEW vFooDelim
DROP VIEW vFooStar
DROP TABLE Foo
A significantly more obscure situation (that we do all the time here), if you have to set up a reference in a database to an object in another database, you don’t necessarily know what columns are in that table (dynamic denormalized) are or will be, and you don’t know the name of the database at the time you write your code (one database per client, but only once they sign the contract) (usually), using synonyms can be a godsend. At the time of database creation, just dynamicaly build and run CREATE SYNONYM myTable FOR <DatabaseName>.<schema>.MyTable, and you are done—no matter what columns get added for which client in the future.
Synonyms are useful for situations where you're working with lots of disparate data sources/multiple databases etc, or doing data migrations.
I've never really found cause to use them in new, greenfield developments.
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.
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.
I am having the stored procedure. For that i need to pass the Database name as the paramters from another application or another SP. I know the approach of dynamic SQL, something like,
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='select * from '+#dbname+'.dbo.table'
end
exec mysp 'mydb'
But i dont want the SQL statements as a string. Because in my SP, i have many Sql statements are coming (Not like this only SELECT statement). so can i use,
USE DatabaseName
inside the stored procedure, so that i can use the db name in the sql statements directly without making it as string. Or any other approach is there.
My requirements, only for db name, i dont want the entire the sql statement to be dynamic...
please help me out.
Thanks in advance.
You can add the USE instruction to the dynamic query you are creating. Then you can work with that database's tables and other objects without the qualifier (within the dynamic query):
Create procedure mysp(#dbname varchar(20))
as
begin
declare #sql varchar(max)
set #sql='use '+#dbname;
set #sql=#sql + ';select ... from dbo.table1';
set #sql=#sql + ';update dbo.table2...';
set #sql=#sql + ';insert into dbo.table3...';
...
exec(#sql);
end
exec mysp 'mydb'
However, while you can do that, it's not something that you should do, unless you really have to. You are probably trying to avoid creating the same procedure in different DBs, but you may be getting you other problems with this approach, or robbing yourself of some advantages you might otherwise have without resorting to dynamic queries in SPs.
No, USE isn't allowed in stored procedures, functions and triggers.
A stored procedure is supposed to be local to the database. To access another database, there is one way (as far as I know), and it's the one you used.