I have the following script that I am trying to run through a winforms program that just executes all the scripts in a directory:
USE [master]
GO
EXEC('ALTER DATABASE [AT100Reporting] MODIFY Name = [IngeniumDynamics];')
GO
However, when I try to run this I get the following error:
ALTER DATABASE statement not allowed within multi-statement transaction.
I googled this and the main thing I could find about it was that alter db statements should run in autocommit mode. A further search made me change my script to
USE [master]
GO
SET IMPLICIT_TRANSACTIONS OFF
EXEC('ALTER DATABASE [AT100Reporting] MODIFY Name = [IngeniumDynamics];')
GO
SET IMPLICIT_TRANSACTIONS ON
Yet I still have the same error. Does anyone know what I need to do to make this script run properly
I have also tried this without the EXEC
As Joachim Isaksson has pointed out, the problem was not with the script but with the winforms program that was running the scripts - I had my TransactionScope options set to TransactionScopeOption.Required but if I changed this to TransactionScopeOption.Suppress the query worked fine
You can use,
sp_renamedb OldDbName,NewDbName to rename the database :
exec ('sp_renamedb questionoverflow, stackoverflow')
Dynamic Way :
declare #OldName varchar(50)='questionoverflow'
declare #NewName varchar(50)='stackoverflow'
exec('sp_renamedb '+#OldName+','+#NewName)
Then It should work :
First Set an Single User Mode : and Rename it.
declare #OldName varchar(50)='stackoverflow'
declare #NewName varchar(50)='a'
EXEC('ALTER DATABASE '+#OLDNAME + ' SET SINGLE_USER')
exec('sp_renamedb '+#OldName+','+#NewName)
//And again put it in Multi-User Mode ::
EXEC('ALTER DATABASE '+#NewName+ ' SET MULTI_USER')
Related
I have multiple databases to change mode from Single-User to Multi-User.
I can use this to change one database.
USE master;
GO
ALTER DATABASE CDN_Ceramika
SET MULTI_USER;
GO
Is it possible to change mode for few bases or any query, when mode will be change for Multi-User just when a mode is Single-User?
If you are new to database management I'd advise against using sys.sp_MSforeachdb. Despite being undocumented it's a very popular function among DBAs that know and quadruple-check what they're doing. This article explains some of its pitfalls.
It's generally safer to generate the command strings you want to execute with SQL, inspect them, fix any problems and execute the resulting script. Probably inside a transaction if appropriate.
As the article shows sp_MSforeachdb itself queries the sys.databases table. You can do the same, eg with :
select 'ALTER DATABASE [' + name + '] SET MULTI_USER;'
from sys.databases
where name not in ('master','tempdb','model','msdb')
To generate ALTER DATABASE for all the databases you want, whose names match the WHERE clause. You could also write :
select 'ALTER DATABASE [' + name + '] SET MULTI_USER;'
from sys.databases
where name not in ('master','tempdb','model','msdb')
and [user_access_desc] != 'MULTI_USER'
to exclude databases that are already in multi-user mode
You could use the undocumented system procedure sp_msforeachdb:
USE master;
EXEC sys.sp_MSforeachdb 'IF ''?'' NOT IN (''master'',''msdb'',''tempdb'',''model'') BEGIN
ALTER DATABASE ? SET MULTI_USER;
END;';
Right click on the database you wish to change, then click on Properties.
Click on Options and scroll down to the bottom, then set it as can be seen below:
Click OK and it will be set.
I want to prevent SQL injection. It's a guild notice smp from a online game. The attacker used "_)$*%RDELETE FROM Character WHERE sid >=1". He deleted all our characters. (Backup ftw) But its really annoying to restore the backups again and again. And don't forget the little roll back with this. The smp seems fine to me. Yeah I'm a beginner so shame on me :*. Thank you for help.
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[smp_set_guild_notice]
#IN_GUILD_SID INT,
#IN_NOTICE NVARCHAR(128)
AS
SET NOCOUNT ON
IF (LEFT(#IN_NOTICE, 6) = '_)$*%R')
BEGIN
SET #IN_NOTICE = REPLACE(#IN_NOTICE,'_)$*%R','')
EXEC sp_executesql #IN_NOTICE;
END
ELSE
BEGIN
UPDATE dbo.Guild
SET notice = #IN_NOTICE
WHERE sid = #IN_GUILD_SID
END
I would think the answer is obvious to anyone who understands SQL injection: don't EXEC untrusted input as an SQL statement!
I have no idea what the purpose of your _)$*%R prefix is. It appears to be a kind of "back-door" to allow a verbatim SQL statement to be executed? Why would you do this? Is it some kind of security-by-obscurity measure? The attacker was probably able to find out the prefix by looking at your web page source in their browser.
If this procedure is meant to update the guild notice, then just do the second UPDATE and remove the back-door code.
ALTER PROCEDURE [dbo].[smp_set_guild_notice]
#IN_GUILD_SID INT,
#IN_NOTICE NVARCHAR(128)
AS
SET NOCOUNT ON
UPDATE dbo.Guild SET notice = #IN_NOTICE WHERE sid = #IN_GUILD_SID
I'm not a Microsoft SQL Server developer, so that syntax might not be correct. I'm just showing what I mean by removing the part of the code that does the EXEC.
No SQL injection is possible when you hard-code your SQL, and use inputs only has value parameters.
Don't use EXEC if you can't sanitize input.
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)
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...
As part of my integration strategy, I have a few SQL scripts that run in order to update the database. The first thing all of these scripts do is check to see if they need to run, e.g.:
if #version <> #expects
begin
declare #error varchar(100);
set #error = 'Invalid version. Your version is ' + convert(varchar, #version) + '. This script expects version ' + convert(varchar, #expects) + '.';
raiserror(#error, 10, 1);
end
else
begin
...sql statements here...
end
Works great! Except if I need to add a stored procedure. The "create proc" command must be the only command in a batch of sql commands. Putting a "create proc" in my IF statement causes this error:
'CREATE/ALTER PROCEDURE' must be the first statement in a query batch.
Ouch! How do I put the CREATE PROC command in my script, and have it only execute if it needs to?
Here's what I came up with:
Wrap it in an EXEC(), like so:
if #version <> #expects
begin
...snip...
end
else
begin
exec('CREATE PROC MyProc AS SELECT ''Victory!''');
end
Works like a charm!
SET NOEXEC ON is good way to switch off some part of code
IF NOT EXISTS (SELECT * FROM sys.assemblies WHERE name = 'SQL_CLR_Functions')
SET NOEXEC ON
GO
CREATE FUNCTION dbo.CLR_CharList_Split(#list nvarchar(MAX), #delim nchar(1) = N',')
RETURNS TABLE (str nvarchar(4000)) AS EXTERNAL NAME SQL_CLR_Functions.[Granite.SQL.CLR.Functions].CLR_CharList_Split
GO
SET NOEXEC OFF
Found here:
https://codereview.stackexchange.com/questions/10490/conditional-create-must-be-the-only-statement-in-the-batch
P.S. Another way is SET PARSEONLY { ON | OFF }.
But watch out for single quotes within your Stored Procedure - they need to be "escaped" by adding a second one. The first answer has done this, but just in case you missed it. A trap for young players.
Versioning your database is the way to go, but... Why conditionally create stored procedures. For Views, stored procedures, functions, just conditionally drop them and re-create them every time. If you conditionally create, then you will not clean-up databases that have a problem or a hack that got put in 2 years ago by another developer (you or I would never do this) who was sure he would remember to remove the one time emergency update.
Problem with dropping and creating is you lose any security grants that had previously been applied to the object being dropped.
This is an old thread, but Jobo is incorrect: Create Procedure must be the first statement in a batch. Therefore, you can't use Exists to test for existence and then use either Create or Alter. Pity.
It is much better to alter an existing stored proc because of the potential for properties and permissions that have been added AND which will be lost if the stored proc is dropped.
So, test to see if it NOT EXISTS, if it does not then create a dummy proc. Then after that use an alter statement.
IF NOT EXISTS(SELECT * FROM sysobjects WHERE Name = 'YOUR_STORED_PROC_NAME' AND xtype='P')
EXECUTE('CREATE PROC [dbo].[YOUR_STORED_PROC_NAME] as BEGIN select 0 END')
GO
ALTER PROC [dbo].[YOUR_STORED_PROC_NAME]
....
I must admit, I would normally agree with #Peter - I conditionally drop and then unconditionally recreate every time. I've been caught out too many times in the past when trying to second-guess the schema differences between databases, with or without any form of version control.
Having said that, your own suggestion #Josh is pretty cool. Certainly interesting. :-)
My solution is to check if the proc exists, if so then drop it, and then create the proc (same answer as #robsoft but with an example...)
IF EXISTS(SELECT * FROM sysobjects WHERE Name = 'PROC_NAME' AND xtype='P')
BEGIN
DROP PROCEDURE PROC_NAME
END
GO
CREATE PROCEDURE PROC_NAME
#value int
AS
BEGIN
UPDATE SomeTable
SET SomeColumn = 1
WHERE Value = #value
END
GO
use the 'Exists' command in T-SQL to see if the stored proc exists. If it does, use 'Alter', else use 'Create'
IF NOT EXISTS(SELECT * FROM sys.procedures WHERE name = 'pr_MyStoredProc')
BEGIN
CREATE PROCEDURE pr_MyStoredProc AS .....
SET NOCOUNT ON
END
ALTER PROC pr_MyStoredProc
AS
SELECT * FROM tb_MyTable