How can I verify that a file exists in Windows with SQL? - sql

I have a SQL Server running on my Windows Server and, at a specific column of a table, I have the path for a Zip file (which in turn has the source of the data stored in the database). Some of these are not valid (do not match the data in database). I need to make SQL Server verify that these Zip files exist and that they match the column that stores the path and name of the zip file. This way I will delete the wrong file-path column correspondences.

you can use the undocumented proc xp_fileexist will return 1 if it exists and 0 otherwise
SET NOCOUNT ON
DECLARE #iFileExists INT
EXEC master..xp_fileexist 'c:\bla.txt',
#iFileExists OUTPUT
select #iFileExists

You can use xp_fileexist however please note it is undocumented and unsupported.
You can use SQLCLR, however you didn't bother specifying what version of SQL Server you're using, so it may not be relevant - and in any case it is disabled by default, and security policies prevent its use in some places.
You can use a #temp table and xp_cmdshell, however xp_cmdshell is typically disabled for the same reasons as SQLCLR.
/* if you need to enable xp_cmdshell:
exec master..sp_configure 'show adv', 1;
reconfigure with override;
exec master..sp_configure 'xp_cmdshell', 1;
reconfigure with override;
exec master..sp_configure 'show adv', 0;
reconfigure with override;
*/
SET NOCOUNT ON;
DECLARE
#file VARCHAR(1000),
#path VARCHAR(255),
#cmd VARCHAR(2048);
SELECT
#file = 'foo.zip',
#path = 'C:\wherever\';
SELECT #cmd = 'dir /b "' + #path + #file + '"';
CREATE TABLE #x(a VARCHAR(1255));
INSERT #x EXEC master..xp_cmdshell #cmd;
IF EXISTS (SELECT 1 FROM #x WHERE a = #file)
PRINT 'file exists';
ELSE
PRINT 'file does not exist';
DROP TABLE #x;
EDIT based on new requirements. It shows a list of files either in the table or in the database, and indicates whether the file is in only one location or both. It assumes that path + file is <= 900 characters long (merely to be able to use an index on at least one side).
USE tempdb;
GO
CREATE TABLE dbo.files(f VARCHAR(1000));
INSERT dbo.files(f) SELECT 'zip_that_does_not_exist.zip'
UNION ALL SELECT 'c:\path\file_that_does_not_exist.zip'
UNION ALL SELECT 'c:\path\file_that_exists.zip'
UNION ALL SELECT 'zip_that_exists.zip';
DECLARE
#path VARCHAR(255),
#cmd VARCHAR(2048);
SELECT
#path = path_column,
#cmd = 'dir /b "' + path_column + '"'
FROM
dbo.table_that_holds_path;
CREATE TABLE #x(f VARCHAR(900) UNIQUE);
INSERT #x EXEC master..xp_cmdshell #cmd;
DELETE #x WHERE f IS NULL;
UPDATE #x SET f = LOWER(f);
WITH f AS
(
SELECT f = REPLACE(LOWER(f), LOWER(#path), '')
FROM dbo.files
)
SELECT
[file] = COALESCE(x.f, f.f),
[status] = CASE
WHEN x.f IS NULL THEN 'in database, not in folder'
WHEN f.f IS NULL THEN 'in folder, not in database'
ELSE 'in both' END
FROM
f FULL OUTER JOIN #x AS x
ON x.f = f.f;
DROP TABLE #x, dbo.files;

Related

Import multiple text files into SQL Server

I am using SQL Server 2017 Express edition on Microsoft Windows 10. I want to import all text files in a directory into a database at one time. My idea is that that SQL Server loops over the files in that directory and imports them all at the same time. Is there a way that I can achieve this?
Best Regards,
declare #dir varchar(1000) = 'C:\Temp';
declare #command varchar(1000);
declare #files table (id int identity, filename varchar(1000));
set #command = 'dir /b ' + #dir;
insert into #files execute xp_cmdshell #command;
declare commands cursor for
select 'bulk insert <your table name here> from ''' + #dir + '\' + filename + ''' <other options, WITH FORMAT, etc. here>;' from #files;
open commands;
fetch commands into #command;
while (##fetch_status = 0) begin
--print #command;
execute(#command);
fetch commands into #command;
end;
close commands;
deallocate commands;
Modify #dir and the bulk insert command that is being build and you're done.
You may have to enable 'xp_cmdshell', and this could be a problem for your DBA; and using 'execute' is always a potential issue (SQL injection, etc.).
To enable xp_cmdshell:
-- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1;
GO
-- To update the currently configured value for advanced options.
RECONFIGURE;
GO
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1;
GO
-- To update the currently configured value for this feature.
RECONFIGURE;
GO
As noted in another answer, xp_commandshell is problematic. SQL Server 2016+ allows another approach. See example
declare #tbl table(fn varchar(255), depth int,[isfile] int,primary key(fn))
declare #dir varchar(50)='C:\temp\' --'starting dir
insert #tbl
EXEC Master.dbo.xp_DirTree #dir,1,1 --dir, depth (1 - current only), file (0 - dirs only, 1+ - dirs and files)
delete #tbl where [isfile]=0 or fn not like '%.txt' --keep .txt files only
--select * from #tbl
--will insert into this table
create table #fileTbl (id int identity(1,1) primary key,fn varchar(255),txt varchar(max))
declare #fn varchar(255), #query nvarchar(4000)
select top 1 #fn=fn from #tbl
while ##ROWCOUNT>0--number of rows from the last query executed
begin
--dynamic query required to build OPENROWSET
set #query='insert #fileTbl(fn,txt) select '''+#dir+#fn+''',BulkColumn from openrowset(bulk ''c:\temp\'+#fn+''',single_blob) t'
exec sp_executesql #query
delete #tbl where fn=#fn
select top 1 #fn=fn from #tbl --proceed
end
select * from #fileTbl

ALLUSERSPROFILE OF the HOST SQL Server?

I need to write a SQL Server 2008R2 compatible script to create a share. The script will be executed from inside VB6 code but I am pretty sure that's a moot point here.
The following is PSEUDOCODE at the end
CREATE PROCEDURE [dbo].[create_Server_share]
#TheShare VARCHAR(50),
#TheDIR VARCHAR(250) = NULL
AS
BEGIN
IF (#TheDIR IS NULL) -- ALLUSERSPROFILE usually C:\Programdata
SET #TheDIR = ENVREFERENCE('%ALLUSERSPROFILE%')+ '\XYZ'
....
I already see that ENVREFERENCE is NOT available in SQL Server 2008 R2 (which is the oldest version I have to accomodate for our clients)
But I am not married to using ENVREFERENCE either - I just want the HOST MACHINE to give me its environment return for ALLUSERSPROFILE (obviously I should not grab this value from the executing code in the application because I will be getting the CLIENT's value instead of the desired HOST server's value; hence my desire to execute it from the T-SQL script)
So do any SQL guru's have some insight into this?
Thanks in advance.
Harry
Can't say this is completely bulletproof, but I past the first few dozen tests.
Thanks to Jeroen Mostert I realized I had my access to %ALLUSERSPROFILES% already on the Host server. the script then became something I could do...
-- create_Server_share.sql written by Harry Abramowski on 6/26/2018
-- we ARE NOT doing these steps in a command line prompt nor in VB6
-- because this share has to be made **ON** THE SERVER!
-- stored procs are a bitch!
go
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS(SELECT 1
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'create_Server_share'
AND SPECIFIC_SCHEMA = 'dbo')
BEGIN
DROP PROCEDURE create_Server_share
END
go
CREATE PROCEDURE [dbo].[create_Server_share]
#TheShare varchar(50),
#TheDrive char=null,
#TheDIR varchar(250)=null
AS
BEGIN
if (#TheDIR is null)
set #TheDIR = '%ALLUSERSPROFILE%\XYZ'
if (#TheDrive is null)
set #TheDrive = 'X'
DECLARE #answer as varchar(MAX)
declare #myString as varchar(1000)
DECLARE #i INT
-- JUST in case its not already set up, let's enable use of the reconfig in SQL
EXEC sp_configure 'show advanced options', 1; --might not be needed
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1; -- wont hurt to assume it IS needed
RECONFIGURE;
-- net share XYZShare=C:\Programdata\XYZ /grant:everyone,FULL
a_redo:
set #myString = ('net share ' + #TheShare +'=' + #TheDIR + ' /grant:everyone,FULL')
CREATE TABLE #xyzout ([outputtext] varchar(MAX))
Insert into #xyzout (outputtext) EXECUTE xp_cmdshell #myString
-- what about The system cannot find the file specified.???
if exists(select #xyzout.outputtext from #xyzout where #xyzout.outputtext = 'The system cannot find the file specified.')
begin
set #myString = ('mkdir ' + #TheDIR)
EXECUTE xp_cmdshell #mystring
print ('The directory ' + #TheDIR + ' was created')
drop table #xyzout
goto a_redo -- yeah I know!
end
-- was there an error - was it just an "already exists" message? let's see
set #answer = (select top 1 outputtext from #xyzout)
print #answer
-- now update systemProps table so the client machines know there's a share and what drive they should map it to
if charindex('system error',lower(#answer))= 0
IF NOT EXISTS (SELECT a.* FROM syscolumns a, sysobjects b
WHERE a.name = 'XYZShare' AND
a.id = b.id AND
b.name = 'systemProps')
ALTER TABLE system ADD XYZShare NVARCHAR(1000) NULL ;
if charindex('system error',lower(#answer))= 0
begin
update systemProps set XYZShare = (#TheDrive + '=\\' +
CAST(serverproperty('MachineName') as varchar) + '\' + #TheShare );
select systemProps.XYZShare from systemProps;
return 0;
end
else
begin
select * from #xyzout where not(outputtext is null)
return 1;
end
EXEC sp_configure 'xp_cmdshell',0; --let's leave that off?
RECONFIGURE;
DROP TABLE #xyzout
---- if you need to delete the SHARE ITSELF you COULD use this: EXEC XP_CMDSHELL 'net share Xshared /delete'
--HOWEVER you can easily do either from the windows explorer in NETWORK view or My Computer view
END
GRANT EXECUTE ON dbo.create_Server_share TO PUBLIC
GO
Hope this is useful to someone. You guys always come through for me!

How do I resolve this "Access is denied" error which occurs when using xp_cmdshell in Microsoft SQL?

This is my entire routine:
Declare #AttFileType as char(5), #HQCo as int, #FormName as Varchar(15), #KeyID as VarChar(10), #UniqueID as uniqueidentifier, #FilePath as Varchar(100), #StringCommand as Varchar(200)
Declare #AttID as int
DECLARE #cmd as VARCHAR(500)
DECLARE #cmd2 as VARCHAR(500)
CREATE TABLE #tmp(eFileName VARCHAR(100));
INSERT INTO #tmp
EXEC xp_cmdshell 'dir /B C:\Users\*****\Desktop\Test_Images';
Declare #FileName varchar(100)
Set #UniqueID = NewID()
While (Select Count(*) From #tmp where eFileName is not null) > 0
Begin
Select Top 1 #FileName = eFileName From #tmp
Set #FilePath = 'C:\Users\*****\Desktop\Test_Images\' + #FileName
Set #AttID = (Select TOP 1 AttachmentID FROM dbo.bHQAF ORDER BY AttachmentID DESC) + 1
Set #AttFileType = '.jpg'
Insert Into dbo.bHQAF (AttachmentID, AttachmentFileType)
Select #AttID, #AttFileType
SET #cmd = '
Declare #AttID2 as int, #AttFileType2 as char(5), #FilePath2 as Varchar(100)
Set #AttFileType2 = ''.jpg''
Set #AttID2 = (Select TOP 1 AttachmentID FROM dbo.bHQAF ORDER BY AttachmentID DESC)
Update dbo.bHQAF
Set AttachmentData = (SELECT * From OPENROWSET (Bulk ''' + #FilePath + ''', Single_Blob) rs)
Where AttachmentID = #AttID2 and AttachmentFileType = #AttFileType2'
Exec (#cmd)
Set #HQCo = 101
Set #FormName = 'HRCompAssets'
Set #KeyID = 'KeyID=2'
Insert Into dbo.bHQAT (HQCo, AttachmentID, FormName, KeyField, UniqueAttchID)
Select #HQCo, #AttID, #FormName, #KeyID, #UniqueID
Insert Into dbo.bHQAI (AttachmentID, HRCo)
Select #AttID, #HQCo
Update dbo.bHQAT
Set Description = 'TEST3', AddDate = GETDATE(), AddedBy = '****', DocAttchYN = 'N', DocName = 'Database', OrigFileName = #FileName, TableName = 'HRCA'
Where AttachmentID = #AttID and HQCo = #HQCo
Insert Into dbo.bHQAI (AttachmentID, HRCo)
Select #AttID, 101
Update dbo.bHRCA
Set UniqueAttchID = #UniqueID
Where HRCo = 101 and Asset = '00001'
Delete from #tmp Where eFileName = #FileName
End
I have verified that the code works, for loading a single image into the server, without this bit here:
-- Declarations here
CREATE TABLE #tmp(eFileName VARCHAR(100));
INSERT INTO #tmp
EXEC xp_cmdshell 'dir /B C:\Users\*****\Desktop\Test_Images';
While (Select Count(*) From #tmp where eFileName is not null) > 0
Begin
Select Top 1 #FileName = eFileName From #tmp
-- Rest of code here
Delete from #tmp Where eFileName = #FileName
End
But once the while loop and xp_cmdshell statements are added, the file name is returned as "Access is denied".
Any help would be appreciated!
I'm not an expert in SQL, but I've been asked to load about 1000 PDF and JPEG files into the database and a script seemed to be the most logical approach.
When all is said and done, I would like the script to grab each image from the folder and load it into the database.
I'm open to using a different looping method if necessary.
Edit:
I have also tried adding the following to the beginning of the code which didn't resolve the issue:
--Allow for SQL to use cmd shell
EXEC sp_configure 'show advanced options', 1 -- To allow advanced options to be changed.
RECONFIGURE -- To update the currently configured value for advanced options.
EXEC sp_configure 'xp_cmdshell', 1 -- To enable the feature.
RECONFIGURE -- To update the currently configured value for this feature.
I also went into Facets > Surface Area Configuration and made sure xp_cmdshell is enabled / allowed (true). It was also already marked true under Facets > Server Security.
There are a couple of possible issues here.
xp_cmdShell runs on the server. If that machine does not have a folder called C:\Users\*****\Desktop\Test_Images it will not work.
xp_CmdShell runs using the service account. If that account does not have permissions on the target folder it will fail.
xp_CmdShell has to be enabled. From MSDN.
The xp_cmdshell option is a SQL Server server configuration option
that enables system administrators to control whether the xp_cmdshell
extended stored procedure can be executed on a system. By default, the
xp_cmdshell option is disabled on new installations and can be enabled
by using the Policy-Based Management or by running the sp_configure
system stored procedure.
I figured it out!
Thank you #destination-data & #GarethLyons for all the help!
You guys were right, there was an issue with the folder permissions I didn't realize that I would have to go into the folder and manually update the permissions to include "Service". Once I did, everything worked perfectly!
Thank you both again, sorry for the confusion.
For anyone else who is having this issue in the future, do the following first:
1) Go to the folder
2) Right click
3) Select properties
4) Select the Security tab
5) Click Advanced
6) Click Add
7) Click select a principle
8) Enter "Service" and Check Names
9) Select Service and click OK
10) Select the appropriate permissions under "Basic Permissions"
11) Select "Only apply these permissions to the object in this container"
12) Apply the changes and try running xp_cmdshell again

Scripted Restore Using xp_DirTree For Transient Logical BAK File Name SQL Server

Hi I am trying to restore a DB from one server to another where the logical name of the .bak file changes daily with a new timestamp, I have so far found success in determining this name using the following SQL script provided by Jeff Moden here: http://www.sqlservercentral.com/Forums/Topic1200360-391-1.aspx
--===== Create a holding table for the file names
CREATE TABLE #File
(
FileName SYSNAME,
Depth TINYINT,
IsFile TINYINT
)
;
--===== Capture the names in the desired directory
-- (Change "C:\Temp" to the directory of your choice)
INSERT INTO #File
(FileName, Depth, IsFile)
EXEC xp_DirTree '\\filepath\',1,1
;
--===== Find the latest file using the "constant" characters
-- in the file name and the ISO style date.
SELECT TOP 1
FileName
FROM #File
WHERE IsFile = 1
AND FileName LIKE '%.bak' ESCAPE '_'
ORDER BY FileName DESC
;
DROP TABLE #File
My question is now how do I use this as the basis of a scripted restore operation? any help would be very much appreciated!
I have found success by extending the above to cache the directory path and ordered bak files chronologically to determine which to use, then combined the restore operation, with move for logs.
--==CHECK IF DB EXISTS IF IT DOES DROP IT
USE [master]
IF EXISTS(SELECT * FROM sys.databases where name='insert db name')
DROP DATABASE [insert db name]
--==START THE RESTORE PROCESS
DECLARE #FileName varchar(255), #PathToBackup varchar(255), #RestoreFilePath varchar(1000)
DECLARE #Files TABLE (subdirectory varchar(255), depth int, [file] int)
SET NOCOUNT ON
--==SET THE FILEPATH
SET #PathToBackup = '\\insert path to back up'
--insert into memory table using dirtree at a single file level
INSERT INTO #Files
EXEC master.dbo.xp_DirTree #PathToBackup,1,1
SELECT TOP 1
#FileName = [subdirectory]
FROM
#Files
WHERE
-- get where it is a file
[file] = 1
AND
--==FIND THE LOGICAL NAME OF THE BAK FILE FROM THE CHRONILOGICALLY ORDERED LIST
subdirectory LIKE '%.bak'
ORDER BY
-- order descending so newest file will be first by naming convention
subdirectory DESC
IF LEFT(REVERSE(#PathToBackup), 1) != '\'
BEGIN
SET #PathToBackup = #PathToBackup + '\'
END
SET #RestoreFilePath = #PathToBackup + #FileName
--Grab the file path to restore from
SELECT #RestoreFilePath
--BEGIN THE RESTORE TO THE DESIGNATED SERVER
RESTORE DATABASE [insert name of database to restore]
FROM DISK = #RestoreFilePath
WITH
FILE = 1,
--Create transactional log files on target
MOVE 'mdf_file_name' TO 'file_path\file.mdf',
MOVE 'log_file_name' TO 'file_path\file.ldf', REPLACE;
Here is a script that I have partly written and partly collected.
Features include:
Imports exactly one backup file that has any file name with .bak
ending. If there are more files, then imports only one without any
errors.
If no files exist, gives an error.
Kicks users out from the database.
Deletes the backup file
DECLARE #DBName nvarchar(255), #FileName nvarchar(255), #PathToBackup nvarchar(255), #RestoreFilePath nvarchar(1000)
DECLARE #Files TABLE (subdirectory nvarchar(255), depth int, [file] int)
SET XACT_ABORT, NOCOUNT ON
SET #PathToBackup = N'I:\Folder'
-- insert into our memory table using dirtree and a single file level
INSERT INTO #Files
EXEC master.dbo.xp_DirTree #PathToBackup,1,1
SELECT
#FileName = [subdirectory]
FROM
#Files
WHERE
-- get where it is a file
[file] = 1
AND
subdirectory LIKE N'%.bak'
ORDER BY
-- order descending so newest file will be first by naming convention
subdirectory DESC
IF LEFT(REVERSE(#PathToBackup), 1) != N'\'
BEGIN
SET #PathToBackup = #PathToBackup + N'\'
END
SET #RestoreFilePath = #PathToBackup + #FileName
SET #DBName = LEFT(#FileName, LEN(#FileName)-4)
-- SELECT 'Replace AdventureWorks2016CTP3 in this script with #DBName'
SELECT #RestoreFilePath
BEGIN TRY
-- You can try to check if this command works "already":
-- ALTER DATABASE [#DBName] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [AdventureWorks2016CTP3] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
-- You can try to check if this command works "already":
-- RESTORE DATABASE [#DBName]
RESTORE DATABASE [AdventureWorks2016CTP3]
FROM DISK = #RestoreFilePath
WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 10
END TRY
BEGIN CATCH
-- You can try to check if this command works "already":
-- ALTER DATABASE [#DBName] SET MULTI_USER;
ALTER DATABASE [AdventureWorks2016CTP3] SET MULTI_USER;
; THROW
END CATCH
-- You can try to check if this command works "already":
-- ALTER DATABASE [#DBName] SET MULTI_USER;
ALTER DATABASE [AdventureWorks2016CTP3] SET MULTI_USER;
-- This script is especially for the case where you replication from one location to another using backup and restore.
-- Typically you don't need transaction log backups as all changes will be wiped out on next transfer.
-- You can try to check if this command works "already":
-- ALTER DATABASE [#DBName] SET RECOVERY SIMPLE;
ALTER DATABASE [AdventureWorks2016CTP3] SET RECOVERY SIMPLE;
-- Delete file(s)
-- NOTE: This works only if you give deletion permissions as defined in https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/xp-cmdshell-server-configuration-option?view=sql-server-2017
-- EXAMPLE: exec xp_cmdshell 'del "I:\Directory\AdventureWorks2016CTP3___.bak"'
-- exec xp_cmdshell 'del "' + '#PathToBackup + ''\'' + #FileName + ''"''
DECLARE #cmd NVARCHAR(MAX) = 'xp_cmdshell ''del "' + #PathToBackup + #FileName + '"''';
-- SELECT #cmd
EXEC (#cmd)

Generating XML file from SQL Server 2008

I am working on an application where I need to get the SQL response as XML into an XML file (and to store it in some physical location, say c:\xyz.xml).
I am able to generate the XML content using the provisions available in SQL Server as shown below.
SELECT * FROM #Table FOR XML AUTO, ELEMENTS
where: #Table is a table variable.
I want to know how I can store the query output to an XML file from SQL Server itself.
There's one more option - use sqlcmd tool.
Add :XML ON as a first line in your SQL file (let's call it input.sql)
A command like this will do the trick:
sqlcmd -S <your-server> -i input.sql -o output.xml
You need to use xp_cmdshell, and the bcp utility in the following way
EXEC xp_cmdshell 'bcp "SELECT * FROM #Table FOR XML AUTO, ELEMENTS" queryout "C:\table.xml" -c -T'
Hit me back in the comments if you've got any questions or want to know anything more about how this works.
You can't write to the file system from SQL Server itself. At least not that easily. There are three alternatives:
use xp_cmdshell. I would strongly advise against it. By default xp_cmdshell is disabled for security purposes, and to have it enabled it just for this operation opens a way to big security hole in your system.
use the FileSystemObject and the OLE Automation procedures sp_OACreate/sp_OAMethod. See Reading and Writing Files in SQL Server using T-SQL. This, while marginally better than the xp_cmdshell option, it doesn't give a much better security story. The only reason why is better than xp_cmdshell is that is by far less known by hackers. But the OLE Automation procedures option in SQL Server is also disabled by default and enabling this option exposes the very same security problems xp_cmdshell has.
use a CLR procedure. This would be my recommendation. Create an assembly with digital signature, use the assembly signature to allow, through Transact-SQL code signing, EXTERNAL ACCESS, then use the CLR procedure to write the XML into the file system. While this is significantly more complex than the simple xp_cmdshell or OLE Automation options, it is the most controlable and granular one from a security point of view and is the easiest to maintain and get right (is .Net code as opposed to shell scripts). Unfortunately, by default the clr option is also disabled in the server and has to be enabled.
If you press
ctrl + shift + f
you will have selected "Results To File." This can be found in the Query menu on the top bar of Sql Management Studio.
Or put something like this into your sql script
exec xp_cmdshell 'bcp "select * from suppliers" queryout "c:\suppliers.txt" -S server -T'
See this link, there is an issue about whether it is the app's c drive or the sql server's c drive. Have fun sorting that out.
You can create CLR function that create the file, build it into the sql server, and use it from a stored procedure
Another way( I haven't tested it ) - There is a tool bcp
bcp "Select * from dbo..table FOR XML RAW" queryout c:\temp\test.xml -Soc-db -Uuser -Ppassword
This example is from here
Simple SQL Write to File method
DECLARE #xml XML = '<MyXML></MyXMl>'
DECLARE #strXML varchar(max) = convert(varchar(max),#XML)
-- Add white space for readability
SELECT #strxml = replace(#strxml,'</',char(13) + char(10) + '</')
--- Add Declartives, namespaces and xls
Create Table dbo.BCP_OUT(contents varchar(max))
INSERT INTO dbo.bcp_out(contents)
SELECT Convert(varchar(max),#strXML )
EXEC xp_cmdshell N'BCP -S SERVER [database].dbo.bcp_out -T -c -o \\pathto\file.name'
If your xml output is relatively small (<4000 characters), then you can use this SP:
IF EXISTS (SELECT TOP 1 1 FROM sys.objects WHERE object_id = OBJECT_ID('dbo.USP_WRITE_UNICODE_STRING_TO_FILE') AND type = 'P')
BEGIN
DROP PROCEDURE dbo.USP_WRITE_UNICODE_STRING_TO_FILE
END
GO
-- =============================================
-- Description: Writes the specified Unicode string to the specified file.
-- Permissions: This stored procedure uses xp_cmdshell which is disabled by default. To enable it:
-- 1. In Management Studio connect to a component of SQL Server.
-- 2. In Object Explorer, right-click the server, and then click Facets.
-- 3. In the View Facets dialog box, expand the Facet list, and select the Surface Area Configuration.
-- 4. In the Facet properties area, select XPCmdShellEnabled property and set its value to True.
-- 5. Click OK.
-- Example: EXEC dbo.USP_WRITE_UNICODE_STRING_TO_FILE'<root> <a b="c" /> </root>', 'C:\Test.xml', 1;
-- =============================================
CREATE PROCEDURE dbo.USP_WRITE_UNICODE_STRING_TO_FILE
(
#Str NVARCHAR(4000),
#XmlFilePath NVARCHAR(256),
#Debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Str1 NVARCHAR(MAX),
#Cmd NVARCHAR(4000),
#MaxLen int = 4000;
--see: http://technet.microsoft.com/en-us/library/bb490897.aspx
SET #Str1 = REPLACE(REPLACE(REPLACE(#Str, '>', '^>'), '<', '^<'), '"', '^"');
-- '>' Writes the command output to a file
SET #Str1 =N'ECHO ' + #Str1 + N'>"'+ #XmlFilePath + N'"';
IF #Debug = 1
BEGIN
DECLARE #Msg varchar(128) = 'The total lenght is ' + CAST(LEN(#Str1) AS VARCHAR(10)) + ' characters.'
PRINT #Msg;
PRINT #Str1;
END
IF (LEN(#Str1) > #MaxLen)
RAISERROR ('The input string is too long', 11, 0);
ELSE
SET #Cmd = CAST (#Str1 AS NVARCHAR(4000));
EXEC master..xp_cmdshell #Cmd, NO_OUTPUT;
END
GO
--Test 1
DECLARE #Str NVARCHAR(4000);
DECLARE #Xml xml = '<root> <a b="c" /> </root>';
SET #Str = CAST (#Xml AS NVARCHAR(4000));
EXEC dbo.USP_WRITE_UNICODE_STRING_TO_FILE #Str, 'C:\Test.xml', 1;
GO
--Test 2
DECLARE #Str NVARCHAR(4000);
SET #Str = REPLICATE('a', 4000);
EXEC dbo.USP_WRITE_UNICODE_STRING_TO_FILE #Str, 'C:\Test.xml', 1;
GO
If you don't work with Unicode, then you can create another SP: USP_WRITE_NON_UNICODE_STRING_TO_FILE, which will be very similar to the previous one with the following changes:
CREATE PROCEDURE dbo.USP_WRITE_NON_UNICODE_STRING_TO_FILE
(
#Str VARCHAR(8000),
#XmlFilePath NVARCHAR(256),
#Debug BIT = 0
)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Str1 VARCHAR(MAX),
#Cmd VARCHAR(8000),
#MaxLen int = 8000;
...
SET #Cmd = CAST (#Str1 AS VARCHAR(8000));
That SP allows the use of two times longer the input string (<8000 characters).
If your XML is longer than 8000 but less than 1MB you can use sqlcmd utility without :XML ON command. It greatly simplify the usage of the utility because you don't need a separate input_file with :XML ON command included. Here is an example:
DECLARE #Cmd NVARCHAR(4000);
SET #Cmd = N'sqlcmd -S ' + ##SERVERNAME + N' -d ' + DB_NAME() +
N' -Q "SET NOCOUNT ON; DECLARE #Xml xml = ''<root> <a >b</a> </root>''; SELECT CONVERT(NVARCHAR(MAX), #Xml);" -o "C:\Test.xml" -y 0';
PRINT #Cmd;
EXEC master..xp_cmdshell #Cmd, NO_OUTPUT;
You can also use an SP here:
CREATE PROCEDURE dbo.USP_SAMPLE_PROCEDURE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Xml xml;
SET #Xml = (SELECT name, type_desc FROM sys.objects FOR XML PATH('object'), ROOT('sys.objects'));
SELECT CONVERT(NVARCHAR(MAX), #Xml)
END
GO
DECLARE #Cmd NVARCHAR(4000);
SET #Cmd = N'sqlcmd -S ' + ##SERVERNAME + N' -d ' + DB_NAME() +
N' -Q "EXEC dbo.USP_SAMPLE_PROCEDURE;" -o "C:\Test.xml" -y 0';
PRINT #Cmd;
EXEC master..xp_cmdshell #Cmd, NO_OUTPUT;
GO
If your XML is more than 1MB you should use :XML ON command in a separate script and specify it as -i input_file parameter.
I made this SP so I can easily extract data from db or temp table to XML file on file system. It supports where clause.
CREATE PROCEDURE dbo.ExportToXMLFile
#TableName varchar(1000)
, #Where varchar(2000)=''
, #TicketNumber varchar(500)
, #debug bit=0
as
/*
Date:2016-03-27
Author: BojNed
Purpose: Exports data from table to XML file on filesystem.
#TableName = name of table to export.
#Where = optitional, to set #Where Clause. DO NOT ENTER WHERE at beggining of the string
#TicketNumber = To save to folder on filesystem
#Debug = Optitional. To debug this SP.
Examples:
EXEC dbo.ExportToXMLFile '#tt','columnX=2','221',0
EXEC dbo.ExportToXMLFile '[Production].[Product]','','252',1
EXEC dbo.ExportToXMLFile '[dbo].[DatabaseLog]','ColumnZ=55','351',0
EXEC dbo.ExportToXMLFile '[dbo].[DatabaseLog]','','7865',1
*/
begin
if #debug=0
SET NOCOUNT ON
declare #SQL nvarchar(max)
declare #IsTempTable bit
declare #NewTableName varchar(1000)
declare #Xml as XML
if (isnull(#TicketNumber,''))=''
begin
RAISERROR('No ticket number defined',16,1,1)
RETURN
END
--check if table is tmp or variable
if (SELECT SUBSTRING(#TableName,1,1))='#' or (SELECT SUBSTRING(#TableName,1,1))='#'
BEGIN
if #debug=1
PRINT 'Source is TMP table'
set #NewTableName='TMPTBL_'+#TableName
END
ELSE
BEGIN
if #debug=1
PRINT 'Source is db table'
set #NewTableName=replace(#TableName,'.','_')
END
--RemoveSpecialChars
declare #KeepValues varchar(1000)
set #KeepValues = '%[^a-z^0-9^_]%'
WHILE PATINDEX(#KeepValues,#NewTableName)>0
set #NewTableName = STUFF(#NewTableName, PATINDEX(#KeepValues,#NewTableName),1,'')
if #debug=1
PRINT 'Node name for XML Header and filename: '+#NewTableName
if ISNULL(#Where,'')=''
BEGIN
set #SQL= 'SELECT * FROM '+ #TableName+' FOR XML PATH, ROOT ('''+#NewTableName+'''), ELEMENTS'
if #debug=1
PRINT 'NO Where condition'
END
ELSE
BEGIN
set #SQL= 'SELECT * FROM '+ #TableName+' WHERE '+#Where+ ' FOR XML PATH, ROOT ('''+#NewTableName+'''), ELEMENTS'
if #debug=1
PRINT 'With Where condition'
END
--Get XML to tbl
if ISNULL(OBJECT_ID ('tempdb..##TXML'),0)>0
DROP TABLE ##TXML
CREATE TABLE ##TXML (XMLText XML)
set #SQL = ' insert into ##TXML select ('+#SQL+')'
--parse query
declare #testsql nvarchar(max)
declare #result int
set #testsql = N'set parseonly on; ' + #sql
exec #result = sp_executesql #testsql
-- If it worked, execute it
if #result = 0
begin
if #debug=1
PRINT 'Query OK: '+ #SQL
exec sp_executesql #sql
end
else
BEGIN
DECLARE #msg varchar(2000)
set #msg ='Parsing Error on query: ' + #SQL
RAISERROR (#msg,16,1,1)
RETURN
END
DECLARE #Tbl TABLE (id int identity(1,1), Dir varchar(256))
--check if dir exsists
INSERT into #Tbl
EXEC master.dbo.xp_subdirs 'C:\DataCorrectionBackup\'
if (SELECT Count(*) from #Tbl WHERE Dir=#TicketNumber)=0
BEGIN
--create new dir
DECLARE #t varchar(500)
set #t ='C:\DataCorrectionBackup\'+#TicketNumber
EXEC master.sys.xp_create_subdir #t
END
declare #bcp varchar(500)
declare #Filename VARCHAR(255)
set #Filename =convert(varchar(100),GETDATE(),112)+'_'+replace(convert(varchar(100),GETDATE(),114),':','')+'_'+#NewTableName+'.xml'
set #bcp = 'bcp "SELECT XMLText from ##TXML" queryout C:\DataCorrectionBackup\'+#TicketNumber+'\'+#Filename+' -w -T -S'+ ##servername
--save file
if #debug=0
EXEC xp_cmdshell #bcp, NO_OUTPUT
ELSE
BEGIN
EXEC xp_cmdshell #bcp
PRINT #bcp
END
DROP table ##TXML
end
go