How do I use variables in SQL Server backup disk path? - sql-server

The following SQL statement works fine:
DECLARE #database VARCHAR(30) = 'DEMO';
BACKUP LOG #database
TO DISK = N'C:\zbackups\DEMO.trn'
WITH NOFORMAT, NOINIT,
NAME = N'MyDatabase Log Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10;
DBCC SHRINKFILE ('DEMO_log', 500);
This however does not:
DECLARE #database VARCHAR(30) = 'DEMO';
BACKUP LOG #database
TO DISK = 'C:\zbackups\' + #database + '.trn'
WITH NOFORMAT, NOINIT,
NAME = 'MyDatabase Log Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10;
DBCC SHRINKFILE (#database + '_log', 500);
Both the TO DISK directive and the DBCC SHRINKFILE parameters throw an error when I attempt to concatenate.
How do I use a variable in these location? Would also like to append some datetime parameters to the file path but haven't started researching that yet.

The problem is simply that you can't pass an expression there. Instead of:
TO DISK = 'C:\zbackups\' + #database + '.trn'
You need:
DECLARE #fullpath nvarchar(1024);
SET #fullpath = 'C:\zbackups\' + #database + '.trn';
...
TO DISK = #fullpath
...
You can also parameterize the backup name and log file name in a similar way (instead of hard-coding NAME = 'MyDatabase Log Backup'). But to get the shrink operation to run in the right context (if you really, really, really, really need it, that is), and you can rely on every single database being configured in the same way, you're going to have to hard-code the database name somewhere (e.g. in a USE statement), or use dynamic SQL to set the context to the right database, e.g.
DECLARE #database sysname = N'DEMO';
DECLARE #fullpath nvarchar(1024) = N'C:\zbackups\' + #database + '.trn',
#backup_name nvarchar(500) = #database + N' log backup',
#log_name nvarchar(500) = #database _ N'_log';
DECLARE #sql nvarchar(max) = N'BACKUP LOG #db
TO DISK = #fullpath
WITH NOFORMAT, NOINIT, NAME = #backup_name,
SKIP, NOREWIND, NOUNLOAD, STATS = 10;
DBCC SHRINKFILE(#log, 500);';
DECLARE #context nvarchar(1024) = QUOTENAME(#database)
+ N'.sys.sp_executesql';
EXECUTE #context #sql, -- run #sql but in the context of #database
N'#db sysname, #log nvarchar(500),
#fullpath nvarchar(1024), #backup_name nvarchar(500)',
#database, #log_name, #fullpath, #backup_name;

According to https://learn.microsoft.com/en-us/sql/t-sql/statements/backup-transact-sql?view=sql-server-ver16 a variable can be used. I think an expression would be listed if allowed. The same goes for DBCC SHRINKFILE. Try
DECLARE #database VARCHAR(30) = 'DEMO';
DECLARE #target VARCHAR(260) = 'C:\zbackups\' + #database + '.trn';
DECLARE #logical_name VARCHAR(128) = #database + '_log';
BACKUP LOG #database
TO DISK = #target
WITH NOFORMAT, NOINIT,
NAME = 'MyDatabase Log Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10;
DBCC SHRINKFILE (#logical_name, 500);
If the logical file name does not always follow the dbname_log pattern, perhaps lookup the file id and use that instead of the logical name in SHRINKFILE. Be careful of database scope on the connection.
DECLARE #file_id int
SELECT #file_id = file_id
FROM sys.database_files
WHERE physical_name = #target

Related

Passing a file path as a variable in SQL Server

I'm trying to add a file to a file group in order to create a partition in SQL Server. When I pass in a hardcoded file path to the the filename, the code works. But when I use a variable for the file path, I get an error
Incorrect syntax near #FilePath or Unexpected symbol #FilePath
This is my code:
USE StudentRepository
BEGIN
SET NOCOUNT ON;
DECLARE #FilePath NVARCHAR(MAX) = "C:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLEXPRESS\MSSQL\DATA\",
#FileName NVARCHAR(MAX) = "FirstTerm2020",
#FileExt NVARCHAR(MAX) = ".NDF";
DECLARE #count INT = 0;
ALTER DATABASE StudentRepository
ADD FILE
(
NAME = 'FirstTerm2020',
FILENAME = #FilePath + #FileName + #FileExt, --Error here!
SIZE = 5MB,
MAXSIZE = 100MB,
FILEGROWTH = 5MB
)
TO FILEGROUP FirstTerm2020
END
When I pass a hardcoded file path to the filename, it works and creates the required partitions. as below
--Code omitted for brevity
FILENAME = "C:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLEXPRESS\MSSQL\DATA\FirstTerm2020.NDF" --No error!
Please I need some help. How do I pass a file path as a variable?
This is how I solved the problem. I create a dynamic sql and passed the filepath variable like this.
USE StudentRepository
BEGIN
SET NOCOUNT ON;
DECLARE #FilePath NVARCHAR(MAX) = 'C:\Program Files\Microsoft SQL Server\MSSQL14.MSSQLEXPRESS\MSSQL\DATA\',
#FileName NVARCHAR(MAX) = 'SecondTerm2022',
#FileExt NVARCHAR(MAX) = '.NDF';
DECLARE #count INT = 0;
DECLARE #sql NVARCHAR(MAX) = '
ALTER DATABASE StudentRepository ADD FILEGROUP ' + #FileName + ';
ALTER DATABASE StudentRepository
ADD FILE
(
NAME = '+ #FileName + ',
FILENAME = ''' + #FilePath + #FileName + #FileExt + ''',
SIZE = 5MB,
MAXSIZE = 100MB,
FILEGROWTH = 5MB
)
TO FILEGROUP '+ #FileName
END
EXEC sp_executesql #sql
My major problem was that I was ommitting the three single quotes near #FilePath

Is there a function to compress 'bak' file in sql script?

In SQL Server, I want to clear personal info and backup it
Backup original DB
Restore as another DB name
Clear personal info in another DB
Backup another DB
Delete rest files
Zip Backup DB
I finished 1~5. but couldn't find a way to do 6.
I Want to compress bak file to zip here.
For instance, below code can be used in Powershell script. Is there a way to use this .Net function in SQL script?
[System.IO.Compression.ZipFile]::CreateFromDirectory($CurrentPath, $DeployHistoryFilePath)
Below is my full script.
DECLARE #DBName NVARCHAR(MAX) = N'TestDB'
DECLARE #BackupPath NVARCHAR(MAX) = N'D:\Database\Backup'
EXEC ('master.dbo.xp_create_subdir N'''+ #BackupPath +'''')
DECLARE #BackupName NVARCHAR(MAX) = N'OnCube_' + REPLACE(REPLACE(REPLACE(CONVERT(NVARCHAR(MAX), GETDATE(), 120), N'-', N''), N':', N''), N' ', N'_')
DECLARE #DiskFile NVARCHAR(MAX) = #BackupPath + N'\' + #BackupName + N'.bak'
BACKUP DATABASE #DBName TO DISK = #DiskFile
DECLARE #SQL NVARCHAR(MAX) = 'SELECT TOP (1) #OriginalMdf = name FROM ' + #DBName + '.sys.database_files WHERE file_id = 1'
DECLARE #OriginalMdf NVARCHAR(MAX)
EXEC sp_executesql #SQL, N'#OriginalMdf NVARCHAR(MAX) OUT', #OriginalMdf out
SET #SQL = 'SELECT TOP (1) #OriginalLdf = name FROM ' + #DBName + '.sys.database_files WHERE file_id = 2'
DECLARE #OriginalLdf NVARCHAR(MAX)
EXEC sp_executesql #SQL, N'#OriginalLdf NVARCHAR(MAX) OUT', #OriginalLdf out
DECLARE #PartialMdf NVARCHAR(MAX) = #BackupPath + N'\' + #BackupName + N'.mdf'
DECLARE #PartialLdf NVARCHAR(MAX) = #BackupPath + N'\' + #BackupName + N'_0.ldf'
RESTORE FILELISTONLY FROM DISK = #DiskFile
RESTORE DATABASE #BackupName
FROM DISK = #DiskFile
WITH MOVE #OriginalMdf TO #PartialMdf,
MOVE #OriginalLdf TO #PartialLdf
EXEC (N'
USE [' + #BackupName + ']
UPDATE Person
SET
PatientNo = NULL
, PatientName = N''Cleared'' + CONVERT(NVARCHAR(MAX), RawID)
, RoomNo = NULL
, BedNo = NULL
, Birthday = NULL
, Sex = NULL
, Address = NULL
, AdmitDate = NULL
, AdmitNo = NULL
, Description = NULL
, DischargedDate = NULL
')
DECLARE #ClearedDiskFile NVARCHAR(MAX) = #BackupPath + N'\' + #BackupName + N'_PatientInfoCleared.bak'
BACKUP DATABASE #BackupName TO DISK = #ClearedDiskFile
EXEC('DROP DATABASE [' + #BackupName + ']')
EXEC ('xp_cmdshell ''del "' + #DiskFile + '"''')
-- I Want to compress bak file to zip here
-- For instance, below code can be used in Powershell script. Is there a way to use this .Net function in SQL script?
-- [System.IO.Compression.ZipFile]::CreateFromDirectory($CurrentPath, $DeployHistoryFilePath)
PRINT N'Success to make ' + #ClearedDiskFile + '. Patient informations are all cleared'
Is there a way to use this .Net function in SQL script?
Yes, you can use SQL CLR with C#
see samples Using 7-zip and SharpZipLib here:
also , you can create zip file from SQL without Powershell script:
Create zip file from SQL Server

Backup a SQL Server database with current date

I'm trying to backup databases using the current date time to create a unique filename.
I keep getting incorrect syntax on the '+' before #TimeDate
My Code:
ALTER PROCEDURE [dbo].[BackUpDatabase]
(#DatabaseName Varchar(256))
AS
Begin
DECLARE #TimeDate Varchar(256);
SET #TimeDate = CONVERT(Varchar(256),GETDATE(), 113);
BACKUP DATABASE #DatabaseName
TO DISK = 'C:\SQLEXPRESSBACKUP\DB'+ #TimeDate + '.bak'
WITH NOFORMAT, INIT, NAME= #DatabaseName, SKIP, NOREWIND, NOUNLOAD, STATS=10
END
And I am unable to figure what to search on, to know what I am doing wrong ?
First of all, look at what your #TimeDate value generates:
DECLARE #TimeDate Varchar(256);
SET #TimeDate = CONVERT(Varchar(256),GETDATE(), 113);
SELECT #TimeDate
Produces:
26 Sep 2017 11:49:07:650
This wouldn't be an acceptable file name when appended to your string. So you need to cleanse this to remove : characters.
SET #TimeDate = REPLACE(CONVERT(Varchar(256),GETDATE(), 126), ':','');
Secondly, it doesn't like you concatenating a string here so use a variable instead that you can prepare before:
TO DISK = 'C:\SQLEXPRESSBACKUP\DB'+ #TimeDate + '.bak'
Change to:
DECLARE #location VARCHAR(100) = 'C:\SQLEXPRESSBACKUP\DB' + #timedate + '.bak'
Full soution:
DECLARE #TimeDate Varchar(256);
SET #TimeDate = REPLACE(CONVERT(Varchar(256),GETDATE(), 126), ':','');
DECLARE #location VARCHAR(100) = 'C:\SQLEXPRESSBACKUP\DB' + #timedate + '.bak'
BACKUP DATABASE #DatabaseName
TO DISK = #location
WITH NOFORMAT, INIT, NAME= #DatabaseName, SKIP, NOREWIND, NOUNLOAD, STATS=10
You can declare a variable #ExecuteQuery and then assign this variable value to TO DISK.
Like this.
DECLARE #ExecuteQuery Varchar(1000);
SET #ExecuteQuery = 'C:\SQLEXPRESSBACKUP\DB' + CONVERT(Varchar(256),GETDATE(), 113) + '.bak'
BACKUP DATABASE #DatabaseName
TO DISK = #ExecuteQuery
WITH NOFORMAT, INIT, NAME= #DatabaseName, SKIP, NOREWIND, NOUNLOAD, STATS=10
You'll need to use Dynamic SQL to build the query rather than trying to add in the variables:
ALTER PROCEDURE [dbo].[BackUpDatabase](#DatabaseName VARCHAR(256))
AS
BEGIN
DECLARE #TimeDate VARCHAR(256);
SET #TimeDate = CONVERT(VARCHAR(256), GETDATE(), 113);
DECLARE #sql VARCHAR(MAX);
SET #sql = '
BACKUP DATABASE ['+#DatabaseName+']
TO DISK = ''C:\SQLEXPRESSBACKUP\DB'+#TimeDate+'.bak''
WITH NOFORMAT, INIT, NAME= '''+#DatabaseName+''', SKIP, NOREWIND, NOUNLOAD, STATS=10';
PRINT #sql;
--exec (#sql)
END;
The PRINT #SQL will show you what is going to be run, if you are happy with it then remove that and uncomment out exec (#sql)

Database Restore script says Cannot open backup device when path passed to FROM DISK using variable

I have the following script to restore a database. On the line that says:
FROM DISK = #backup_location --N'C:\Temp\TempDB.bak'
If I use the #backup_location variable as above, it fails with the error:
Msg 3201, Level 16, State 2, Line 24 Cannot open backup device
'C:\Program Files\Microsoft SQL
Server\MSSQL12.SQLEXPRESS\MSSQL\Backup\C'. Operating system error
2(The system cannot find the file specified.). Msg 3013, Level 16,
State 1, Line 24 RESTORE DATABASE is terminating abnormally.
If I hard code the location using:
FROM DISK = N'C:\Temp\TempDB.bak'
It works fine.
How can I use a variable to specify the database path?
USE [master]
Go
-- DECLARE VARIABLES:
DECLARE #dbname SYSNAME
DECLARE #backup_location VARCHAR
SET #dbname = 'TempDB';
SET #backup_location = N'C:\Temp\TempDB.bak';
-- BEGIN: KILL ACTIVE CONNECTIONS
Declare #spid int
Select #spid = min(spid) from master.dbo.sysprocesses
where dbid = db_id(#dbname)
While #spid Is Not Null
Begin
Execute ('Kill ' + #spid)
Select #spid = min(spid) from master.dbo.sysprocesses
where dbid = db_id(#dbname) and spid > #spid
End
-- END: KILL ACTIVE CONNECTIONS
-- RESTORE DB:
RESTORE DATABASE #dbname
FROM DISK = #backup_location --N'C:\Temp\TempDB.bak'
WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 5
Go
Thanks for your time.
Change the last few lines:
-- RESTORE DB:
declare #statement varchar(max)
--allow for quotation marks in #backup_location
select #statement =
'RESTORE DATABASE ' + #dbname +
'FROM DISK = ''' + replace(#backup_location, '''', '''''') +
'WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 5'
exec (#statement)
#codenoir, thanks for the response. Once I saw your varchar(max) I changed #backup_location variable declaration to the same and it worked without the need for exec.
So final code was:
USE [master]
GO
-- DECLARE VARIABLES:
DECLARE #dbname SYSNAME
DECLARE #backup_location VARCHAR(MAX)
SET #dbname = 'TestDB';
SET #backup_location = N'C:\Temp\TestDB.bak';
-- BEGIN: KILL ACTIVE CONNECTIONS
DECLARE #spid INT
SELECT #spid = MIN(spid) FROM master.dbo.sysprocesses
WHERE dbid = DB_ID(#dbname)
WHILE #spid Is Not Null
BEGIN
EXECUTE ('Kill ' + #spid)
SELECT #spid = MIN(spid) FROM master.dbo.sysprocesses
WHERE dbid = db_id(#dbname) and spid > #spid
END
-- END: KILL ACTIVE CONNECTIONS
-- RESTORE DB:
RESTORE DATABASE #dbname
FROM DISK = #backup_location
WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 5
Go

How to create daily backup with unique name in sql server

I want to make full database backup of my server's all databases with unique name daily. For that I have an idea to keep timestamp which will make database copy separate.
Suppose there is a database on server named ABCD then it should be backuped like:
ABCD_21_03_2013
ABCD_22_03_2013
How can I do this. I don't know much about these types of SQL Backup JOBS.
To create a daily backup with a name such as Filename_MM_DD_YYYY:
In SSMS, right-click the database you want to backup
Select Tasks | Back Up
In the dialog, select the type and location of the backup
At the top of this dialog, select the Script Action to Job option in the Script drop down menu
A New Job dialog is opened and the first step creates a database backup
Go to the fist step and click Edit
Existing code looks like:
BACKUP DATABASE [AdventureWorks2012] TO DISK = N'E:\Test\AdventureWorks.bak' WITH NOFORMAT, NOINIT, NAME = N'AdventureWorks2012-Full Database Backup', SKIP, NOREWIND, NOUNLOAD, STATS = 10
GO
Replace it to be
DECLARE #SQLStatement VARCHAR(2000)
SET #SQLStatement = 'E:\Test\AdventureWorks_' + CONVERT(nvarchar(30), GETDATE(), 110) +'.bak'
BACKUP DATABASE [AdventureWorks2012] TO DISK = #SQLStatement
6 Save the job
The database backups will be named:
AdventureWorks_07-29-2013
AdventureWorks_07-30-2013
AdventureWorks_07-31-2013
Now all you have to do is make the right schedule
Automatic backup of all databases on the server.
About Jobs:
http://msdn.microsoft.com/en-us/library/ms190268.aspx
Query:
SET NOCOUNT ON;
DECLARE
#FileName NVARCHAR(1024)
, #DBName NVARCHAR(256)
, #PathName NVARCHAR(256)
, #Message NVARCHAR(2048)
, #IsCompressed BIT
SELECT
#PathName = 'D:\BACKUP\'
, #IsCompressed = 1
DECLARE db CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
sd.name
, file_path = #PathName + FileDate + '_' + name + '.bak'
FROM sys.databases sd
CROSS JOIN (
SELECT FileDate = 'ABCD_' + REPLACE(CONVERT(VARCHAR(10), GETDATE(), 103), '/', '_')
) fd
WHERE sd.state_desc != 'OFFLINE'
AND sd.name NOT IN ('master', 'model', 'msdb', 'tempdb')
ORDER BY sd.name
OPEN db
FETCH NEXT FROM db INTO
#DBName
, #FileName
WHILE ##FETCH_STATUS = 0 BEGIN
DECLARE #SQL NVARCHAR(MAX)
SELECT #Message = REPLICATE('-', 80) + CHAR(13) + CONVERT(VARCHAR(20), GETDATE(), 120) + N': ' + #DBName
RAISERROR (#Message, 0, 1) WITH NOWAIT
SELECT #SQL =
'BACKUP DATABASE [' + #DBName + ']
TO DISK = N''' + #FileName + '''
WITH FORMAT, ' + CASE WHEN #IsCompressed = 1 THEN N'COMPRESSION, ' ELSE '' END + N'INIT, STATS = 15;'
EXEC sys.sp_executesql #SQL
FETCH NEXT FROM db INTO
#DBName
, #FileName
END
CLOSE db
DEALLOCATE db
Output:
BACKUP DATABASE [AdventureWorks2008R2]
TO DISK = N'D:\BACKUP\ABCD_24_05_2013_AdventureWorks2008R2.bak'
WITH FORMAT, COMPRESSION, INIT, STATS = 15;
BACKUP DATABASE [AdventureWorks2008R2_Live]
TO DISK = N'D:\BACKUP\ABCD_24_05_2013_AdventureWorks2008R2_Live.bak'
WITH FORMAT, COMPRESSION, INIT, STATS = 15;
BACKUP DATABASE [AdventureWorks2012]
TO DISK = N'D:\BACKUP\ABCD_24_05_2013_AdventureWorks2012.bak'
WITH FORMAT, COMPRESSION, INIT, STATS = 15;
Results:
2013-05-24 09:54:34: AdventureWorks2008R2
15 percent processed.
30 percent processed.
45 percent processed.
60 percent processed.
75 percent processed.
90 percent processed.
Processed 23416 pages for database 'AdventureWorks2008R2', file 'AdventureWorks2008R2_Data' on file 1.
Processed 1 pages for database 'AdventureWorks2008R2', file 'AdventureWorks2008R2_Log' on file 1.
BACKUP DATABASE successfully processed 23417 pages in 4.052 seconds (45.148 MB/sec).
.....
I think, best way to perform schedule back-up is to create Job. Add your back-up job and schedule that on particular date and time.
How to create job
Some third-party backup programs, for example: EMS SQL Backup, allow setting templates for backup file names. Timestamp, server instance name, database names and other info can be added to file name.
Thanks for the posts
I just want to share a small update I have made on the script to make log database backups and automatically skip all databases with recovery model = simple which does not allow log backups. Hope it helps...
And yes, Mr. Ravi is right the best approach is to create a job, I've created a SP and am running from a job.
CREATE PROCEDURE sp_logbackup
AS
SET NOCOUNT ON;
DECLARE
#FileName NVARCHAR(1024)
, #DBName NVARCHAR(256)
, #PathName NVARCHAR(256)
, #Message NVARCHAR(2048)
, #IsCompressed BIT
SELECT
#PathName = '\\myServer\...'
, #IsCompressed = 1
DECLARE db CURSOR LOCAL READ_ONLY FAST_FORWARD FOR
SELECT
sd.name
, file_path = #PathName + name + '_' + FileDate + '.trn'
FROM sys.databases sd
CROSS JOIN (
SELECT FileDate = REPLACE(REPLACE(REPLACE(CONVERT(varchar,GETDATE(), 20),'-','_'),':',''),' ','')
) fd
WHERE sd.state_desc != 'OFFLINE'
AND sd.recovery_model != 3
AND sd.name NOT IN ('master', 'model', 'msdb', 'tempdb')
ORDER BY sd.name
OPEN db
FETCH NEXT FROM db INTO
#DBName
, #FileName
WHILE ##FETCH_STATUS = 0 BEGIN
DECLARE #SQL NVARCHAR(MAX)
SELECT #Message = REPLICATE('-', 80) + CHAR(13) + CONVERT(VARCHAR(20), GETDATE(), 120) + N': ' + #DBName
RAISERROR (#Message, 0, 1) WITH NOWAIT
SELECT #SQL =
'BACKUP LOG [' + #DBName + ']
TO DISK = N''' + #FileName + '''
WITH FORMAT, NAME = N''' + #DBName + ''', SKIP, REWIND, NOUNLOAD, STATS = 10;'
EXEC sys.sp_executesql #SQL
FETCH NEXT FROM db INTO
#DBName
, #FileName
END
CLOSE db
DEALLOCATE db

Resources