I wrote a stored procedure that restores as set of the database backups. It takes two parameters - a source directory and a restore directory. The procedure looks for all .bak files in the source directory (recursively) and restores all the databases.
The stored procedure works as expected, but it has one issue - if I uncomment the try-catch statements, the procedure terminates with the following error:
error_number = 3013
error_severity = 16
error_state = 1
error_message = DATABASE is terminating abnormally.
The weird part is sometimes (it is not consistent) the restore is done even if the error occurs. The procedure:
create proc usp_restore_databases
(
#source_directory varchar(1000),
#restore_directory varchar(1000)
)
as
begin
declare #number_of_backup_files int
-- begin transaction
-- begin try
-- step 0: Initial validation
if(right(#source_directory, 1) <> '\') set #source_directory = #source_directory + '\'
if(right(#restore_directory, 1) <> '\') set #restore_directory = #restore_directory + '\'
-- step 1: Put all the backup files in the specified directory in a table --
declare #backup_files table ( file_path varchar(1000))
declare #dos_command varchar(1000)
set #dos_command = 'dir ' + '"' + #source_directory + '*.bak" /s/b'
/* DEBUG */ print #dos_command
insert into #backup_files(file_path) exec xp_cmdshell #dos_command
delete from #backup_files where file_path IS NULL
select #number_of_backup_files = count(1) from #backup_files
/* DEBUG */ select * from #backup_files
/* DEBUG */ print #number_of_backup_files
-- step 2: restore each backup file --
declare backup_file_cursor cursor for select file_path from #backup_files
open backup_file_cursor
declare #index int; set #index = 0
while(#index < #number_of_backup_files)
begin
declare #backup_file_path varchar(1000)
fetch next from backup_file_cursor into #backup_file_path
/* DEBUG */ print #backup_file_path
-- step 2a: parse the full backup file name to get the DB file name.
declare #db_name varchar(100)
set #db_name = right(#backup_file_path, charindex('\', reverse(#backup_file_path)) -1) -- still has the .bak extension
/* DEBUG */ print #db_name
set #db_name = left(#db_name, charindex('.', #db_name) -1)
/* DEBUG */ print #db_name
set #db_name = lower(#db_name)
/* DEBUG */ print #db_name
-- step 2b: find out the logical names of the mdf and ldf files
declare #mdf_logical_name varchar(100),
#ldf_logical_name varchar(100)
declare #backup_file_contents table
(
LogicalName nvarchar(128),
PhysicalName nvarchar(260),
[Type] char(1),
FileGroupName nvarchar(128),
[Size] numeric(20,0),
[MaxSize] numeric(20,0),
FileID bigint,
CreateLSN numeric(25,0),
DropLSN numeric(25,0) NULL,
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0) NULL,
ReadWriteLSN numeric(25,0) NULL,
BackupSizeInBytes bigint,
SourceBlockSize int,
FileGroupID int,
LogGroupGUID uniqueidentifier NULL,
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit,
IsPresent bit
)
insert into #backup_file_contents
exec ('restore filelistonly from disk=' + '''' + #backup_file_path + '''')
select #mdf_logical_name = LogicalName from #backup_file_contents where [Type] = 'D'
select #ldf_logical_name = LogicalName from #backup_file_contents where [Type] = 'L'
/* DEBUG */ print #mdf_logical_name + ', ' + #ldf_logical_name
-- step 2c: restore
declare #mdf_file_name varchar(1000),
#ldf_file_name varchar(1000)
set #mdf_file_name = #restore_directory + #db_name + '.mdf'
set #ldf_file_name = #restore_directory + #db_name + '.ldf'
/* DEBUG */ print 'mdf_logical_name = ' + #mdf_logical_name + '|' +
'ldf_logical_name = ' + #ldf_logical_name + '|' +
'db_name = ' + #db_name + '|' +
'backup_file_path = ' + #backup_file_path + '|' +
'restore_directory = ' + #restore_directory + '|' +
'mdf_file_name = ' + #mdf_file_name + '|' +
'ldf_file_name = ' + #ldf_file_name
restore database #db_name from disk = #backup_file_path
with
move #mdf_logical_name to #mdf_file_name,
move #ldf_logical_name to #ldf_file_name
-- step 2d: iterate
set #index = #index + 1
end
close backup_file_cursor
deallocate backup_file_cursor
-- end try
-- begin catch
-- print error_message()
-- rollback transaction
-- return
-- end catch
--
-- commit transaction
end
Does anybody have any ideas why this might be happening?
Another question: is the transaction code useful ? i.e., if there are 2 databases to be restored, will SQL Server undo the restore of one database if the second restore fails?
Essentially, what was happening was that one of the files that needed to be restored had a problem, and the restore process was throwing an error, but the error is not severe enough to abort the proc. That is the reason there is no problem without the try-catch. However, adding the try-catch traps any error with severity greater than 10, and therefore the control flow switches to the catch block which displays the error messages and aborts the proc.
Also, if you are not removing the NULL records from your file list (since its commented out) then with your loop starting at 0 it ends up processing a non-existant file for its last iteration.
Instead of #index=0 instead it should be #index=1
or uncomment out the
delete from #backup_files where file_path IS NULL
Problems I noticed:
Commit Transaction needs to be inside
the BEGIN TRY....END TRY block
Cursor will not get closed or
deallocated if an error is
encountered and control goes to BEGIN
CATCH...END CATCH block
Try this modified code. It will demonstrate that your code is working fine..
ALTER proc usp_restore_databases
(
#source_directory varchar(1000),
#restore_directory varchar(1000)
)
as
begin
declare #number_of_backup_files int
begin transaction
begin try
print 'Entering TRY...'
-- step 0: Initial validation
if(right(#source_directory, 1) <> '\') set #source_directory = #source_directory + '\'
if(right(#restore_directory, 1) <> '\') set #restore_directory = #restore_directory + '\'
-- step 1: Put all the backup files in the specified directory in a table --
declare #backup_files table ( file_path varchar(1000))
declare #dos_command varchar(1000)
set #dos_command = 'dir ' + '"' + #source_directory + '*.bak" /s/b'
/* DEBUG */ print #dos_command
insert into #backup_files(file_path) exec xp_cmdshell #dos_command
--delete from #backup_files where file_path IS NULL
select #number_of_backup_files = count(1) from #backup_files
/* DEBUG */ select * from #backup_files
/* DEBUG */ print #number_of_backup_files
-- step 2: restore each backup file --
declare backup_file_cursor cursor for select file_path from #backup_files
open backup_file_cursor
declare #index int; set #index = 0
while(#index < #number_of_backup_files)
begin
declare #backup_file_path varchar(1000)
fetch next from backup_file_cursor into #backup_file_path
/* DEBUG */ print #backup_file_path
-- step 2a: parse the full backup file name to get the DB file name.
declare #db_name varchar(100)
set #db_name = right(#backup_file_path, charindex('\', reverse(#backup_file_path)) -1) -- still has the .bak extension
/* DEBUG */ print #db_name
set #db_name = left(#db_name, charindex('.', #db_name) -1)
/* DEBUG */ print #db_name
set #db_name = lower(#db_name)
/* DEBUG */ print #db_name
-- step 2b: find out the logical names of the mdf and ldf files
declare #mdf_logical_name varchar(100),
#ldf_logical_name varchar(100)
declare #backup_file_contents table
(
LogicalName nvarchar(128),
PhysicalName nvarchar(260),
[Type] char(1),
FileGroupName nvarchar(128),
[Size] numeric(20,0),
[MaxSize] numeric(20,0),
FileID bigint,
CreateLSN numeric(25,0),
DropLSN numeric(25,0) NULL,
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0) NULL,
ReadWriteLSN numeric(25,0) NULL,
BackupSizeInBytes bigint,
SourceBlockSize int,
FileGroupID int,
LogGroupGUID uniqueidentifier NULL,
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit,
IsPresent bit
)
insert into #backup_file_contents
exec ('restore filelistonly from disk=' + '''' + #backup_file_path + '''')
select #mdf_logical_name = LogicalName from #backup_file_contents where [Type] = 'D'
select #ldf_logical_name = LogicalName from #backup_file_contents where [Type] = 'L'
/* DEBUG */ print #mdf_logical_name + ', ' + #ldf_logical_name
-- step 2c: restore
declare #mdf_file_name varchar(1000),
#ldf_file_name varchar(1000)
set #mdf_file_name = #restore_directory + #db_name + '.mdf'
set #ldf_file_name = #restore_directory + #db_name + '.ldf'
/* DEBUG */ print 'mdf_logical_name = ' + #mdf_logical_name + '|' +
'ldf_logical_name = ' + #ldf_logical_name + '|' +
'db_name = ' + #db_name + '|' +
'backup_file_path = ' + #backup_file_path + '|' +
'restore_directory = ' + #restore_directory + '|' +
'mdf_file_name = ' + #mdf_file_name + '|' +
'ldf_file_name = ' + #ldf_file_name
print #index
-- restore database #db_name from disk = #backup_file_path
-- with
-- move #mdf_logical_name to #mdf_file_name,
-- move #ldf_logical_name to #ldf_file_name
-- step 2d: iterate
set #index = #index + 1
end
close backup_file_cursor
deallocate backup_file_cursor
end try
begin catch
print 'Entering Catch...'
print error_message()
rollback transaction
return
end catch
commit transaction
end
Raj
The actual problem here is that the try and catch only gives you the last error message 3013 "backup terminating abnormally", but does not give you the lower level error for the reason the 3013 error was triggered.
If you execute a backup command such as with an incorrect databasename, you will get 2 errors.
backup database incorrect_database_name to disk = 'drive:\path\filename.bak'
Msg 911, Level 16, State 11, Line 1
Could not locate entry in sysdatabases for database 'incorrect_database_name'. No entry found with that name. Make sure that the name is entered correctly.
Msg 3013, Level 16, State 1, Line 1
BACKUP DATABASE is terminating abnormally.
If you want to know the actual error for why you backup is failing within a try a catch, the stored procedure is masking it.
Now, on to your question.. what I would do is when a restore succeeds, I would immediately delete or move the .bak to a new location, thereby removing it from the directory you stated in your parameter. Upon a failure, your catch statement can contain a GOTO that takes you back to before the BEGIN TRY and starts executing where it left off because it will not recursively detect the files you have moved from the directory.
RUN_AGAIN:
BEGIN TRY
RECURSIVE DIR FOR FILENAMES
RESTORE DATABASE...
ON SUCCEED, DELETE .BAK FILE
END TRY
BEGIN CATCH
ON FAILURE, MOVE .BAK to A SAFE LOCATION FOR LATER ANALYSIS
GOTO RUN_AGAIN
END CATCH
I'm not saying it is pretty, but it will work. You cannot put a GOTO reference within a TRY/CATCH block, so it has to be outside of it.
Anyway, I just thought I would add my thoughts to this even though the question is old, just to help out others in the same situation.
Related
I frequently need to restore database from backup files on my SQL server and I have a configurable script that takes care of that. Basically the script is using RESTORE command and has variables all over to replace database name, bak file folder, etc. However, one issue I face is restoring a database which is split into multiple files, at times the count goes up till 100.
So my question is how do I make the script dynamic so that I can just specify a file count and the script will iterate through all files and restore it. From what I understand the RESTORE command does not allow this flexibility.
One option that I see right now is to dynamically generate the entire RESTORE command and then execute it, but I want to use it only as a last option.
Are there any other options?
Thanks,
I am not sure that I understand you about "specify a file count", but I use some kind of script for implement testing environment for myself. I hope it helps you. Feel free to ask me
declare #fileListTable table
(
LogicalName nvarchar(128),
PhysicalName nvarchar(260),
[Type] char(1),
FileGroupName nvarchar(128),
Size numeric(20,0),
MaxSize numeric(20,0),
FileID bigint,
CreateLSN numeric(25,0),
DropLSN numeric(25,0),
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0),
ReadWriteLSN numeric(25,0),
BackupSizeInBytes bigint,
SourceBlockSize int,
FileGroupID int,
LogGroupGUID uniqueidentifier,
DifferentialBaseLSN numeric(25,0),
DifferentialBaseGUID uniqueidentifier,
IsReadOnl bit,
IsPresent bit,
TDEThumbprint varbinary(32)
)
insert into #fileListTable exec('restore filelistonly from disk = ''C:\Share\BackUp\Reporting\Prod23.bak''') -- hear I get all files from backup
select * from #fileListTable
declare #sctript nvarchar(max)
select
#sctript = 'restore database Prod from disk = ''C:\Share\BackUp\Reporting\Prod23.bak'' WITH FILE = 1,' +
STUFF( (SELECT results.MoveTo + ' , '
from
(
select
'MOVE ''' + LogicalName+ ''' TO ''E:\MobiledbnkDB_Report\' + LogicalName +
case [Type]
when 'D' then '.mdf'
when 'L' then '.ldf'
end + ''' ' as MoveTo
FROM #fileListTable -- hear I mode files to another folder
) as results
FOR XML PATH('')),
1, 0, '')
+ ' NOUNLOAD, STATS = 5'
use master
if exists(SELECT * FROM sys.databases d WITH(NOLOCK) where d.name = 'Prod')
begin
alter database Prod set restricted_user with rollback immediate
drop database Prod
end
print #sctript
exec (#sctript)
I think what you are looking for is a solution I came up with to restore uncompressed LiteSpeed files. Since the amount of files differs based on how many threads litespeed had available at the time of the backup, we never know how many files we will need to include to restore to our reporting environment. I used Vladimir's use of the stuff function and another script to do the following. Replace FILELOCATION with your folder and update your database, data and log file names.
Declare #BackupFolder nvarchar(100) = N'FILELOCATION'
DECLARE #BackupDirectory SYSNAME = #BackupFolder
IF OBJECT_ID('tempdb..#DirTree') IS NOT NULL
DROP TABLE #DirTree
CREATE TABLE #DirTree (
Id int identity(1,1),
SubDirectory nvarchar(255),
Depth smallint,
FileFlag bit,
ParentDirectoryID int
)
INSERT INTO #DirTree (SubDirectory, Depth, FileFlag)
EXEC master..xp_dirtree #BackupDirectory, 10, 1
UPDATE #DirTree
SET ParentDirectoryID = (
SELECT MAX(Id) FROM #DirTree d2
WHERE Depth = d.Depth - 1 AND d2.Id < d.Id
)
FROM #DirTree d
DECLARE
#ID INT,
#BackupFile VARCHAR(MAX),
#Depth TINYINT,
#FileFlag BIT,
#ParentDirectoryID INT,
#wkSubParentDirectoryID INT,
#wkSubDirectory VARCHAR(MAX)
DECLARE #BackupFiles TABLE
(
FileNamePath VARCHAR(MAX),
TransLogFlag BIT,
BackupFile VARCHAR(MAX),
DatabaseName VARCHAR(MAX)
)
DECLARE FileCursor CURSOR LOCAL FORWARD_ONLY FOR
SELECT * FROM #DirTree WHERE FileFlag = 1
OPEN FileCursor
FETCH NEXT FROM FileCursor INTO
#ID,
#BackupFile,
#Depth,
#FileFlag,
#ParentDirectoryID
SET #wkSubParentDirectoryID = #ParentDirectoryID
WHILE ##FETCH_STATUS = 0
BEGIN
--loop to generate path in reverse, starting with backup file then prefixing subfolders in a loop
WHILE #wkSubParentDirectoryID IS NOT NULL
BEGIN
SELECT #wkSubDirectory = SubDirectory, #wkSubParentDirectoryID = ParentDirectoryID
FROM #DirTree
WHERE ID = #wkSubParentDirectoryID
SELECT #BackupFile = #wkSubDirectory + '\' + #BackupFile
END
SELECT #BackupFile = #BackupDirectory + #BackupFile
INSERT INTO #BackupFiles (FileNamePath) VALUES(#BackupFile)
FETCH NEXT FROM FileCursor INTO
#ID,
#BackupFile,
#Depth,
#FileFlag,
#ParentDirectoryID
SET #wkSubParentDirectoryID = #ParentDirectoryID
END
CLOSE FileCursor
DEALLOCATE FileCursor
SELECT #BackupFolder + '\' + SubDirectory from #DirTree
Declare #filecount int = (select count(*) from #DirTree)
declare #sctript nvarchar(max)
select
#sctript = 'RESTORE DATABASE rrrealty FROM ' +
STUFF( (SELECT +',' + results.MoveTo
from
(
select 'DISK = N''' + 'FILELOCATION' + SubDirectory + '''' as MoveTo
FROM #DirTree -- hear I mode files to another folder
) as results
FOR XML PATH('')),
1, 1, '') + ' WITH FILE = 1
MOVE N''file'' TO N''file.mdf'',
MOVE N''file_Log'' TO N''L:\filelog.ldf'',
NOUNLOAD, REPLACE, STATS = 5'
Exec (#sctript)
I have sp in MSSQL server - code below. When I run it from job, or SSMS it runs OK. But I need to run it from VB6 app with ADODB.
My VB6 code:
Dim cmd As New ADODB.Command
cmd.ActiveConnection = CNN
cmd.CommandTimeout = 180
cmd.CommandText = "dbbackup"
cmd.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc
cmd.Execute(, , ADODB.ConnectOptionEnum.adAsyncConnect)
Problem is: When database backup is almost done - about 90+%, cmd.State changes from Executing to Closed and VB6 code continue in executing (to this moment it waits for sp to complete). But there is a lot of code after backup which never run this way(old backup delete,...). I realized that “Last database backup” property on MSSQL database was not set and in table msdb.dbo.backupset there are no rows for my backup. But there si good restorable backup on HDD.
When i stops program for 5 minutes in debug, sp runs properly to end and everything is OK. This backup code is last code in app run and after it ends program closes all connections and exits. I added wait to VB6 code and it helps on some servers, but many other servers still has same problem.
I think main question is why MSSQL server returns control flow to VB6 code and sp is not completed yet.
Thanks
sp code:
PROCEDURE [dbo].[dbBackup]
AS
BEGIN
SET NOCOUNT ON;
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE
If OBJECT_ID('tempdb..#DBName','u') IS NULL
Create Table #DBName
(
ID int identity (1,1) ,
Name varchar(128) not null ,
RetentionPeriod int null,
BackupPath varchar(255) default(''),
DBSize float default(0)
)
If OBJECT_ID('tempdb..#ExistingBackups', 'u') IS NULL
Create Table #ExistingBackups
(
Name varchar(128) ,
ID int identity (1,1)
)
Declare #Path varchar(255)
Declare #sql varchar(1000)
Declare #Name varchar(128)
Declare #RetentionPeriod int
Declare #LastBackupToKeep varchar(8)
Declare #ID int
Declare #MaxID int
Declare #eName varchar(255)
Declare #eMaxID int
Declare #eID int
Declare #eTimeStamp varchar(20)
Declare #errMsg nvarchar(2048)
Declare #errCount int; set #errCount = 0;
Declare #freeSpace bigint
Declare #pageSize float
Declare #dbSize bigint
Declare #procDate datetime
Declare #Sklad char(3)
Declare #backupName as varchar(255)
Select #pageSize = v.low / 1024 From master..spt_values v (noLock) Where v.number = 1 And v.[type] = 'E'
Select Top 1 #sklad = sklad_id From dbo.pohyb (noLock) Where Convert(int, sklad_id) > 500
Set #procDate = GETDATE()
Truncate Table #DBName
Insert Into #DBName (Name, RetentionPeriod, BackupPath)
Select DBName, BackupsToStore, BackupPath
From dbo.databaseBackup (noLock)
Where runBackup = 1
Select #MaxID = max(ID), #ID = 0 From #DBName
While #ID < #MaxID
Begin
Select #ID = min(ID) From #DBName Where ID > #ID
Select #Name = Name, #RetentionPeriod = RetentionPeriod, #Path = BackupPath
From #DBName
Where ID = #ID
If SUBSTRING(#Path, Len(#Path), 1) <> '\' Set #Path = #Path + '\'
Set #sql = 'Update #DBName Set DBSize= (Select Round(Sum(size) *' + CONVERT(varchar, #pageSize) + '/1024, 0) From ' + #Name + '.dbo.sysfiles (noLock)) Where Name = ''' + #Name + ''''
Exec (#sql)
Select #dbSize = DBSize From #DBName
--Exec #freeSpace = dbo.getDiskFreeSpace #drive = #Path
--If #freeSpace > #dbSize
--Begin
Set #eTimeStamp = REPLACE(REPLACE(CONVERT(varchar, #procDate, 113), ' ', '_'), ':', '-')
Set #sql = #Path + #Name + '_' + #eTimeStamp + '.bak'
Set #errMsg = 'OK'
Begin Try
SET #backupName = 'Objednavky backup by job ' + CONVERT(varchar, GETDATE(), 104) + ' ' + CONVERT(varchar, GETDATE(), 108);
Backup Database #Name To Disk = #sql
WITH NAME = #backupName;
-------mazanie backupu begin
Truncate Table #ExistingBackups
Set #sql = 'dir /B /OD ' + #Path + #Name + '_*.bak'
Insert #ExistingBackups Exec master..xp_cmdshell #sql
If Exists (Select 1 From #ExistingBackups Where PATINDEX('%File Not Found%', Name) > 0)
Truncate Table #ExistingBackups
Delete From #ExistingBackups Where Name IS NULL
Select #eID = 0
Select #eMaxID = Max(ID) - #RetentionPeriod From #ExistingBackups
While #eID < #eMaxID
Begin
Select #eID = Min(ID) From #ExistingBackups Where ID > #eID
Select #eName = Name From #ExistingBackups Where ID = #eID
Set #sql = 'del ' + #Path + #eName
Exec master..xp_cmdshell #sql
End
Truncate Table #ExistingBackups
-------mazanie backupu end
End Try
Begin Catch
Set #errMsg = #errMsg + '||' + CONVERT(varchar,ERROR_MESSAGE())
Set #errCount = #errCount + 1;
End Catch
--End
--Else
--Set #errMsg = 'Pln? disk (Vo?n? miesto: ' + CONVERT(varchar, #freeSpace) + ' MB, potrebn? aspo?: ' + CONVERT(varchar, #dbSize) + ' MB)'
Insert Into [dbo].[databaseBackup_log] ([Sklad_id], [DBName], [BackupDate], [Status]) Values (#Sklad, #Name, #procDate, Ltrim(Rtrim(CONVERT(varchar,#errMsg))))
End
Drop Table #DBName
Drop Table #ExistingBackups
IF #errCount > 0 BEGIN
RAISERROR (#errMsg, 16, 2) WITH SETERROR
END
RETURN 0;
END
I want to write a SQL script that will copy a database on the same server. I could do a backup/restore, but I think it might be faster to just "copy" somehow. Does anyone know if this is possible? Is there a way to write a script that will just detach, copy the file on the HD, and then reattach both copies?
#Tony the Lion:
Hi - I experienced some problems using your script, so I came up with a hybrid of your script and this post:
link
USE master;
GO
-- the original database (use 'SET #DB = NULL' to disable backup)
DECLARE #SourceDatabaseName varchar(200)
DECLARE #SourceDatabaseLogicalName varchar(200)
DECLARE #SourceDatabaseLogicalNameForLog varchar(200)
DECLARE #query varchar(2000)
DECLARE #DataFile varchar(2000)
DECLARE #LogFile varchar(2000)
DECLARE #BackupFile varchar(2000)
DECLARE #TargetDatabaseName varchar(200)
DECLARE #TargetDatbaseFolder varchar(2000)
-- ****************************************************************
SET #SourceDatabaseName = '[Source.DB]' -- Name of the source database
SET #SourceDatabaseLogicalName = 'Source_DB' -- Logical name of the DB ( check DB properties / Files tab )
SET #SourceDatabaseLogicalNameForLog = 'Source_DB_log' -- Logical name of the DB ( check DB properties / Files tab )
SET #BackupFile = 'C:\Temp\backup.dat' -- FileName of the backup file
SET #TargetDatabaseName = 'TargetDBName' -- Name of the target database
SET #TargetDatbaseFolder = 'C:\Temp\'
-- ****************************************************************
SET #DataFile = #TargetDatbaseFolder + #TargetDatabaseName + '.mdf';
SET #LogFile = #TargetDatbaseFolder + #TargetDatabaseName + '.ldf';
-- Backup the #SourceDatabase to #BackupFile location
IF #SourceDatabaseName IS NOT NULL
BEGIN
SET #query = 'BACKUP DATABASE ' + #SourceDatabaseName + ' TO DISK = ' + QUOTENAME(#BackupFile,'''')
PRINT 'Executing query : ' + #query;
EXEC (#query)
END
PRINT 'OK!';
-- Drop #TargetDatabaseName if exists
IF EXISTS(SELECT * FROM sysdatabases WHERE name = #TargetDatabaseName)
BEGIN
SET #query = 'DROP DATABASE ' + #TargetDatabaseName
PRINT 'Executing query : ' + #query;
EXEC (#query)
END
PRINT 'OK!'
-- Restore database from #BackupFile into #DataFile and #LogFile
SET #query = 'RESTORE DATABASE ' + #TargetDatabaseName + ' FROM DISK = ' + QUOTENAME(#BackupFile,'''')
SET #query = #query + ' WITH MOVE ' + QUOTENAME(#SourceDatabaseLogicalName,'''') + ' TO ' + QUOTENAME(#DataFile ,'''')
SET #query = #query + ' , MOVE ' + QUOTENAME(#SourceDatabaseLogicalNameForLog,'''') + ' TO ' + QUOTENAME(#LogFile,'''')
PRINT 'Executing query : ' + #query
EXEC (#query)
PRINT 'OK!'
CREATE DATABASE mydatabase_copy AS COPY OF mydatabase;
This will work on an AZURE Database, and will achieve the same end result. Two attached databases - the original and the copied one.
Source for the script that copies a database.
USE master;
DECLARE
#SourceDatabaseName AS SYSNAME = '<SourceDB>',
#TargetDatabaseName AS SYSNAME = '<TargetDB>'
-- ============================================
-- Define path where backup will be saved
-- ============================================
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = #SourceDatabaseName)
RAISERROR ('Variable #SourceDatabaseName is not set correctly !', 20, 1) WITH LOG
DECLARE #SourceBackupFilePath varchar(2000)
SELECT #SourceBackupFilePath = BMF.physical_device_name
FROM
msdb.dbo.backupset B
JOIN msdb.dbo.backupmediafamily BMF ON B.media_set_id = BMF.media_set_id
WHERE B.database_name = #SourceDatabaseName
ORDER BY B.backup_finish_date DESC
SET #SourceBackupFilePath = REPLACE(#SourceBackupFilePath, '.bak', '_clone.bak')
-- ============================================
-- Backup source database
-- ============================================
DECLARE #Sql NVARCHAR(MAX)
SET #Sql = 'BACKUP DATABASE #SourceDatabaseName TO DISK = ''#SourceBackupFilePath'''
SET #Sql = REPLACE(#Sql, '#SourceDatabaseName', #SourceDatabaseName)
SET #Sql = REPLACE(#Sql, '#SourceBackupFilePath', #SourceBackupFilePath)
SELECT 'Performing backup...', #Sql as ExecutedSql
EXEC (#Sql)
-- ============================================
-- Automatically compose database files (.mdf and .ldf) paths
-- ============================================
DECLARE
#LogicalDataFileName as NVARCHAR(MAX)
, #LogicalLogFileName as NVARCHAR(MAX)
, #TargetDataFilePath as NVARCHAR(MAX)
, #TargetLogFilePath as NVARCHAR(MAX)
SELECT
#LogicalDataFileName = name,
#TargetDataFilePath = SUBSTRING(physical_name,1,LEN(physical_name)-CHARINDEX('\',REVERSE(physical_name))) + '\' + #TargetDatabaseName + '.mdf'
FROM sys.master_files
WHERE
database_id = DB_ID(#SourceDatabaseName)
AND type = 0 -- datafile file
SELECT
#LogicalLogFileName = name,
#TargetLogFilePath = SUBSTRING(physical_name,1,LEN(physical_name)-CHARINDEX('\',REVERSE(physical_name))) + '\' + #TargetDatabaseName + '.ldf'
FROM sys.master_files
WHERE
database_id = DB_ID(#SourceDatabaseName)
AND type = 1 -- log file
-- ============================================
-- Restore target database
-- ============================================
IF EXISTS (SELECT 1 FROM sys.databases WHERE name = #TargetDatabaseName)
RAISERROR ('A database with the same name already exists!', 20, 1) WITH LOG
SET #Sql = 'RESTORE DATABASE #TargetDatabaseName
FROM DISK = ''#SourceBackupFilePath''
WITH MOVE ''#LogicalDataFileName'' TO ''#TargetDataFilePath'',
MOVE ''#LogicalLogFileName'' TO ''#TargetLogFilePath'''
SET #Sql = REPLACE(#Sql, '#TargetDatabaseName', #TargetDatabaseName)
SET #Sql = REPLACE(#Sql, '#SourceBackupFilePath', #SourceBackupFilePath)
SET #Sql = REPLACE(#Sql, '#LogicalDataFileName', #LogicalDataFileName)
SET #Sql = REPLACE(#Sql, '#TargetDataFilePath', #TargetDataFilePath)
SET #Sql = REPLACE(#Sql, '#LogicalLogFileName', #LogicalLogFileName)
SET #Sql = REPLACE(#Sql, '#TargetLogFilePath', #TargetLogFilePath)
SELECT 'Restoring...', #Sql as ExecutedSql
EXEC (#Sql)
Try this:
USE master
GO
-- the original database (use 'SET #DB = NULL' to disable backup)
DECLARE #DB varchar(200)
SET #DB = 'PcTopp'
-- the backup filename
DECLARE #BackupFile varchar(2000)
SET #BackupFile = 'c:\pctopp\sqlserver\backup.dat'
-- the new database name
DECLARE #TestDB varchar(200)
SET #TestDB = 'TestDB'
-- the new database files without .mdf/.ldf
DECLARE #RestoreFile varchar(2000)
SET #RestoreFile = 'c:\pctopp\sqlserver\backup'
-- ****************************************************************
-- no change below this line
-- ****************************************************************
DECLARE #query varchar(2000)
DECLARE #DataFile varchar(2000)
SET #DataFile = #RestoreFile + '.mdf'
DECLARE #LogFile varchar(2000)
SET #LogFile = #RestoreFile + '.ldf'
IF #DB IS NOT NULL
BEGIN
SET #query = 'BACKUP DATABASE ' + #DB + ' TO DISK = ' + QUOTENAME(#BackupFile, '''')
EXEC (#query)
END
-- RESTORE FILELISTONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE HEADERONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE LABELONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE VERIFYONLY FROM DISK = 'C:\temp\backup.dat'
IF EXISTS(SELECT * FROM sysdatabases WHERE name = #TestDB)
BEGIN
SET #query = 'DROP DATABASE ' + #TestDB
EXEC (#query)
END
RESTORE HEADERONLY FROM DISK = #BackupFile
DECLARE #File int
SET #File = ##ROWCOUNT
DECLARE #Data varchar(500)
DECLARE #Log varchar(500)
SET #query = 'RESTORE FILELISTONLY FROM DISK = ' + QUOTENAME(#BackupFile , '''')
CREATE TABLE #restoretemp
(
LogicalName varchar(500),
PhysicalName varchar(500),
type varchar(10),
FilegroupName varchar(200),
size int,
maxsize bigint
)
INSERT #restoretemp EXEC (#query)
SELECT #Data = LogicalName FROM #restoretemp WHERE type = 'D'
SELECT #Log = LogicalName FROM #restoretemp WHERE type = 'L'
PRINT #Data
PRINT #Log
TRUNCATE TABLE #restoretemp
DROP TABLE #restoretemp
IF #File > 0
BEGIN
SET #query = 'RESTORE DATABASE ' + #TestDB + ' FROM DISK = ' + QUOTENAME(#BackupFile, '''') +
' WITH MOVE ' + QUOTENAME(#Data, '''') + ' TO ' + QUOTENAME(#DataFile, '''') + ', MOVE ' +
QUOTENAME(#Log, '''') + ' TO ' + QUOTENAME(#LogFile, '''') + ', FILE = ' + CONVERT(varchar, #File)
EXEC (#query)
END
GO
Got it from here
Is there a way to write a script that will just detach, copy the file on the HD, and then reattach both copies?
Yes. For detaching and attaching you can use sp_detach_db and sp_attach_db. For copying the files, you can use xp_cmdshell and xcopy.
Still, I think the backup-and-restore approach is easier, since it does not require you to copy the files.
Here is a version of the code Tony posted which works in SQL Server 2005
USE master
GO
-- the original database (use 'SET #DB = NULL' to disable backup)
DECLARE #DB varchar(200)
SET #DB = 'GMSSDB'
-- the backup filename
DECLARE #BackupFile varchar(2000)
SET #BackupFile = 'c:\temp\backup.dat'
-- the new database name
DECLARE #TestDB varchar(200)
SET #TestDB = 'GMSSDBArchive'
-- the new database files without .mdf/.ldf
DECLARE #RestoreFile varchar(2000)
SET #RestoreFile = 'c:\temp\backup'
-- ****************************************************************
-- no change below this line
-- ****************************************************************
DECLARE #query varchar(2000)
DECLARE #DataFile varchar(2000)
SET #DataFile = #RestoreFile + '.mdf'
DECLARE #LogFile varchar(2000)
SET #LogFile = #RestoreFile + '.ldf'
IF #DB IS NOT NULL
BEGIN
SET #query = 'BACKUP DATABASE ' + #DB + ' TO DISK = ' + QUOTENAME(#BackupFile, '''')
EXEC (#query)
END
-- RESTORE FILELISTONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE HEADERONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE LABELONLY FROM DISK = 'C:\temp\backup.dat'
-- RESTORE VERIFYONLY FROM DISK = 'C:\temp\backup.dat'
IF EXISTS(SELECT * FROM sysdatabases WHERE name = #TestDB)
BEGIN
SET #query = 'DROP DATABASE ' + #TestDB
EXEC (#query)
END
CREATE TABLE #headeronly
(
BackupName nvarchar(128) null,
BackupDescription nvarchar(255) null,
BackupType smallint,
ExpirationDate datetime null,
Compressed bit,
Position smallint,
DeviceType tinyint,
UserName nvarchar(128),
ServerName nvarchar(128),
DatabaseName nvarchar(128),
DatabaseVersion int,
DatabaseCreationDate datetime,
BackupSize numeric(20,0),
FirstLSN numeric(25,0),
LastLSN numeric(25,0),
CheckpointLSN numeric(25,0),
DatabaseBackupLSN numeric(25,0),
BackupStartDate datetime,
BackupFinishDate datetime,
SortOrder smallint,
CodePage smallint,
UnicodeLocaleId int,
UnicodeComparisonStyle int,
CompatibilityLevel tinyint,
SoftwareVendorId int,
SoftwareVersionMajor int,
SoftwareVersionMinor int,
SoftwareVersionBuild int,
MachineName nvarchar(128),
Flags int,
BindingID uniqueidentifier,
RecoveryForkID uniqueidentifier,
Collation nvarchar(128),
FamilyGUID uniqueidentifier,
HasBulkLoggedData bit,
IsSnapshot bit,
IsReadOnly bit,
IsSingleUser bit,
HasBackupChecksums bit,
IsDamaged bit,
BeginsLogChain bit,
HasIncompleteMetaData bit,
IsForceOffline bit,
IsCopyOnly bit,
FirstRecoveryForkID uniqueidentifier,
ForkPointLSN numeric(25,0) NULL,
RecoveryModel nvarchar(60),
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
BackupTypeDescription nvarchar(60),
BackupSetGUID uniqueidentifier NULL
)
--RESTORE HEADERONLY FROM DISK = #BackupFile
SET #query = 'RESTORE HEADERONLY FROM DISK = ' + QUOTENAME(#BackupFile, '''')
INSERT #headeronly exec(#query)
DECLARE #File int
select #File = count(1) from #headeronly
print CONVERT(varchar, #File)
DROP TABLE #headeronly
DECLARE #Data varchar(500)
DECLARE #Log varchar(500)
SET #query = 'RESTORE FILELISTONLY FROM DISK = ' + QUOTENAME(#BackupFile , '''')
--RESTORE FILELISTONLY FROM DISK = 'c:\temp\backup.dat'
CREATE TABLE #restoretemp
(
LogicalName nvarchar(128),
PhysicalName nvarchar(260),
type char(1),
FilegroupName nvarchar(128),
size numeric(20,0),
maxsize numeric(20,0),
FileID bigint,
CreateLSN numeric(25,0),
DropLSN numeric(25,0 )NULL,
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0) NULL,
ReadWriteLSN numeric(25,0) NULL,
BackupSizeInBytes bigint,
SourceBlockSize int,
FileGroupID int,
LogGroupGUID uniqueidentifier NULL,
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit,
IsPresent bit
)
--select * from EXEC (#query)
INSERT #restoretemp EXEC (#query)
SELECT #Data = LogicalName FROM #restoretemp WHERE type = 'D'
SELECT #Log = LogicalName FROM #restoretemp WHERE type = 'L'
PRINT #Data
PRINT #Log
TRUNCATE TABLE #restoretemp
DROP TABLE #restoretemp
print CONVERT(varchar, #File)
IF #File > 0
BEGIN
SET #query = 'RESTORE DATABASE ' + #TestDB + ' FROM DISK = ' + QUOTENAME(#BackupFile, '''') +
' WITH MOVE ' + QUOTENAME(#Data, '''') + ' TO ' + QUOTENAME(#DataFile, '''') + ', MOVE ' +
QUOTENAME(#Log, '''') + ' TO ' + QUOTENAME(#LogFile, '''') + ', FILE = ' + CONVERT(varchar, #File)
print 'starting restore'
EXEC (#query)
print 'finished restore'
END
GO
I'm not sure, but I think you are looking for this:
BACKUP DATABASE MyDB TO DISK='D:\MyDB.bak'
When we copy a database down from production, we make a backup of the database, zip it up and copy the backup down. Then we have to restore using the SQL Server GUI, which involves navigating through several menus and windows. As far as I know, you can not do this with SQL Server's built in stored procedures because you may not know the logical filename of the database (which is required to restore). So doing this via query consists of the following:
RESTORE FILELISTONLY
FROM DISK = 'C:\backup_of_production_database.bak'
GO
The above provides the logical file names from the backup file, you then have to use these logical names in the next query...
RESTORE DATABASE NewDevelopmentDatabase
FROM DISK = 'C:\backup_of_production_database.bak'
WITH MOVE 'YourMDFLogicalName' TO 'C:\mssql\data\DataYourMDFFile.mdf',
MOVE 'YourLDFLogicalName' TO 'C:\mssql\data\DataYourLDFFile.mdf'
As you can see this seems inefficient because you must manually enter the logical file names into the next query.
You can find my solution to this problem as an answer below.
Here is a SQL script that will restore a database with no interaction required.
Just enter your "Source Database" & your "Destination Database" - the script will do the rest :)
SET NOCOUNT ON;
DECLARE
#MySourceDatabase NVarchar(1000),
#MyDestinationDatabase NVarchar(100),
#DeviceFrom NVarchar(1000),
#DeviceTo NVarchar(1000),
#LogicalName NVarchar(1000),
#PhysicalName NVarchar(1000),
#SQL NVarchar(MAX),
#RowsToProcess integer,
#CurrentRow integer,
#Comma NVarchar(25);
--SOURCE DATABASE (DATABASE TO RESTORE)
SET #MySourceDatabase = 'D:\Backups\backup_db.bak';
--DESTINATION DATABASE (DATABASE TO RESTORE TO)
SET #MyDestinationDatabase = 'mydatabase_db';
SELECT #DeviceFrom = SUBSTRING(physical_name, 1,
CHARINDEX(#MyDestinationDatabase + '.mdf',
physical_name) - 1)
FROM master.sys.master_files
WHERE name = #MyDestinationDatabase AND FILE_ID = 1;
SET #SQL = 'RESTORE DATABASE ' + #MyDestinationDatabase + ' FROM DISK = ''' + #MySourceDatabase + ''' WITH ';
SET #CurrentRow = 0;
SET #Comma = ',';
DECLARE #FileList TABLE (
RowID int not null primary key identity(1,1)
,LogicalName NVARCHAR(128)
,PhysicalName NVARCHAR(260)
,Type CHAR(1)
,FileGroupName NVARCHAR(128)
,Size numeric(20,0)
,MaxSize numeric(20,0)
,FileId BIGINT
,CreateLSN numeric(25,0)
,DropLSN numeric(25,0)
,UniqueId uniqueidentifier
,ReadOnlyLSN numeric(25,0)
,ReadWriteLSN numeric(25,0)
,BackupSizeInBytes BIGINT
,SourceBlockSize BIGINT
,FilegroupId BIGINT
,LogGroupGUID uniqueidentifier
,DifferentialBaseLSN numeric(25)
,DifferentialBaseGUID uniqueidentifier
,IsReadOnly BIGINT
,IsPresent BIGINT
,TDEThumbprint VARBINARY(32) -- Remove this line for SQL Server 2005
);
INSERT INTO #FileList
EXEC('RESTORE FILELISTONLY FROM DISK = ''' + #MySourceDatabase + '''')
SET #RowsToProcess = ##RowCount;
WHILE #CurrentRow < #RowsToProcess
BEGIN
SET #CurrentRow= #CurrentRow + 1;
BEGIN
IF #CurrentRow = #RowsToProcess
SET #Comma = ',REPLACE';
END
SELECT #LogicalName = LogicalName,#PhysicalName = PhysicalName FROM #FileList WHERE RowID=#CurrentRow;
SET #PhysicalName = Replace(#PhysicalName,#LogicalName,#MyDestinationDatabase);
SET #SQL = #SQL + 'MOVE ''' + #LogicalName + ''' TO ''' + #PhysicalName + '''' + #Comma + '';
END
--PREVIEW THE GENERATED QUERY
SELECT #SQL;
--EXECUTE THE GENERATED QUERY
--EXEC(#SQL);
It will automatically produce a query like:
RESTORE DATABASE mydatabase_db
FROM DISK = 'D:\Backups\backup_db.bak'
WITH
MOVE 'backup_db'
TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\mydatabase_db.mdf',
MOVE 'backup_db_log'
TO 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\DATA\mydatabase_db.LDF',
REPLACE
The solution:
Using various resources I came up with the below stored procedure that lets you cut down this restore into one step. I hope it proves as useful to others as it has been to myself.
ALTER PROCEDURE [dbo].[sp_makedev]
#backupfile sysname,
#newdatabase sysname
AS
BEGIN
DECLARE #fname VARCHAR(200)
DECLARE #dirfile VARCHAR(300)
DECLARE #LogicalName NVARCHAR(128)
DECLARE #PhysicalName NVARCHAR(260)
DECLARE #type CHAR(1)
DECLARE #sql NVARCHAR(1000)
DECLARE #mdfFilePath varchar(1000)
DECLARE #ldfFilePath varchar(1000)
CREATE TABLE #dbfiles(
LogicalName NVARCHAR(128)
,PhysicalName NVARCHAR(260)
,Type CHAR(1)
,FileGroupName NVARCHAR(128)
,Size numeric(20,0)
,MaxSize numeric(20,0)
,FileId INT
,CreateLSN numeric(25,0)
,DropLSN numeric(25,0)
,UniqueId uniqueidentifier
,ReadOnlyLSN numeric(25,0)
,ReadWriteLSN numeric(25,0)
,BackupSizeInBytes INT
,SourceBlockSize INT
,FilegroupId INT
,LogGroupGUID uniqueidentifier
,DifferentialBaseLSN numeric(25)
,DifferentialBaseGUID uniqueidentifier
,IsReadOnly INT
,IsPresent INT
)
set #mdfFilePath = ''c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\data''
set #ldfFilePath = ''c:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\data''
set #sql = ''RESTORE DATABASE '' + #newdatabase + '' FROM DISK = '''''' + #backupfile + '''''' WITH MOVE ''
DECLARE dbfiles CURSOR FOR
SELECT LogicalName, PhysicalName, [type] FROM #dbfiles
INSERT #dbfiles
EXEC(''RESTORE FILELISTONLY FROM DISK = '''''' + #backupfile + '''''''')
OPEN dbfiles
FETCH NEXT FROM dbfiles INTO #LogicalName, #PhysicalName, #type
WHILE ##FETCH_STATUS = 0
BEGIN
IF #type = ''D''
SET #sql = #sql + '''''''' + #LogicalName + '''''' TO '''''' + #mdfFilePath + ''\'' + #newdatabase + ''.mdf'''', MOVE ''
ELSE IF #type = ''L''
SET #sql = #sql + '''''''' + #LogicalName + '''''' TO '''''' + #ldfFilePath + ''\'' + #newdatabase + ''.ldf''''''
FETCH NEXT FROM dbfiles INTO #LogicalName, #PhysicalName, #type
END
CLOSE dbfiles
DEALLOCATE dbfiles
EXEC(#SQL)
END
I'm sure a few things about this query can be improved, however I already wasted enough time just trying to come to this solution. Regardless I'd love to hear some feedback. I hope that others find this useful!
(Note this question asks about linking ALL Users, unlike the possible duplicate that asks about linking a single user)
I wish to move a database between two servers, I have backed the database up from the first server and done a database restore on the 2nd server, so far so good.
However our application makes use of a lot of database users that are defined in the database. These have to be linked to logins that are defined in the master database. The server I have restored the database to has all the logins defined, however they have different sids.
I am not a T-SQL expert….
I think sp_change_users_login is part of the solution, but I can't find out how to get it to automatically link all users in the restored database to the login of the same name.
The database creation scripts we use for our application create the users and logins, however it does not specify the SID when creating the login, hence this problem. Now if I had a time machine...
(When I Google I get lots of hits, however they are mostly sites that won't let you see the answer without having to register on the site first.)
Yes, you can do that by executing:
EXEC sp_change_users_login 'Auto_Fix' , 'TheUserName';
However if your question was can I fix all users automatically then this won't do that.
I came up with the following. It works great because it shows you:
All the current orphaned users.
Which ones were fixed.
Which ones couldn't be fixed.
Other solutions require you to know the orphaned user name before hand in order to fix.
The following code could run in a sproc that is called after restoring a database to another server.
Script:
EXEC sp_change_users_login 'report'--See all orphaned users in the database.
DECLARE #OrphanedUsers TABLE
(
IndexKey Int IDENTITY(1,1) PRIMARY KEY,
UserName SysName,--nVarChar(128)
UserSID VarBinary(85)
)
INSERT INTO #OrphanedUsers
EXEC sp_change_users_login 'report'
DECLARE #CRLF as nVarChar
SET #CRLF = CHAR(10) + '&' + CHAR(13)--NOTE: Carriage-Return/Line-Feed will only appear in PRINT statements, not SELECT statements.
DECLARE #Sql as nVarChar(MAX)
SET #Sql = N''
DECLARE #IndexKey as Int
SET #IndexKey = 1
DECLARE #MaxIndexKey as Int
SET #MaxIndexKey = (SELECT COUNT(*) FROM #OrphanedUsers)
DECLARE #Count as Int
SET #Count = 0
DECLARE #UsersFixed as nVarChar(MAX)
SET #UsersFixed = N''
DECLARE #UserName as SysName--This is an orphaned Database user.
WHILE (#IndexKey <= #MaxIndexKey)
BEGIN
SET #UserName = (SELECT UserName FROM #OrphanedUsers WHERE IndexKey = #IndexKey)
IF 1 = (SELECT COUNT(*) FROM sys.server_principals WHERE Name = #UserName)--Look for a match in the Server Logins.
BEGIN
SET #Sql = #Sql + 'EXEC sp_change_users_login ''update_one'', [' + #UserName + '], [' + #UserName + ']' + #CRLF
SET #UsersFixed = #UsersFixed + #UserName + ', '
SET #Count = #Count + 1
END
SET #IndexKey = #IndexKey + 1
END
PRINT #Sql
EXEC sp_executesql #Sql
PRINT 'Total fixed: ' + CAST(#Count as VarChar) + '. Users Fixed: ' + #UsersFixed
SELECT ('Total fixed: ' + CAST(#Count as VarChar) + '. Users Fixed: ' + #UsersFixed)[Fixed]
EXEC sp_change_users_login 'report'--See all orphaned users still in the database.
Result:
*Note: The 4 that were not fixed (in my example screenshot above) did not have a corresponding User in the destination Server that the database was restored to.
If:
EXEC sp_change_users_login 'Auto_Fix' , 'TheUserName';
Doest't work, try this:
EXEC sp_change_users_login 'Auto_Fix', 'Username', NULL, 'p#ssword123'
I found it here:
http://dbadiaries.com/using-sp_change_users_login-to-fix-sql-server-orphaned-users
I found the following script from Microsoft KB918992 - run it on the original server and it will create a stored procedure called 'sp_help_revlogin' which generates another script to run on the destination server, creating all user accounts with the same passwords and sids. Worked wonders for our upgrade from SQL2000 to 2008.
USE master
GO
IF OBJECT_ID ('sp_hexadecimal') IS NOT NULL
DROP PROCEDURE sp_hexadecimal
GO
CREATE PROCEDURE sp_hexadecimal
#binvalue varbinary(256),
#hexvalue varchar(256) OUTPUT
AS
DECLARE #charvalue varchar(256)
DECLARE #i int
DECLARE #length int
DECLARE #hexstring char(16)
SELECT #charvalue = '0x'
SELECT #i = 1
SELECT #length = DATALENGTH (#binvalue)
SELECT #hexstring = '0123456789ABCDEF'
WHILE (#i <= #length)
BEGIN
DECLARE #tempint int
DECLARE #firstint int
DECLARE #secondint int
SELECT #tempint = CONVERT(int, SUBSTRING(#binvalue,#i,1))
SELECT #firstint = FLOOR(#tempint/16)
SELECT #secondint = #tempint - (#firstint*16)
SELECT #charvalue = #charvalue +
SUBSTRING(#hexstring, #firstint+1, 1) +
SUBSTRING(#hexstring, #secondint+1, 1)
SELECT #i = #i + 1
END
SELECT #hexvalue = #charvalue
GO
IF OBJECT_ID ('sp_help_revlogin') IS NOT NULL
DROP PROCEDURE sp_help_revlogin
GO
CREATE PROCEDURE sp_help_revlogin #login_name sysname = NULL AS
DECLARE #name sysname
DECLARE #xstatus int
DECLARE #binpwd varbinary (256)
DECLARE #txtpwd sysname
DECLARE #tmpstr varchar (256)
DECLARE #SID_varbinary varbinary(85)
DECLARE #SID_string varchar(256)
IF (#login_name IS NULL)
DECLARE login_curs CURSOR FOR
SELECT sid, name, xstatus, password FROM master..sysxlogins
WHERE srvid IS NULL AND name <> 'sa'
ELSE
DECLARE login_curs CURSOR FOR
SELECT sid, name, xstatus, password FROM master..sysxlogins
WHERE srvid IS NULL AND name = #login_name
OPEN login_curs
FETCH NEXT FROM login_curs INTO #SID_varbinary, #name, #xstatus, #binpwd
IF (##fetch_status = -1)
BEGIN
PRINT 'No login(s) found.'
CLOSE login_curs
DEALLOCATE login_curs
RETURN -1
END
SET #tmpstr = '/* sp_help_revlogin script '
PRINT #tmpstr
SET #tmpstr = '** Generated '
+ CONVERT (varchar, GETDATE()) + ' on ' + ##SERVERNAME + ' */'
PRINT #tmpstr
PRINT ''
PRINT 'DECLARE #pwd sysname'
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
PRINT ''
SET #tmpstr = '-- Login: ' + #name
PRINT #tmpstr
IF (#xstatus & 4) = 4
BEGIN -- NT authenticated account/group
IF (#xstatus & 1) = 1
BEGIN -- NT login is denied access
SET #tmpstr = 'EXEC master..sp_denylogin ''' + #name + ''''
PRINT #tmpstr
END
ELSE BEGIN -- NT login has access
SET #tmpstr = 'EXEC master..sp_grantlogin ''' + #name + ''''
PRINT #tmpstr
END
END
ELSE BEGIN -- SQL Server authentication
IF (#binpwd IS NOT NULL)
BEGIN -- Non-null password
EXEC sp_hexadecimal #binpwd, #txtpwd OUT
IF (#xstatus & 2048) = 2048
SET #tmpstr = 'SET #pwd = CONVERT (varchar(256), ' + #txtpwd + ')'
ELSE
SET #tmpstr = 'SET #pwd = CONVERT (varbinary(256), ' + #txtpwd + ')'
PRINT #tmpstr
EXEC sp_hexadecimal #SID_varbinary,#SID_string OUT
SET #tmpstr = 'EXEC master..sp_addlogin ''' + #name
+ ''', #pwd, #sid = ' + #SID_string + ', #encryptopt = '
END
ELSE BEGIN
-- Null password
EXEC sp_hexadecimal #SID_varbinary,#SID_string OUT
SET #tmpstr = 'EXEC master..sp_addlogin ''' + #name
+ ''', NULL, #sid = ' + #SID_string + ', #encryptopt = '
END
IF (#xstatus & 2048) = 2048
-- login upgraded from 6.5
SET #tmpstr = #tmpstr + '''skip_encryption_old'''
ELSE
SET #tmpstr = #tmpstr + '''skip_encryption'''
PRINT #tmpstr
END
END
FETCH NEXT FROM login_curs INTO #SID_varbinary, #name, #xstatus, #binpwd
END
CLOSE login_curs
DEALLOCATE login_curs
RETURN 0
GO
List of all orphan user details with corresponding DB name
Simple step
EXEC master.sys.sp_MSforeachdb ' USE [?]
Select ''?''
EXEC ?.dbo.sp_change_users_login ''report'' '
I have a nice script that you can use to create logins from database users,which I came across after searching for this issue this script is using a stored procedure. you can find some other useful scripts here also at this url
http://www.sqlserveroptimizer.com/2011/08/how-to-script-logins-from-user-database-in-sql-server-20052008-r2/
USE MyDatabaseName
DECLARE #login nvarchar(50)
DECLARE logins_cursor CURSOR FOR SELECT l.name FROM sys.database_principals u INNER JOIN sys.server_principals l ON u.sid=l.sid
OPEN logins_cursor FETCH NEXT FROM logins_cursor INTO #login
WHILE ##FETCH_STATUS = 0 BEGIN EXEC sp_help_revlogin #login FETCH NEXT FROM logins_cursor INTO #login END
CLOSE logins_cursor DEALLOCATE logins_cursor GO