T-SQL query each database in instance without using a cursor - sql-server

Using SQL Server 2012.
Using the system views stored for each database I wanted to keep a summary of index behavior for all indexes across the instance. The code below collects some stats and drops them in a table for later use. The snag is, I had to use a cursor to loop through the databases in the instance. Can anyone suggest a nicer way of doing this without having to use the cursor?
USE Master
GO
-- If the procedure already exists then update it.
IF OBJECT_ID('sp_index_stats') IS NOT NULL
DROP PROCEDURE sp_index_stats
GO
CREATE PROCEDURE sp_index_stats AS
DECLARE #dbname NVARCHAR(128)
DECLARE #finalSQL NVARCHAR(1024)
-- Results of the query are put into a table in Demo_DB.
-- So empty it now for the new results.
TRUNCATE TABLE Demo_DB.dbo.index_stats
-- Use a cursor to get a list of all the tables in the instance.
DECLARE database_cursor CURSOR LOCAL FOR
SELECT name
FROM Master.dbo.sysdatabases
WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')
OPEN database_cursor
FETCH NEXT FROM database_cursor INTO #dbname
WHILE ##FETCH_STATUS = 0
BEGIN
-- Each database will contain the same structue, so I need
-- to create a standard query just changing the database name.
SET #finalSQL = 'INSERT INTO Demo_DB.dbo.index_stats '
-- Sort out the select statement.
SET #finalSQL = #finalSQL + 'SELECT sdb.name, st.name, si.name, si.type_desc, '
SET #finalSQL = #finalSQL + 'si.is_primary_key, si.fill_factor, '
SET #finalSQL = #finalSQL + 'sd.user_scans, sd.last_user_scan, '
SET #finalSQL = #finalSQL + 'sd.user_updates, sd.last_user_update '
-- The 'from' and 'joins' need to be adjusted slightly for each databse.
SET #finalSQL = #finalSQL + 'FROM ' + #dbname + '.sys.tables st '
SET #finalSQL = #finalSQL + 'JOIN ' + #dbname + '.sys.indexes si ON (si.object_id = st.object_id) '
SET #finalSQL = #finalSQL + 'JOIN ' + #dbname + '.sys.dm_db_index_usage_stats sd ON (si.object_id = sd.object_id) '
SET #finalSQL = #finalSQL + 'JOIN ' + #dbname + '.sys.databases sdb ON (sdb.database_id = sd.database_id) ;'
-- Ok, let's run that...
EXEC (#finalSQL)
-- Get the next database in the instance..,
FETCH NEXT FROM database_cursor INTO #dbname
END
CLOSE database_cursor
DEALLOCATE database_cursor
GO
Regards,
JC.

What about concatenation?
Something like this (not reproduced the whole stored procedure, but hopefully you get the gist)
DECLARE #SQL VARCHAR(MAX)
SET #SQL = ''
SELECT #SQL = #SQL +
'INSERT INTO Demo_DB.dbo.index_stats ' +
'SELECT sdb.name, st.name, si.name, si.type_desc, ' +
'si.is_primary_key, si.fill_factor, ' +
'sd.user_scans, sd.last_user_scan, ' +
'sd.user_updates, sd.last_user_update ' +
'FROM ' + name + '.sys.tables st ' +
'JOIN ' + name + '.sys.indexes si ON (si.object_id = st.object_id) ' +
'JOIN ' + name + '.sys.dm_db_index_usage_stats sd ON (si.object_id = sd.object_id) ' +
'JOIN ' + name + '.sys.databases sdb ON (sdb.database_id = sd.database_id) ;'
FROM Master.dbo.sysdatabases
WHERE name NOT IN ('master', 'model', 'msdb', 'tempdb')
SELECT #SQL
(Obviously not EXECing anything at this point).

Related

How to use while loop to iterate through each table name in SQL Server?

I'm working with Dynamic SQL (still in the learning phase) and I'm stuck at a part where I need to use a WHILE loop:
SET #tableName = (SELECT DISTINCT TableName FROM #dataStructure)
Here basically I want to make sure that the operations inside the while loop should occur for all the tables in the #tableName (defined above). I don't know how I can give this condition as an input for the while loop.
WHILE() #HOW CAN I PUT THE CONDITION HERE????
BEGIN
SET #str = ''
SET #sqlstr = ''
SELECT #table = TableName FROM #dataStructure
SET #str = 'UPDATE a0' + char(13) + char(10)
+ ' SET a0.Mjolnir_Source_ID = CONCAT( '
SELECT #str = #str + IIF(ReferenceTable IS NULL, 'a0.' + columnName , alias + '.Mjolnir_Source_ID') + ','
FROM #dataStructure
WHERE TableName = #tableName AND ReferenceTable IS NOT NULL
ORDER BY columnName
SELECT #str = #str + ') FROM ' + #table + ' a0'
SELECT #sqlstr = #sqlstr + +
+ ' INNER JOIN ' + QUOTENAME(#U4SM_db_name) + '.dbo.' + QUOTENAME(ReferenceTable) + ' ' + alias + char(13) + char(10)
+ ' ON a0.' + columnName + ' = ' + alias + '.' + ReferenceColumn + char(13) + char(10)
FROM #dataStructure
WHERE TableName = #tableName AND ReferenceTable IS NOT NULL
ORDER BY columnPosition
select #str + #sqlstr
select #sqlstr
SET #tableName = #tableName + 1
END
Can anyone please help me out here?
Here's an example of a WHILE loop. Basically, you get the first TableName, then if it's NOT NULL, you do your functions. Then get the next table name, and repeat as necessary.
DECLARE #CurrentTableName nvarchar(100)
DECLARE #CustomSQL nvarchar(4000)
SET #CurrentTableName = (SELECT TOP 1 TableName FROM #dataStructure ORDER BY TableName)
WHILE #CurrentTableName IS NOT NULL
BEGIN
SET #CustomSQL = 'SELECT TOP 10 * FROM ' + #CurrentTableName
EXEC (#CustomSQL)
SET #CurrentTableName = (SELECT TOP 1 TableName FROM #dataStructure WHERE TableName > #CurrentTableName ORDER BY TableName)
END
Note that SQL commands often cannot contain variable names in key spots (e.g., SELECT * FROM #tableName). Instead, you save it as an SQL string (what I've called #CustomSQL above) and then EXEC it (put brackets around the variable name though).
Edit: Do this on a test site first before production, and know where the 'cancel query' button is. It's not often, but it's also not unknown, that the 'getting the next row' part isn't properly written and it just runs in a perpetual loop.
FETCH CURSOR with WHILE. Example:
DECLARE myCursor CURSOR FOR
SELECT DISTINCT TableName FROM #dataStructure;
OPEN myCursor;
FETCH NEXT FROM myCursor INTO #table:Name;
WHILE ##FETCH_STATUS = 0
BEGIN
Print ' ' + #TableName
FETCH NEXT FROM myCursor INTO #TableName;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
GO
Don't recreate the wheel unless you need a better wheel:
sp_MSforeachtable
https://www.sqlshack.com/an-introduction-to-sp_msforeachtable-run-commands-iteratively-through-all-tables-in-a-database/
If you are worried about using an undocumented procedure in production that might change in the future, simply script it out and create your own custom named version.

Msg 8169, Level 16, State 2, Line 52 Conversion failed when converting from a character string to uniqueidentifier

I am new to this SQL scripting.
Getting error while trying to execute a delete command.
Msg 8169, Level 16, State 2, Line 52 Conversion failed when converting
from a character string to uniqueidentifier.
And the script which I want to execute is:
-- attachments cleardown script
-- SJM 18/09/2009
set nocount on
declare #tableName nvarchar(200)
declare #columnName nvarchar(200)
declare #moduleName nvarchar(200)
declare #objectName nvarchar(200)
declare #attributeName nvarchar(200)
declare #dynamicSQL nvarchar(500)
declare #attachXML varchar(2000)
declare #fileGuid varchar(36)
declare #deletedXML varchar(100)
set #deletedXML = '<DataObjectAttachment><FileName>Deleted</FileName></DataObjectAttachment>'
declare attachCursor cursor fast_forward for
select t5.md_name, t4.md_name, t3.md_title, t2.md_title, t1.md_title
from md_attribute_type t1
join md_class_type t2 on t1.md_class_type_guid = t2.md_guid
join md_module t3 on t2.md_module_guid = t3.md_guid
join md_database_column t4 on t1.md_database_column_guid = t4.md_guid
join md_database_table t5 on t2.md_database_table_guid = t5.md_guid
where t1.md_data_type = 16 and (
t1.md_defined_by = 2 or
t1.md_guid in ('103DBEAB-252F-4C9A-952A-90A7800FFC00', '2515E980-788D-443D-AA89-AA7CC3653867',
'2D29495E-785E-4062-A49C-712A8136EBC7', '6A204C77-1007-48CC-B3BC-077B094540AE',
'9A24BFEF-2CAB-4604-8814-656BA0B9ECC3', 'C368CB18-B4C4-4C4F-839C-F7E6E1E17AA8',
'0814586B-A11E-4129-AF9B-9EC58120C9AF', 'BD4F73C4-07CA-4DC2-86AD-D713FBC6E7BA')
)
and t2.md_behaviour not in (2, 4, 8) -- exclude reference lists
and t2.md_guid not in (select b.md_class_type_guid from md_class_behaviour b where b.md_behaviour_type_guid in
(select bt.md_guid from md_behaviour_type bt where bt.md_name in ('Category','Ranked'))) -- exclude categories and ordered lists
and t3.md_name != 'Lifecycle'
order by t3.md_title, t2.md_title, t1.md_title
open attachCursor
fetch next from attachCursor into #tableName, #columnName, #moduleName, #objectName, #attributeName
while (##FETCH_STATUS = 0)
begin
print 'Deleting from ' + #moduleName + ' -> ' + #objectName + ' (' + #tableName + ') -> ' + #attributeName + ' (' + #columnName + ')...'
set #dynamicSQL = 'declare attachCursor1 cursor fast_forward for'
set #dynamicSQL = #dynamicSQL + ' select ' + #columnName + ' from ' + #tableName
set #dynamicSQL = #dynamicSQL + ' where ' + #columnName + ' != ''' + #deletedXML + ''''
exec sp_executesql #dynamicSQL
open attachCursor1
fetch next from attachCursor1 into #attachXML
while (##FETCH_STATUS = 0)
begin
set #fileGuid = substring(#attachXML, charindex('<Guid>', #attachXML) + 6, 36)
delete from tps_attachment_data where tps_guid = convert(uniqueidentifier, #fileGuid)
fetch next from attachCursor1 into #attachXML
end
close attachCursor1
deallocate attachCursor1
set #dynamicSQL = 'update ' + #tableName + ' set ' + #columnName + ' = ''' + #deletedXML + ''''
set #dynamicSQL = #dynamicSQL + ' where ' + #columnName + ' != ''' + #deletedXML + ''''
exec sp_executesql #dynamicSQL
print '- Deleted ' + convert(varchar(10),##Rowcount) + ' records.'
fetch next from attachCursor into #tableName, #columnName, #moduleName, #objectName, #attributeName
end
close attachCursor
deallocate attachCursor
set nocount off
print char(13) + 'Attachment cleardown complete.'
print char(13) + 'Next steps to reclaim data file space:'
print '1. Take a backup!'
print '2. Shrink the database using the command: DBCC SHRINKDATABASE(''' + convert(varchar(100),SERVERPROPERTY('ServerName')) + ''')'
print '3. Put the database into SINGLE_USER mode (Properties -> Options -> Restrict Access)'
print '4. Detach the database'
print '5. Rename the database .LDF log file'
print '6. Re-attach the database using single file method'
When I parse above script then it is completing successfully but on execution it throws the error.
There are two new functions that have been available since SQL Server 2012: TRY_CAST and TRY_CONVERT, which complement the existing CAST and CONVERT functions.
If you cast an empty string to a unique identifier:
SELECT CAST('' as uniqueidentifier) uid
This would result in the error you are seeing. The same would be true or other strings that aren't GUIDs. Use TRY_CAST instead and the function returns a NULL instead of throwing an error. You can then skip records that don't have a valid GUID.
begin
set #fileGuid = substring(#attachXML, charindex('<Guid>', #attachXML) + 6, 36)
DECLARE #fileGuidOrNull uniqueidentifier = try_convert(uniqueidentifier, #fileGuid)
IF #fileGuidOrNull IS NOT NULL
delete from tps_attachment_data where tps_guid = #fileGuidOrNull
fetch next from attachCursor1 into #attachXML
end

Database Files show in master_files but not in database_files

I have a few SQL databases, that I inherited.
I have a fresh install of SQL 2012
I have attached the databases to the server without an issue.
Yet, where I run Select * From sys.database_files; they are not in there, but when I run Select * From sys.master_files; they are.
This is messing up some code I am attempting to write by throwing the following errors:
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 'I:'.
Msg 8985, Level 16, State 1, Line 1
Could not locate file 'I:\SQL Databases\Cloud.CMS_log.ldf' for database 'master' in sys.database_files. The file either does not exist, or was dropped.
How can I fix it so they are there in sys.database_files so my code runs?
Declare #TempDBList Table
(
Id int,
DBName VarChar(250),
FileType int,
DBFile VarChar(1000)
);
Declare #BackupLocation VarChar(Max) = 'I:\SQL Databases\Backup';
Declare #FileLocation VarChar(Max);
Declare #DBName VarChar(250);
Declare #FileType Int;
Declare #DBBackup VarChar(Max);
Declare #LogBackup VarChar(Max);
Declare #Sql VarChar(Max);
Insert Into #TempDBList
(id, DBName, FileType, DBFile)
Select a.database_id DBid, a.Name , b.type FileType, b.physical_name As FileLocation
From sys.databases a
Inner Join sys.master_files b On b.database_id = a.database_id
Where b.state = 0 AND a.database_id > 4;
Select #DBName = DBName, #FileType = FileType, #FileLocation = DBFile From #TempDBList Order By DBName;
While ##ROWCOUNT <> 0
Begin
--- Set all databases to Simple Recovery
Set #Sql = 'Alter DATABASE ' + QUOTENAME(#DBName) + ' Set RECOVERY SIMPLE';
Exec(#Sql);
Set #DBBackup = #BackupLocation + #DBName + '\' + #DBName + '_' + Convert(Varchar(500), GetDate(), 112)+ '.bak';
Set #LogBackup = #BackupLocation + #DBName + '\' + #DBName + '_' + Convert(Varchar(500), GetDate(), 112)+ '.log.bak';
If #FileType = 1
Begin
Set #Sql = 'Backup Database ' + QUOTENAME(#DBName) + ' To Disk = ' + #DBBackup;
Exec(#Sql);
Exec('DBCC SHRINKFILE(''' + #FileLocation + ''', TruncateOnly)');
End
Else If #FileType = 0
Begin
Set #Sql = 'Backup Log ' + QUOTENAME(#DBName) + ' To Disk = ' + #LogBackup;
Exec(#Sql);
Exec('DBCC SHRINKFILE(''' + #FileLocation + ''', TruncateOnly)');
End
End
Screenshot for both sys.database_files & sys.master_files
http://prntscr.com/57p49b
sys.master_files is a system wide view and will show you all the files on the instance you are connected to (where you have sufficient permission), sys.database_files is a per database view, and will only show files in the specified database. You either need to connect to the correct database to see the files, e.g:
USE Master;
SELECT *
FROM sys.database_files;
Or use the 3 part object name:
SELECT *
FROM master.sys.database_files;
EDIT
I can only apologise for not explaining myself very well, but the above does point out why you cannot see files in sys.database_files that you can see in sys.master_files.
Look at the following screenshot:
You can see that after connecting to a different database (USE TestDB) different files are showing in sys.database_files, but the record count (and the actual records) in sys.master_files is the same regardless of database.
Now, looking at your actual error:
Could not locate file 'I:\SQL Databases\Cloud.CMS_log.ldf' for database 'master' in sys.database_files.
This explains the problem, you are connected to the database master, so sys.database_files would only show the files in the master database (master, and masterlog). You are looking for CMS_log which is presumably located in the database CMS, so to view this file in sys.database_files you would need to run:
USE CMS;
SELECT * FROM sys.database_files;
Or
SELECT * FROM CMS.sys.database_files;
Your actual error comes because you are trying to shrink the file CMS_Log while connected to the master database, which you cannot do, you would need to run:
USE CMS;
DBCC SHRINKFILE('CMS_Log', TRUNCATEONLY);
Hopefully this explains why you are getting the error.
FULL SCRIPT
DECLARE #BackupLocation VARCHAR(MAX) = '',
#DBName SYSNAME,
#DataFile SYSNAME,
#LogFile SYSNAME,
#SQL NVARCHAR(MAX);
DECLARE FileCursor CURSOR STATIC FORWARD_ONLY READ_ONLY
FOR
SELECT DBName = d.Name,
DataFile = MAX(CASE WHEN f.Type = 0 THEN f.Name END),
LogFile = MAX(CASE WHEN f.Type = 1 THEN f.Name END)
FROM sys.databases d
INNER JOIN sys.master_files f
ON d.database_id = f.database_id
WHERE d.Name NOT IN ('master', 'tempdb', 'model', 'msdb')
AND d.Name NOT LIKE 'ReportServer$%'
GROUP BY d.Name;
OPEN FileCursor;
FETCH NEXT FROM FileCursor INTO #DBName, #DataFile, #LogFile;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = '
USE ' + QUOTENAME(#DBName) + ';
ALTER DATABASE ' + QUOTENAME(#DBName) + ' SET RECOVERY SIMPLE;
BACKUP DATABASE ' + QUOTENAME(#DBName) + ' TO DISK = ''' + #BackupLocation +
+ #DBName + '_' + CONVERT(VARCHAR(8), GETDATE(), 112) + '.bak'';
ALTER DATABASE ' + QUOTENAME(#DBName) + ' SET RECOVERY FULL;
BACKUP LOG ' + QUOTENAME(#DBName) + ' TO DISK = ''' + #BackupLocation +
+ #DBName + '_' + CONVERT(VARCHAR(8), GETDATE(), 112) + '.log.bak'';
DBCC SHRINKFILE(' + #DataFile + ', TRUNCATEONLY);
DBCC SHRINKFILE(' + #LogFile + ', TRUNCATEONLY);';
EXECUTE sp_executesql #SQL;
FETCH NEXT FROM FileCursor INTO #DBName, #DataFile, #LogFile;
END
CLOSE FileCursor;
DEALLOCATE FileCursor;
This generates and executes a command like the following for each database:
USE [TestDB];
ALTER DATABASE [TestDB] SET RECOVERY SIMPLE;
BACKUP DATABASE [TestDB] TO DISK = 'I:\SQL Databases\Backup\TestDB_20141119.bak';
ALTER DATABASE [TestDB] SET RECOVERY FULL;
BACKUP LOG [TestDB] TO DISK = 'I:\SQL Databases\Backup\TestDB_20141119.log.bak';
DBCC SHRINKFILE(TestDB, TRUNCATEONLY);
DBCC SHRINKFILE(TestDB_log, TRUNCATEONLY);

SQLServer conditional execution within 'EXEC' if a column exists

I'm new to SQLServer scripting (normally being a C++ developer), and would really appreciate a bit of assistance.
I am attempting to perform a "find and replace" update on all tables in a SQLServer database that contain a 'PROJID' column. I am really struggling to find a way to do this that doesn't report to me:
Msg 207, Level 16, State 1, Line 1 Invalid column name 'PROJID'.
The statement I am executing is:
EXEC
(
'IF EXISTS(SELECT * FROM sys.columns WHERE name = N''PROJID'' AND Object_ID = Object_ID(N''' + #TableName + '''))' +
' BEGIN' +
' UPDATE ' + #TableName +
' SET ' + #ColumnName + ' = REPLACE(' + #ColumnName + ',''' + #ReplaceIDStr + ''',''' + #FindIDStr + ''')' +
' WHERE ' + #ColumnName + ' LIKE ''' + #ReplaceIDStr + '''' + ' AND PROJID = ''1000''' +
' END'
)
I have also tried using:
'IF COL_LENGTH(''' + #TableName + ''',''PROJID'') IS NOT NULL' +
instead of the column-exist check above. This also still gives me the "Invalid Column Name" messages.
I would be happy to take the column-exist check outside of the 'Exec' statement, but I'm not sure how to go about doing this either.
You just need to do it in a different scope.
IF EXISTS (SELECT 1 FROM sys.columns ...)
BEGIN
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'UPDATE ...';
EXEC sp_executesql #sql;
END
Output the results of this query to text. Don't forget to change the values of the variables! Take the result of this and run it.
SET NOCOUNT ON
DECLARE #ColumnName VARCHAR(200) = 'ReplaceColumn'
, #ReplaceIdStr VARCHAR(200) = 'ExampleReplaceIdStr'
, #FindIdStr VARCHAR(200) = 'ExampleFindIdStr'
PRINT 'BEGIN TRAN'
PRINT 'SET XACT_ABORT ON'
SELECT
'UPDATE ' + C.TABLE_NAME + CHAR(13)
+ 'SET ' + #ColumnName + ' = REPLACE(' + #ColumnName + ', ''' + #ReplaceIdStr + ''', ''' + #FindIdStr + ''')' + CHAR(13)
+ 'WHERE ' + #ColumnName + ' LIKE ''%' + #ReplaceIdStr + '%'' AND PROJID = ''1000''' + CHAR(13)
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE C.COLUMN_NAME = 'PROJID'
PRINT 'COMMIT TRAN'
SET NOCOUNT OFF
EDIT: Also, some reasoning: You said you want update all tables where they contain a column called PROJID. Your first query just says that if the table #TableName has a PROJID column, then update #ColumnName on it. But it doesn't guarantee that it has #ColumnName on it. The query I gave doesn't check that either, because I'm assuming that all tables that have PROJID also have #ColumnName. If that isn't the case, let me know and I can update the answer to check that. That you're getting an Invalid Column Name error points to #ColumnName not existing.
Your query would have updated one table (#TableName) at most, whereas the one I gave you will update every table that has PROJID. I hope that's what your going for.
EDIT 2: Here is a version that would run it all at once:
DECLARE #ColumnName VARCHAR(200) = 'Value'
, #ReplaceIdStr VARCHAR(200) = 'ExampleReplaceIdStr'
, #FindIdStr VARCHAR(200) = 'ExampleFindIdStr'
DECLARE #Sql NVARCHAR(MAX)
DECLARE UpdateCursor CURSOR FOR
SELECT
'UPDATE ' + C.TABLE_NAME
+ ' SET ' + #ColumnName + ' = REPLACE(' + #ColumnName + ', ''' + #ReplaceIdStr + ''', ''' + #FindIdStr + ''')'
+ ' WHERE ' + #ColumnName + ' LIKE ''%' + #ReplaceIdStr + '%'' AND PROJID = ''1000'''
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE C.COLUMN_NAME = 'PROJID'
OPEN UpdateCursor
FETCH NEXT FROM UpdateCursor
INTO #Sql
WHILE ##FETCH_STATUS = 0
BEGIN
EXEC sp_executesql #Sql
FETCH NEXT FROM UpdateCursor
INTO #Sql
END
CLOSE UpdateCursor
DEALLOCATE UpdateCursor

What is the best way to copy a database?

When I want to make a copy of a database, I always create a new empty database, and then restore a backup of the existing database into it. However, I'm wondering if this is really the least error-prone, least complicated, and most efficient way to do this?
It is possible to skip the step of creating the empty database. You can create the new database as part of the restore process.
This is actually the easiest and best way I know of to clone a database. You can eliminate errors by scripting the backup and restore process rather than running it through the SQL Server Management Studio
There are two other options you could explore:
Detach the database, copy the .mdf file and re-attach.
Use SQL Server Integration Services (SSIS) to copy all the objects over
I suggest sticking with backup and restore and automating if necessary.
Here's a dynamic sql script I've used in the past. It can be further modified but it will give you the basics. I prefer scripting it to avoid the mistakes you can make using the Management Studio:
Declare #OldDB varchar(100)
Declare #NewDB varchar(100)
Declare #vchBackupPath varchar(255)
Declare #query varchar(8000)
/*Test code to implement
Select #OldDB = 'Pubs'
Select #NewDB = 'Pubs2'
Select #vchBackupPath = '\\dbserver\C$\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Backup\pubs.bak'
*/
SET NOCOUNT ON;
Select #query = 'Create Database ' + #NewDB
exec(#query)
Select #query = '
Declare #vBAKPath varchar(256)
declare #oldMDFName varchar(100)
declare #oldLDFName varchar(100)
declare #newMDFPath varchar(100)
declare #newLDFPath varchar(100)
declare #restQuery varchar(800)
select #vBAKPath = ''' + #vchBackupPath + '''
select #oldLDFName = name from ' + #OldDB +'.dbo.sysfiles where filename like ''%.ldf%''
select #oldMDFName = name from ' + #OldDB +'.dbo.sysfiles where filename like ''%.mdf%''
select #newMDFPath = physical_name from ' + #NewDB +'.sys.database_files where type_desc = ''ROWS''
select #newLDFPath = physical_name from ' + #NewDB +'.sys.database_files where type_desc = ''LOG''
select #restQuery = ''RESTORE DATABASE ' + #NewDB +
' FROM DISK = N'' + '''''''' + #vBAKpath + '''''''' +
'' WITH MOVE N'' + '''''''' + #oldMDFName + '''''''' +
'' TO N'' + '''''''' + #newMDFPath + '''''''' +
'', MOVE N'' + '''''''' + #oldLDFName + '''''''' +
'' TO N'' + '''''''' + #newLDFPath + '''''''' +
'', NOUNLOAD, REPLACE, STATS = 10''
exec(#restQuery)
--print #restQuery'
exec(#query)
Backup and Restore is the most straight-forward way I know. You have to be careful between servers as security credentials don't come with the restored database.
The Publish to Provider functionality has worked great for me. See Scott Gu's Blog Entry.
If you need something really robust look at redgate software's tools here...if you are doing much SQL at all, these are worth the $$.
::================ BackUpAllMyDatabases.cmd ============= START
::BackUpAllMyDatabases.cmd
:: COMMAND LINE BATCH SCRIPT FOR TAKING BACKUP OF ALL DATABASES
::RUN THE SQL SCRIPT VIA THE COMMAND LINE WITH LOGGING
sqlcmd -S localhost -e -i "BackUpAllMyDatabases.sql" -o Result_Of_BackUpAllMyDatabases.log
::VIEW THE RESULTS
Result_Of_BackUpAllMyDatabases.log
::pause
::================ BackUpAllMyDatabases.cmd ============= END
--=================================================BackUpAllMyDatabases.sql start
DECLARE #DBName varchar(255)
DECLARE #DATABASES_Fetch int
DECLARE DATABASES_CURSOR CURSOR FOR
select
DATABASE_NAME = db_name(s_mf.database_id)
from
sys.master_files s_mf
where
-- ONLINE
s_mf.state = 0
-- Only look at databases to which we have access
and has_dbaccess(db_name(s_mf.database_id)) = 1
-- Not master, tempdb or model
--and db_name(s_mf.database_id) not in ('Master','tempdb','model')
group by s_mf.database_id
order by 1
OPEN DATABASES_CURSOR
FETCH NEXT FROM DATABASES_CURSOR INTO #DBName
WHILE ##FETCH_STATUS = 0
BEGIN
declare #DBFileName varchar(256)
set #DBFileName = #DbName + '_' + replace(convert(varchar, getdate(), 112), '-', '.') + '.bak'
--REMEMBER TO PUT HERE THE TRAILING \ FOR THE DIRECTORY !!!
exec ('BACKUP DATABASE [' + #DBName + '] TO DISK = N''D:\DATA\BACKUPS\' +
#DBFileName + ''' WITH NOFORMAT, INIT, NAME = N''' +
#DBName + '-Full Database Backup'', SKIP, NOREWIND, NOUNLOAD, STATS = 100')
FETCH NEXT FROM DATABASES_CURSOR INTO #DBName
END
CLOSE DATABASES_CURSOR
DEALLOCATE DATABASES_CURSOR
--BackUpAllMyDatabases==========================end
--======================RestoreDbFromFile.sql start
-- Restore database from file
-----------------------------------------------------------------
use master
go
declare #backupFileName varchar(100), #restoreDirectory varchar(100),
#databaseDataFilename varchar(100), #databaseLogFilename varchar(100),
#databaseDataFile varchar(100), #databaseLogFile varchar(100),
#databaseName varchar(100), #execSql nvarchar(1000)
-- Set the name of the database to restore
set #databaseName = 'ReplaceDataBaseNameHere'
-- Set the path to the directory containing the database backup
set #restoreDirectory = 'ReplaceRestoreDirectoryHere' -- such as 'c:\temp\'
-- Create the backup file name based on the restore directory, the database name and today's date
#backupFileName = #restoreDirectory + #databaseName + '-' + replace(convert(varchar, getdate(), 110), '-', '.') + '.bak'
-- set #backupFileName = 'D:\DATA\BACKUPS\server.poc_test_fbu_20081016.bak'
-- Get the data file and its path
select #databaseDataFile = rtrim([Name]),
#databaseDataFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
inner join
master.dbo.sysfilegroups as groups
on
files.groupID = groups.groupID
where DBID = (
select dbid
from master.dbo.sysdatabases
where [Name] = #databaseName
)
-- Get the log file and its path
select #databaseLogFile = rtrim([Name]),
#databaseLogFilename = rtrim([Filename])
from master.dbo.sysaltfiles as files
where DBID = (
select dbid
from master.dbo.sysdatabases
where [Name] = #databaseName
)
and
groupID = 0
print 'Killing active connections to the "' + #databaseName + '" database'
-- Create the sql to kill the active database connections
set #execSql = ''
select #execSql = #execSql + 'kill ' + convert(char(10), spid) + ' '
from master.dbo.sysprocesses
where db_name(dbid) = #databaseName
and
DBID <> 0
and
spid <> ##spid
exec (#execSql)
print 'Restoring "' + #databaseName + '" database from "' + #backupFileName + '" with '
print ' data file "' + #databaseDataFile + '" located at "' + #databaseDataFilename + '"'
print ' log file "' + #databaseLogFile + '" located at "' + #databaseLogFilename + '"'
set #execSql = '
restore database [' + #databaseName + ']
from disk = ''' + #backupFileName + '''
with
file = 1,
move ''' + #databaseDataFile + ''' to ' + '''' + #databaseDataFilename + ''',
move ''' + #databaseLogFile + ''' to ' + '''' + #databaseLogFilename + ''',
norewind,
nounload,
replace'
exec sp_executesql #execSql
exec('use ' + #databaseName)
go
-- If needed, restore the database user associated with the database
/*
exec sp_revokedbaccess 'myDBUser'
go
exec sp_grantdbaccess 'myDBUser', 'myDBUser'
go
exec sp_addrolemember 'db_owner', 'myDBUser'
go
use master
go
*/
--======================RestoreDbFromFile.sql

Resources