In my script for restoring databases, I came to another problem. After restoring a database I want to edit some tables of it. So after the common restore query:
RESTORE DATABASE #DBname
FROM DISK = #BackupFileLocation
GO
I want to edit the mentioned tables. The problem is that I need to use some variables for editing the tables after the restore, that I declare before it (I need them there to). And it seems that GO kind of resets all variables so I can't use them afterwards. But if I try it without GO, it jumps over the restore query and says that the databases I want to edit, don't exist.
My Question: Is there another way to wait for the restore to finish until it continues editing the tables?
Here’s one way: set up a temp table (#table, not #variable), populate it with your values, then set your variables to these values. For example:
-- This will drop the temp table if it already exists.
-- Essential for repetitive testing!
IF object_id('tempdb.dbo.#Foo') is not null
DROP TABLE #Foo
GO
CREATE TABLE #Foo
(
DBName varchar(100) not null
,BackupFileLocation varchar(1000) not null
)
INSERT #Foo values ('MyDatabase', 'C:\SQL_Backups\SampleDBs')
DECLARE
#DBname varchar(100)
,#BackupFileLocation varchar(1000)
SELECT
#DBname = DBName
,#BackupFileLocation = BackupFileLocation
from #Foo
PRINT '-- Before restore --------------------'
PRINT #DBname
PRINT #BackupFileLocation
-- Note: I did not test this statement
RESTORE DATABASE #DBname
FROM DISK = #BackupFileLocation
GO
DECLARE
#DBname varchar(100)
,#BackupFileLocation varchar(1000)
SELECT
#DBname = DBName
,#BackupFileLocation = BackupFileLocation
from #Foo
PRINT ''
PRINT '-- After restore --------------------'
PRINT #DBname
PRINT #BackupFileLocation
GO
Related
I am using the stored procedure shown here for taking backups of databases from SQL Server.
It takes all backups for around 20 databases, now I want little change in the stored procedure.
It want below databases backups to pick through loop, how to modify existing stored procedure. Thanks
Emp_DB
Salary_DB
Company_DB
Attendance_DB
Code:
CREATE PROCEDURE Backupdbs
AS
BEGIN
DECLARE c1 CURSOR FOR
SELECT name FROM sys.databases WHERE database_id > 4
DECLARE #dbname varchar(100)
DECLARE #fname varchar(100)
OPEN C1
FETCH NEXT FROM c1 INTO #dbname
WHILE (##fetch_status = 0)
-- here, you're missing a
-- BEGIN
SET #fname = 'D:\Backup\'+#dbname+'.bak'
BACKUP DATABASE #dbname TO disk=#fname
FETCH NEXT FROM cl INTO #dbname
END
CLOSE c1
DEALLOCATE c1
END IF
Thanks
mg
A better scalable solution would be to have your own list of database names you want to back up, possibly with a groupId.
You can then pass a #groupId parameter and run a batch of database backups for the group or set of databases you choose - eg you can group large databases together, small together, frequently modified etc
So for example you could have the following table
create table BackupSets (
id int identity(1,1) primary key,
GroupId int,
[Name] sysname
)
and use it instead of sys.databases
select [Name] from BackupSets where groupId=#GroupId
You could also have a process as part of your backup to check for any names in sys.databases that don't exist in your list and insert them.
You could just add the databases in question to your where clause in your cursor query against sys.databases.
CREATE PROCEDURE Backupdbs
AS
BEGIN
DECLARE c1 CURSOR FOR
SELECT name FROM sys.databases WHERE database_id > 4
and name IN ('Emp_DB','Salary_DB','Company_DB','Attendance_DB')
DECLARE #dbname varchar(100)
DECLARE #fname varchar(100)
OPEN C1
FETCH NEXT FROM c1 INTO #dbname
WHILE (##fetch_status = 0)
-- here, you're missing a
-- BEGIN
SET #fname = 'D:\Backup\'+#dbname+'.bak'
BACKUP DATABASE #dbname TO disk=#fname
FETCH NEXT FROM cl INTO #dbname
END
CLOSE c1
DEALLOCATE c1
END IF
I use a piece of code to loop through all the databases on an MS SQL server. It works fine for altering a column on a table and also for updating the data. But I continue to get errors when trying to alter a stored procedure. Here is the code:
use master
declare #dbname varchar(100)
,#sql varchar(max)
declare db_cur cursor for
SELECT name
FROM sys.databases where ([name] like 'ce%')
and [state] = 0
open db_cur
fetch next from db_cur into #dbname
while ##FETCH_STATUS = 0
begin
set #sql=
'ALTER TABLE ['+#dbname+'].[dbo].MyStuff
ADD myNewColumn bit NULL DEFAULT(0)
'
exec(#sql)
fetch next from db_cur into #dbname
end
close db_cur
deallocate db_cur
So the code above works perfectly fine. But when I alter that code to instead do an alter stored procedure I receive the message below:
'CREATE/ALTER PROCEDURE' does not allow specifying the database name as a prefix to the object name.
I realized that the message stated I can't use the database name in the front of the procedure like I was doing here: ALTER procedure ['+#dbname+'].[dbo].[spSelectSomething]. But I haven't been able to figure out a way around the issue. Thanks for your help.
You need to nest dynamic SQL for this task because a proc CREATE or ALTER must be the first statement in the batch:
SET #sql= N'EXEC(N''USE ' + QUOTENAME(#dbname) + N';EXEC(N''''CREATE PROC...;'''')'')';
I am working with two databases that are not accessible at the same time. One of the standard methods of dealing with this I've seen on here is to create dynamic sql for loading one from the other.
I created a stored procedure that would drop update statements from an existing database. My issue is what happens when the XML is too large to be held in a VARCHAR(max).
Here is a relevant snippet from my attempt where field2 is actually of an XML data type:
DECLARE #field1Col VARCHAR(50)
DECLARE #field2Col VARCHAR(max)
DECLARE #vsSQL VARCHAR(max)
DECLARE curUpdates CURSOR FOR
-- field 1 is varchar(50), not null
-- field 2 is XML(.), null
SELECT
t.field1
,REPLACE(CAST(t.[field2] AS VARCHAR(max)), '''', '''''')
FROM
myTable t
WHERE
t.criteria = 0
OPEN curUpdates
FETCH NEXT FROM curUpdates INTO #field1Col, #field2Col
WHILE ##FETCH_STATUS = 0
BEGIN
SET #vsSQL = 'UPDATE dbo.myTable SET [field1] = ''' + #field1Col+ ''' WHERE [field2] = ''' + #field2Col + ''''
INSERT INTO #tmp ( SQLText ) VALUES ( #vsSQL )
FETCH NEXT FROM curUpdates INTO #field1Col, #field2Col
END
CLOSE curUpdates
DEALLOCATE curUpdates
SET NOCOUNT OFF;
SELECT * FROM #tmp
The issue I have is that even using VARCHAR(max), the XML will sometimes overrun the size. The end product just stops when it reaches the so many characters (the max size of a VARCHAR?).
Is there another approach for working with large XML (splitting into chunks, avoid casting, etc.) where I can build a string of update statements from it?
I do not have access to database B. I'd like to (one time run) update
a few tables in database B
The one time run could point to something like this:
CREATE DATABASE MyOneTimeRun;
GO
USE MyOneTimeRun;
GO
SELECT * INTO MyCopy FROM YourDatabase.dbo.YourTable;
GO
BACKUP DATABASE [MyOneTimeRun] TO DISK = N'C:\Path\MyOneTimeRun.bak' WITH NOFORMAT, NOINIT
,NAME = N'MyOneTimeRun-Copy of MyTable'
,SKIP, NOREWIND, NOUNLOAD, STATS = 10
GO
USE master;
GO
EXEC msdb.dbo.sp_delete_database_backuphistory #database_name = N'MyOneTimeRun'
GO
USE [master]
GO
ALTER DATABASE [MyOneTimeRun] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
DROP DATABASE [MyOneTimeRun]
GO
Now you have a BAK-file with the content you need which you can restore on your other server.
There you use the appropriate scripts to shuffle your data typesafe and clean from the copy into your target.
In the following TSQL code I can use my local variable in first few lines and then I cannot use it again. Why am I not able to use it in the last line of my code ?
Where does its scope end?
DECLARE ##CurrentDB varchar(50);
SET ##CurrentDB = 'MyDBNAME';
-- Find Data & Log Fiel locations
SELECT DB_NAME(database_id) AS DatabaseName, name AS LogicalFileName, physical_name AS PhysicalFileName, size/(128*1024) [GB]
FROM sys.master_files AS mf
WHERE DB_NAME(database_id) = ##CurrentDB
-- Detach DB
USE
GO
ALTER DATABASE SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO
USE [master]
GO
EXEC master.dbo.sp_detach_db #dbname = ##Cur
GO
Here is the error:
Any time you pass SQL Server a GO command, that ends the context in which the variable exists and it is no longer accessible by anything after that point in the T-SQL code. "Global" variables as such do not exist in SQL Server, but there are ways around it, generally by implementing a global variable table (either temporary or permanent).
You can get the general idea from this blog post that sets up a permanent table to track global variables.
As a workaround, you can use a Global Temp Table:
Declare #CurrentDB varchar(50)
SET #CurrentDB = 'MyDBNAME'
Create Table ##CurrentDB (Name varchar(50))
Insert Into ##CurrentDB Values (#CurrentDB)
GO
-- ...
GO
Declare #CurrentDB varchar(50)
Select Top 1 #CurrentDB = Name From ##CurrentDB
Select #CurrentDB
This should work even if you are using different databases in each part of your script.
Why use a global variable or temp table at all? This cries out to me to be a user defined stored procedure.
Here are the business rules.
1 - You basically want to get the location and size of a database you want to detach.
2 - Want to set database to single user mode.
3 - You want to detach the database. Just remember the files will be hanging around afterwards.
I created it in the MSDB database but you can put it in your own toolbox database.
I did not check to see if the database is really in use only mode. - TODO list
Just check the mode in the sys.databases table. If the ALTER, fails do not try the detach. Just notify the user to find the spids and kill them.
http://technet.microsoft.com/en-us/library/ms178534.aspx
4 - I did not put any error handling in. - TODO list
Last but not least, this solution could be prone to SQL injection, do not give the world access.
In short, the stored procedure below does just what you want.
--
-- Create a user stored procedure
--
-- Start in msdb
use msdb
go
-- drop existing
if object_id('my_detach_process') > 0
drop procedure my_detach_process
go
-- create new
create procedure my_detach_process(#dbname sysname)
as
-- Show the data
SELECT
DB_NAME(mf.database_id) AS DatabaseName,
mf.name AS LogicalName,
mf.physical_name AS PhysicalName, mf.size as SizeMb
FROM sys.master_files AS mf
WHERE DB_NAME(database_id) = #dbname;
-- Set to single user
DECLARE #sqlstmt1 nvarchar(512) = '';
SET #sqlstmt1 = 'ALTER DATABASE [' + #dbname + '] SET SINGLE_USER WITH ROLLBACK IMMEDIATE';
EXEC sp_executesql #sqlstmt1;
-- Detach
DECLARE #sqlstmt2 nvarchar(512) = '';
SET #sqlstmt2 = 'USE [master]; EXEC master.dbo.sp_detach_db #dbname = ' + #dbname;
EXEC sp_executesql #sqlstmt2;
GO
--
-- Sample call
--
-- Choose master
use master
go
-- Create toy db
create database toy;
go
-- Call the sp
exec msdb.dbo.my_detach_process #dbname = 'Toy'
Sample output from sample call.
I use the database name in several places in my script, and I want to be able to quickly change it, so I'm looking for something like this:
DECLARE #DBNAME VARCHAR(50)
SET #DBNAME = 'TEST'
CREATE DATABASE #DBNAME
GO
ALTER DATABASE #DBNAME SET COMPATIBILITY_LEVEL = 90
GO
ALTER DATABASE #DBNAME SET RECOVERY SIMPLE
GO
But it doesn't work. So what's the correct way to write this code?
Put the entire script into a template string, with {SERVERNAME} placeholders. Then edit the string using:
SET #SQL_SCRIPT = REPLACE(#TEMPLATE, '{SERVERNAME}', #DBNAME)
and then run it with
EXECUTE (#SQL_SCRIPT)
It's hard to believe that, in the course of three years, nobody noticed that my code doesn't work!
You can't EXEC multiple batches. GO is a batch separator, not a T-SQL statement. It's necessary to build three separate strings, and then to EXEC each one after substitution.
I suppose one could do something "clever" by breaking the single template string into multiple rows by splitting on GO; I've done that in ADO.NET code.
And where did I get the word "SERVERNAME" from?
Here's some code that I just tested (and which works):
DECLARE #DBNAME VARCHAR(255)
SET #DBNAME = 'TestDB'
DECLARE #CREATE_TEMPLATE VARCHAR(MAX)
DECLARE #COMPAT_TEMPLATE VARCHAR(MAX)
DECLARE #RECOVERY_TEMPLATE VARCHAR(MAX)
SET #CREATE_TEMPLATE = 'CREATE DATABASE {DBNAME}'
SET #COMPAT_TEMPLATE='ALTER DATABASE {DBNAME} SET COMPATIBILITY_LEVEL = 90'
SET #RECOVERY_TEMPLATE='ALTER DATABASE {DBNAME} SET RECOVERY SIMPLE'
DECLARE #SQL_SCRIPT VARCHAR(MAX)
SET #SQL_SCRIPT = REPLACE(#CREATE_TEMPLATE, '{DBNAME}', #DBNAME)
EXECUTE (#SQL_SCRIPT)
SET #SQL_SCRIPT = REPLACE(#COMPAT_TEMPLATE, '{DBNAME}', #DBNAME)
EXECUTE (#SQL_SCRIPT)
SET #SQL_SCRIPT = REPLACE(#RECOVERY_TEMPLATE, '{DBNAME}', #DBNAME)
EXECUTE (#SQL_SCRIPT)
You can also use sqlcmd mode for this (enable this on the "Query" menu in Management Studio).
:setvar dbname "TEST"
CREATE DATABASE $(dbname)
GO
ALTER DATABASE $(dbname) SET COMPATIBILITY_LEVEL = 90
GO
ALTER DATABASE $(dbname) SET RECOVERY SIMPLE
GO
EDIT:
Check this MSDN article to set parameters via the SQLCMD tool.
Unfortunately you can't declare database names with a variable in that format.
For what you're trying to accomplish, you're going to need to wrap your statements within an EXEC() statement. So you'd have something like:
DECLARE #Sql varchar(max) ='CREATE DATABASE ' + #DBNAME
Then call
EXECUTE(#Sql) or sp_executesql(#Sql)
to execute the sql string.
You cannot use a variable in a create table statement. The best thing I can suggest is to write the entire query as a string and exec that.
Try something like this:
declare #query varchar(max);
set #query = 'create database TEST...';
exec (#query);