0

I have a batch query that I execute from SQL Server Management Studio (v18.5) that has multiple dynamic pieces and generates multiple query results that I would like to export to Excel automatically from the SSMS console, if possible.

In the batch query, I first select a unique set values from 1 column in a table, and I iterate through that list to build a dynamic pivot query using the value from the list. Each pivot query will have a different set of results for each value.

For example: unique list that I loop through:

Type
-----
Fan
Compressor
Belt
Motor
Filter

The pivot query results for Fan will have a unique set of columns that are different from the pivot query results for Compressor.

Fan pivot columns

FanID, Speed, Weight, Blade Size, RPM

Compressor pivot columns

CompressorID, HP, Voltage, Amps, Height, 

Each time I loop through the Type list, I would like to export the pivoted query results to an Excel file. Ideally, I would like to export to 1 workbook with each Type's pivoted query results having its own worksheet. Since each pivoted query result will have a difference set of columns, I cannot compile all of the query results into 1 table and then export to Excel (or csv). Trying to avoid having multiple files, one for each type.

UPDATE:

I have added the following to my batch query:

INSERT INTO OPENROWSET(''Microsoft.ACE.OLEDB.12.0'',''Excel 12.0; Database=C:\temp\tesxt.xlsx;'',''SELECT * FROM [Sheet1$]'')

But I'm getting the following error:

Cannot create an instance of OLE DB provider "Microsoft.ACE.OLEDB.12.0" for linked server "(null)"

I ran these two commands prior to attempting the code above:

EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'Ad Hoc Distributed Queries', 1
RECONFIGURE

UPDATE: I was hoping there was a down and dirty export option that would eliminate the need to write an application.

I tried the BCP approach, but I was unable to get it to work. I changed the server name to mine and the database name to mine; no luck. The @sql variable is my dynamic pivot query. Tried different bcp command parameters with no luck.

enter image description here

enter image description here

UPDATE: Getting a little closer; I replaced @sql from the bcp command above with a simple select query and the command executed, creating a new .csv file for each "Type" in the type list; there must be something in the complex dynamic pivot @sql string that bcp doesn't like. Regardless, I know the underlying plumbing of having bcp export query results to a csv file is working.

UPDATE:

Here is the @sqlCommandForBcp

set @sqlCommandForBcp = 'bcp "'+ @sqlForPivot + '" queryout "' + @filePath + @fileName + '" -S CLT00ZB1\SQLEXPRESS2019 -T -t , -d Repository -c'

This is a print out of the @sqlCommandForBcp which includes an example of the real pivot query. Each iteration through the Type list generates a new pivot query with different columns.

bcp "SELECT ProductID, KWGAID, ShortDesc, VendorID, Segment, Category, Type, SubType,[Airflow], [Amperage], [Bearings], [Cross Reference], [Diameter], [Enclosure], [Footnotes], [Frame], [Frequency], [Item], [Motor Type], [OEM], [Options], [Power], [Replacing], [Rotation], [RPM], [RPM-Range], [Series-Brand], [Shaft], [Speed], [Stack Size], [Voltage] 
            FROM
            (
                SELECT ''+ CONVERT(nvarchar(8), a.SeqNum) + '  |  ' + '' + a.TypeID + '' + '  |  ' + CONVERT(nvarchar(1), a.IncludeName) + '  |     ' + 
                         a.Value as Value, a.Name, p.KWGAID, p.SeqNum as ProductID, p.ShortDescription as ShortDesc, 
                            p.VendorID, p.VendorPartNumber, s.Name as Segment, c.Name as Category, 
                            t.Name as Type, st.Name as SubType

            FROM            Product p INNER JOIN
                                     Segment s ON p.SegmentID = s.ID INNER JOIN
                                     Category c ON p.CategoryID = c.ID INNER JOIN
                                     Type t ON p.TypeID = t.ID INNER JOIN
                                     Vendor v ON p.VendorID = v.ID INNER JOIN
                                     SubType st ON p.SubTypeID = st.ID INNER JOIN
                                     Attribute a ON p.SeqNum = a.ProductSeqNum INNER JOIN
                                     AttributeType at ON a.TypeID = at.ID

            Where v.ID = 132 AND c.Name like 'Blower Motors' 

            ) as PivotData
            PIVOT
            (
                Max(Value)
                For Name IN ([Airflow], [Amperage], [Bearings], [Cross Reference], [Diameter], [Enclosure], [Footnotes], [Frame], [Frequency], [Item], [Motor Type], [OEM], [Options], [Power], [Replacing], [Rotation], [RPM], [RPM-Range], [Series-Brand], [Shaft], [Speed], [Stack Size], [Voltage])
            ) as PivotResults Order by Segment, Category, Type, SubType, ShortDesc" queryout "C:\Temp\Blower Motors.csv" -S CLT00ZB1\SQLEXPRESS2019 -T -t , -d Repository -c

UPDATE:

Another step forward. Inserting the pivot query results into a temp global table and then selecting that table in the bcp command works! I am now getting results exporting to individual csv files; however, I'm seeing two issues. 1. the results do not include column headers; I have to have those and each pivot queries results will have different columns. 2. There are commas in the query results and that is throwing off the exported results. I read through the bcp documentation and didn't see a command switch to force column headers; also not sure how to handle the commas in the query results.

Update: I finally gave up on the tSQL approach with SSMSand starting writing a solution in another language. This was solely for a temporary cleanup project, not a long-term production solution. I’m using Access linked via ODBC and some VBA to handle looping through the list of Types and exporting the pivoted (CrossTab in Access) results to Excel. With VBA I have everything I need to create workbooks on the fly and insert worksheets, etc.

I learned a good bit about bcp and linked servers within SQL, but there just isn’t a very simple solution for what I’m wanting to do.

Not sure who should get the points here. Thanks to all for the input.

GJGerson
  • 211
  • 1
  • 9
  • 20
  • 1
    Could you show the SQL? If you need help with it. – gotqn Jul 30 '20 at 04:16
  • 1
    Did you try to check the answer form this thread? https://stackoverflow.com/questions/36987636/cannot-create-an-instance-of-ole-db-provider-microsoft-jet-oledb-4-0-for-linked – Kokokoko Jul 30 '20 at 11:08
  • 1
    This is the article that helped me with this issue https://sqlwithmanoj.com/2012/07/10/querying-excel-2010-from-sql-server-in-64-bit-environment/ – Kokokoko Jul 30 '20 at 11:30
  • 1
    I posted the SQL above; there is something in there it doesn't like, just not sure what. As for Kokokoko's comments, thanks, I looked at both of those...ran into permissions issues with the linked server approach...feel like bcp is " this " close...lol. – GJGerson Jul 30 '20 at 19:14

2 Answers2

1

It will be better to build the file using another language. For example, there are a lot of techniques to generate the file using .net or you can use power-shell. The idea is to wrap your code in a stored procedure and call it from other language.

In the context of SQL Server, you can use BCP to generate a CSV file, which than can be consume by Excel.

You need to enable xp_cmdshell in order to be able to execute bcp command in the context of T-SQL:

 -- 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

Then, you simply to:

DECLARE       @sqlCommand   VARCHAR(1000)
DECLARE       @filePath     VARCHAR(100)
DECLARE       @fileName     VARCHAR(100)

SET    @filePath = 'C:\Temp\'
SET    @fileName = 'MyFile_' + CONVERT(VARCHAR, GETDATE(), 112) + '_' + CAST(DATEPART(HOUR, GETDATE()) AS VARCHAR) + '_' + CAST(DATEPART(MINUTE,GETDATE()) AS VARCHAR) + '.csv'

SET  @sqlCommand = 'bcp "SELECT 1 AS A, 2 AS B, 3 AS C UNION ALL SELECT 4, 5, 6" queryout "' +
                    @filePath + @fileName +
                    ' " -S RMVNSQL01\INST1 -T -t, -d smModel -c'

PRINT       @sqlCommand

EXEC   master..xp_cmdshell @sqlCommand

GO

Which will generate the following file MyFile_20200729_9_1.csv:

enter image description here

Also, if yo want to generate the excel file using SQL Server and you want to have separate sheets, you may want to use the SQL Server Reporting Server. The issue there is having your data dynamic, you will face difficulties to visualize it. It will be better just to ready it and perform the PIVOT there.


In order to add the headings insert a row at the beginning. Something like this:

SELECT 'ProductID', 'KWGAID', 'ShortDesc', 'VendorID', 'Segment', 'Category', 'Type', 'SubType', 'Airflow', 'Amperage', 'Bearings', 'Cross Reference', 'Diameter', 'Enclosure', 'Footnotes', 'Frame', 'Frequency', 'Item', 'Motor Type', 'OEM', 'Options', 'Power', 'Replacing', 'Rotation', 'RPM', 'RPM-Range', 'Series-Brand', 'Shaft', 'Speed', 'Stack Size', 'Voltage'
UNION ALL
...
gotqn
  • 42,737
  • 46
  • 157
  • 243
  • 1
    I understand regarding writing this in another language; I was hoping there was a quick down and dirty option to export the file directly from SSMS. I think bcp will work, but it doesn't like something. I posted the command I'm using and the error results. – GJGerson Jul 29 '20 at 16:21
  • 1
    Not yet; trying to get the ACE OLEDB option working. – GJGerson Aug 01 '20 at 01:53
  • 1
    @GJGerson Show me the code you are using and the error you get. – gotqn Aug 01 '20 at 06:54
  • 1
    Thanks for the help; as noted above I’m going to write a solution in another language; just too many hills to climb; as you mentioned tSQL isn’t the best solution for this project. Not sure what to do about points; you and Mitz have been very helpful. Not sure how to split points...lol – GJGerson Aug 05 '20 at 03:42
  • 1
    @GJGerson, You are welcome. There are very good libraries on .net for generating xls files. They come with many options - even formating, so using such you will do the task pretty easy I believe. – gotqn Aug 05 '20 at 04:13
1

If you can store a blank excel file on the server and use xp_cmdshell to copy it to a new location for each file, you can use:

INSERT INTO OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0; HDR=YES;IMEX=0;Database=c:\mitz\somex2.xlsx', 'Select * from [Sheet1$]')
SELECT * from mytable

This would insert data from your table into Sheet1. You need to install Ace Oledb and do a few tweaks if it's your 1st time using it.

If having a blank file is not a option, you could also create a new XLSX file using openrowset but it has a random sheet name. After creating it like that, you can use other selects to get the sheet name and then convert my upper insert statement into Dynamic SQL so that the worksheet name is dynamic. So the simple way would be to have a blank template.xlsx file and copy it for use in your excel file exports.

For setting up Ace OLEDB, see this link .

EDIT2: if you need variable column names:

  • create a excel file with one row holding some text in it's first row. use as many cells as the max number of columns you will export. This will be your template file

  • for each export, copy the template file using xp_cmdshell to a new file

  • run this query to set column names (you will create the query with dynamic sql for your columns). set non=used columns to ' '(blank). The HDR=no means you update all rows, in this case 1st row which will be your header.

update OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0; Database=c:\mitz\blank.xlsx; HDR=no', 'Select * from [Sheet1$]')
set F1='mycol1', F2='MYCOL2'
  • run this insert query. MYCOL1, MYCOL2 will also be dynamicly created. If column names need to have wierd names use [ ].
INSERT INTO OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0; Database=c:\mitz\blank.xlsx', 'Select MYCOL1, MYCOL2 from [Sheet1$]')
SELECT MYCOL1, MYCOL2 from ##some
Mitz
  • 135
  • 8
  • 1
    If you want to go the bcp route, make sure the @query and the @filepath+@filename are surrounded with " " (double quotes), not 2 x ' (two single quotes after another). The command should run from a command prompt. – Mitz Jul 30 '20 at 13:34
  • 1
    The bcp route will work if I can get it to accept the @query value I am passing to it. I've wrapped it with double quotes, but it still doesn't like something. I've pasted a copy above. – GJGerson Jul 30 '20 at 19:12
  • 1
    I'm 90% sure u can't have (char(13) into a bcp query. I recommend writing the pivoted data into a ##table and then bcp "select * from ##mytable" queryout .... Temp table with # are session dependent and bcp is another session, so you need a global temp table (starting with ##). you drop the table after the BCP command completes – Mitz Jul 30 '20 at 21:20
  • 1
    To be more clear: run your query like this: select ... into ##myTable from ... . Then run the bcp command like "select * from ##mytable". We use this daily to generate text files for banking stuff. – Mitz Jul 30 '20 at 21:32
  • 1
    Inserting into a global temp table solves the issue with the bcp error; posted update above. Now I just have 2 issues left to resolve. Column headers and commas in query results throwing off the columns in the csv files. – GJGerson Jul 31 '20 at 00:10
  • 1
    There is no builtin way to do that in BCP (it is used to export in multiple formats not specific to CSV), so you need to handle CSV specs: you need to surround the fields that contain commas in double-quotes( "14,5" ). If the fields also contain the " character, that becomes "" - that is two double quotes after another. I got this by manually creating a csv in excel with those chars ("Michael ""Wierdname"" Quotey". Perhaps you can add the doublequotes to every field by default and just replace " with "" while you're at it. I still think that Ace OLEDB would be faster to use tho :) – Mitz Jul 31 '20 at 05:28
  • 1
    Looks like I might have to go the Ace OLEDB route; I can add the column headers by doing a query to get the temp table schema and doing a union to the pivot results temp table; however, there are commas and quotes scattered all throughout the data in the pivot tables. I have Ace OLEDB installed (64bit version - did the passive command line install as to not conflict with the 32bit version), and I configured AllowInProcess and DynamicParameters settings, but I get this error: Cannot initiate teh data source object of OLE DB provider "Microsoft.Ace.OLEDB 12.0" for linked server null. – GJGerson Jul 31 '20 at 13:20
  • 1
    Check the user under which the SQL Server service runs. Go to C:\Users\\AppData\Local\Temp and grant full access to everyone (actually to the user running the query but setting it to everyone should get you started). If the user is System or Network Service, try to use these: C:\Windows\ServiceProfiles\NetworkService\AppData\Local\Temp C:\Windows\ServiceProfiles\LocalService\AppData\Local\Temp. In my case i always set Sql Server to run under a AD user. – Mitz Jul 31 '20 at 17:31
  • 1
    SQL Server is running under NT Service\MSSQL$SQLEXPRESS; I granted full access to everyone for the service profiles and that has resolved the "cannot initiate the data source..." error; but I'm not getting this: "Column name or number of supplied values does not match table definition." I'm your exact code above and passing "Select * From ##tempPivotTable" – GJGerson Aug 01 '20 at 02:49
  • 1
    I think with more effort and time I could get this to work, but it just feels like sucking peanut butter through a straw; as I mentioned above I’ve started writing a solution in vba in access; I can do it all there; just thought maybe there was a fast and easy tSQL solution. – GJGerson Aug 05 '20 at 03:38