You can do this from the command line using the bcp utility:
You will need to use a format file, copy and paste the text below and save to an ANSI/ASCII (non-Unicode) txt file (e.g. excel.fmt)
9.0
1
1 SQLBINARY 0 0 "" 1 DataFile ""
You can then do the following using bcp (must all be on a single line):
bcp "SELECT DataFile FROM myTable WHERE IDDocument=1" queryout "U:\SAP_R3V4_Validation_Documents\March2012.xls" -S myServer -T -f excel.fmt
You would need to do some additional programming to dynamically assign the filename to each Excel file. If that's the case let me know, I'll post a solution here.
Yes, I should iterate through every Id in the table, and create the Excel with the name in the FileName column.
Then I can save them where I want, that's not a problem.
The important thing is to have the Excel files somewhere.
Maybe an aspx page, or a C# Console Application could be fine.
A C# console app could also work, but bcp is installed by default on your SQL Server, and can be downloaded and installed from the link I provided. It's much simpler than writing additional code.
If you go that route, or use an ASPX page, you need to ensure that you get the raw bytes from the image column and write them out exactly as-is. bcp by default will include some metadata, that's why the format file is required. I'm rusty on C# but I believe the StreamReader class will provide what you need.
Using the BCP road, how can I iterate for each Id?
I have 600 rows in the table, so I should restore 600 Excel files.
The DB name is:
SapProfile
and the server is localhost.
This bcp will create a text file of IDs and file names:
bcp "select IdDocument,FileName FROM SapProfile.dbo.myTable ORDER BY 1" queryout docs.txt -S localhost -T -c -t,
The docs.txt file will have both columns separated by a comma.
This next command will read that file and parse each column based on a comma delimiter, assigning the ID to variable %a and the file name to %b. It will then construct a bcp command to query for the correct document and write it to the correct file name:
for /F "tokens=1-2 delims=," %a in (docs.txt) do bcp "SELECT DataFile FROM SapProfile.dbo.myTable WHERE IDDocument=%a" queryout "%b" -S localhost -T -f excel.fmt
If you need to change the file path, you can use the following to extract just the file name:
bcp "select IdDocument,reverse(substring(reverse(FileName),1,charindex('\',reverse(FileName))-1)) FROM SapProfile.dbo.myTable ORDER BY 1" queryout docs.txt -S localhost -T -c -t,
You can then embed a fixed path in the subsequent command:
for /F "tokens=1-2 delims=," %a in (docs.txt) do bcp "SELECT DataFile FROM SapProfile.dbo.myTable WHERE IDDocument=%a" queryout "D:\MyNewPath\%b" -S localhost -T -f excel.fmt
Now I've added my login to the server, but I got this error:
Starting copy...
SQLState = 37000, NativeError = 537
Error = [Microsoft][SQL Server Native Client 11.0][SQL Server]Invalid length parameter passed to the LEFT or SUBSTRING
function.
This one works:
bcp "select IdDocumento,Nomefile FROM DBGestioneProfiliSAP.dbo.tblDocumento ORDER BY 1" queryout D:\SAPdocs.txt -S XSPW11P491F -U sanapp -P SQL01 -T -c -t
and I get a txt file like this:
9 U:\SAP_R3V4_Validation_Documents\Revisione 2012\primo trimestre 2012\Arena profili semplici marzo 2012.xls
10 U:\SAP_R3V4_Validation_Documents\Revisione 2012\primo trimestre 2012\Bongiorni profili semplici marzo 2012.xls
The columns are separated by TAB, not by comma.
Is a problem?
If I manually change the paths in this file, could be enough all the same?
And how can I run the
for.....
statement in Command console?
Regarding the "separated by tabs", you need to have
-t,
... with the trailing comma at the end of the bcp command, otherwise it will default to tab as column separator.
Regarding the SUBSTRING error, this should avoid it:
bcp "select IdDocument,reverse(substring(reverse(FileName)+'\',1,charindex('\',reverse(FileName)+'\')-1)) FROM SapProfile.dbo.myTable ORDER BY 1" queryout docs.txt -S localhost -T -c -t,
My fault on that last one, I hadn't considered file names without a path.
Running the "for" command is done in any Windows command console, but NOT in a PowerShell command console. I DO NOT recommend trying to run these via xp_cmdshell either. There will be Start menu items for "Command Prompt" under "Windows System" or "Windows Administrative Tools" if they don't appear elsewhere.
If you can't find them on the Start menu, choose the "Run" option and enter "cmd.exe" without quotes.
Regarding the "separated by tabs", you need to have
-t,
... with the trailing comma at the end of the bcp command, otherwise it will default to tab as column separator.
**** Yes, I've lost the comma, sorry for that (default behaviour is TAB, right). *******
Regarding the SUBSTRING error, this should avoid it:
bcp "select IdDocument,reverse(substring(reverse(FileName)+'',1,charindex('',reverse(FileName)+'')-1)) FROM SapProfile.dbo.myTable ORDER BY 1" queryout docs.txt -S localhost -T -c -t,
My fault on that last one, I hadn't considered file names without a path.
***** I'll try tomorrow. I'll let you know. ****
Running the "for" command is done in any Windows command console, but NOT in a PowerShell command console. I DO NOT recommend trying to run these via xp_cmdshell either. There will be Start menu items for "Command Prompt" under "Windows System" or "Windows Administrative Tools" if they don't appear elsewhere.
If you can't find them on the Start menu, choose the "Run" option and enter "cmd.exe" without quotes.
*** For sure I'd prefer not use PowerShell. I've just seen the option about using xp_cmdshell in SSMS but, ok, I'll try with the Command Prompt ****
My original example assumed that excel.fmt file is in the same directory that you're running bcp from. If it's not, then include the full drive and path for it:
-f D:\path_to_format_file\excel.fmt
If that still doesn't work, make sure your account has permissions on the folder to read the format file. (which I would already assume if you were able to write the file, but never hurts to check)
The more important aspect is to have a FMT file.
For Excel extractions, should be like this:
9.0
1
1 SQLBINARY 0 0 "" 1 FileData ""
and saved with a .fmt extension.
"FileData" is the name of the image/varbinary column in the SQL Server table, from which we need
to extract the Excel files.
Then I've used this script to extract the file name:
bcp "select IdDocumento,reverse(substring(reverse(Nomefile)+'',1,charindex('',reverse(Nomefile)+'')-1)) FROM DBGestioneProfiliSAP.dbo.tblDocumento ORDER BY 1" queryout D:\Excel\SAPdocsFileNames.txt -S -S XSPW11P491F -U sanapp -P SQL01 -T -c -t,
and save the file names, with IDs, in the TXT file "D:\Excel\SAPdocsFileNames.txt".
Then, from the Command Prompt, I run this BCP command:
for /F "tokens=1-2 delims=," %a in (D:\Excel\SAPdocs101-200.txt) do bcp "SELECT FileData FROM DBGestioneProfiliSAP.dbo.tblDocumento WHERE IDDocumento=%a" queryout "%b" -T -f D:\Excel\excel.fmt
Thanks a lot Robert, your help has been incalculable.
Also note that this will work for any binary file stored in an image or varbinary(max) column in the database. I've extracted JPEG and PDF files using this technique. The same format file will work for them all, you may just need to change the column name in the file, or modify your bcp query to alias the real column name to the one in the format file.