Ask Ben: Exporting A Query To CSV Without Using Column Names

Posted October 29, 2008 at 9:58 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

I have an issue that's been bugging me for a while now and I've had to back out of my CF application just to get the job done with SQL Server Enterprise Mgr. The issue is this: I am trying to export a cfquery result set to a file and I want to use all the columns in the table. There are over 300 columns in this table with about 700+ rows. I have been able to create a header row with .columnlist but I cannot seem to get beyond that. I am stuck on what to do with "output" under cffile. I've tried all sorts of string manipulations to get it to print every column. It has not worked. What would you say is the way to get past this? Please don't tell me I have to write out each column name and surround them with hash marks.

You are very close to the solution using the .ColumnList property; I'm sure you'd probably get it eventually, so I'll just help fill in the blanks. Basically, what we want to do is use the ColumnList property to output both the column headers as well as the data values. The trick to this solution is knowing that you can reference a query value using array notation:

Query[ ColumnName ][ RowIndex ]

Once you know this, you can probably start to see how a Query can be thought of as a two-dimensional array. And, once you make that mental leap, it's not too hard to imagine the nested loop that could be employed to loop over every cell value.

When it comes to creating CSV files, I like to put all of my columns in quotes if I think that any of them might contain quotes or commas. I know that this can end up putting a few extra characters in the CSV file, but I think it's a solid methodology. To help centralize this task, I write some sort of ColdFusion user defined function that encapsulates the preparation of each CSV value:

  • <cffunction
  • name="PrepareCSVValue"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I take a CSV value and prepare it for the file.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Value"
  • type="string"
  • required="true"
  • hint="I am the raw data value."
  • />
  •  
  • <!---
  • Esacpe any quotes in the value and then return the clean
  • escaped string surrounded by quotes.
  • --->
  • <cfreturn
  • (
  • """" &
  • Replace(
  • ARGUMENTS.Value,
  • """",
  • """""",
  • "all"
  • ) &
  • """"
  • )
  • />
  • </cffunction>

Notice that this UDF both escapes any quotes in the value and as well as wrapping the given value in its own quotes. We can always change this later; for example, we may decide that we want to check to see if a value has any characters that require escaping before we actually quote the entire value.

Ok, now that we have that out of the way, let's create a query (for testing purposes) and prepare the CSV data:

  • <!---
  • Create out data query (you would be using this as your
  • database query, for demonstration, I have to build it
  • manually).
  • --->
  • <cfset qData = QueryNew( "" ) />
  •  
  • <!--- Add Query columns. --->
  • <cfset QueryAddColumn(
  • qData,
  • "id",
  • "cf_sql_integer",
  • ListToArray( "1,2,3,4,5" )
  • ) />
  •  
  • <cfset QueryAddColumn(
  • qData,
  • "name",
  • "cf_sql_varchar",
  • ListToArray( "Ann,Kit,Libby,Jane,Beth" )
  • ) />
  •  
  • <cfset QueryAddColumn(
  • qData,
  • "pet_name",
  • "cf_sql_varchar",
  • ListToArray( "Cutie,""Honey"",Baby,Sugar,Sweetness" )
  • ) />
  •  
  •  
  •  
  • <!---
  • Now, let's create our array of row data. We are going to
  • create a CSV data set where each array index is its own
  • row of data.
  • --->
  • <cfset arrData = [] />
  •  
  • <!---
  • Now, we are going to start off by building a row of column
  • headers. Since we have (potentially) a lot of columns, we
  • are going to be using the query's column list. Let's first
  • break that up in to an array do its faster to navigate.
  • --->
  • <cfset arrColumns = ListToArray( qData.ColumnList ) />
  •  
  •  
  • <!--- Ok, now lets create an array for our header data. --->
  • <cfset arrRow = [] />
  •  
  • <!--- Loop over columns. --->
  • <cfloop
  • index="strValue"
  • array="#arrColumns#">
  •  
  • <!--- Add prepared CSV value to row array. --->
  • <cfset ArrayAppend(
  • arrRow,
  • PrepareCSVValue( strValue )
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Now that we have built up our row data, let's collapse it
  • and add it to our master data array.
  • --->
  • <cfset ArrayAppend(
  • arrData,
  • ArrayToList( arrRow )
  • ) />
  •  
  •  
  • <!---
  • Now that we have a header row out of the way, we have to
  • basically do the same thing for the actual data rows.
  • --->
  • <cfloop query="qData">
  •  
  • <!--- Create the array for this row. --->
  • <cfset arrRow = [] />
  •  
  • <!--- Loop over columns. --->
  • <cfloop
  • index="strColumn"
  • array="#arrColumns#">
  •  
  • <!--- Add prepared CSV value to row array. --->
  • <cfset ArrayAppend(
  • arrRow,
  • PrepareCSVValue( qData[ strColumn ][ qData.CurrentRow ] )
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Now that we have built up our row data, let's collapse
  • it and add it to our master data array.
  • --->
  • <cfset ArrayAppend(
  • arrData,
  • ArrayToList( arrRow )
  • ) />
  •  
  • </cfloop>
  •  
  •  
  • <!---
  • At this point, we have completely built up our master data
  • array into index-based rows. Now, we have to collapse all
  • those rows down into a single string variable that we can
  • write to the file.
  • --->
  • <cfset strData = ArrayToList(
  • arrData,
  • (Chr( 13 ) & Chr( 10 ))
  • ) />
  •  
  •  
  •  
  • <!---
  • We can now write this data to a file or simply output it.
  • I am outputting for demonstration purposes.
  • --->
  • <cfoutput>
  • #strData#
  • </cfoutput>

As you can see, we never make a single hard-coded reference to any column names. We simply break the column name list into an array and loop over that array any time we need to get at that column's values. Running the above code, we get the following output:

"ID","NAME","PET_NAME"
"1","Ann","Cutie"
"2","Kit","""Honey"""
"3","Libby","Baby"
"4","Jane","Sugar"
"5","Beth","Sweetness"

I only have a few columns and a few rows, but since none of the columns or rows are hard-coded, this is implicitly scalable to any size query you have (assuming you don't run out of RAM building the values). Also, notice also that the quotes in the "Honey" value were escaped as per the CSV standard.

Hope that helps. If you would like to see a UDF for this process that does mostly the same thing, but allows you to choose which columns to use, check out my QueryToCSV() ColdFusion function.




Reader Comments

Oct 29, 2008 at 1:55 PM // reply »
3 Comments

I am getting compile errors with '[' and '{' in your cfset statements.
<cfset arrData = [] />
It says it's not a valid CFML statement. I wonder if it's because I'm using Cf MX 7 and these are used in CF 8. True?


Oct 29, 2008 at 1:57 PM // reply »
11,246 Comments

@Jonathan,

Yeah, those are CF7 vs. CF8 errors. You need to replace [] with ArrayNew( 1 ). You will also need to update the loop tags. Right now, the solution uses array looping. In CF7, you need to use index looping:

<cfloop index="intColIndex" from="1" to="#ArrayLen( arrColumns )#">

<cfset strColumn = arrColumns[ intColIndex ] />

... and there's probably a few of those.


Oct 29, 2008 at 2:16 PM // reply »
131 Comments

And, if you do run into a RAM issue (Ben's note about "assuming you don't run out of RAM building the values"), and you might with 210,000 values, then just think about writing to your output file one line at a time, or blocks of lines at a time.

So, where Ben shows
ArrayAppend(
arrData,
ArrayToList( arrRow )
)

instead use that location to CFFILE action="append" the ArrayToList( arrRow ) to your target file. That will cause a lot more file I/O, but will not overwhelm the server RAM. To cut down on file I/O, you could keep writing to arrData, but then just write out arrData to the file on every 20th row or something and then reset arrData so it doesn't get too huge.

HTH,
-jfish


Oct 29, 2008 at 2:20 PM // reply »
11,246 Comments

@Jason,

Excellent point; thanks.


Oct 29, 2008 at 2:49 PM // reply »
3 Comments

Ben,

In the line 'qData[ strColumn ][ qData.CurrentRow ]', I am getting a compile error saying:

[Table (rows 5 columns id, name, pet_name): [id: coldfusion.sql.QueryColumn@a46637] [name: coldfusion.sql.QueryColumn@132cb29] [pet_name: coldfusion.sql.QueryColumn@1b829c7] ] is not indexable by 1.0

I saw your "...by name" entry from '06 but I'm not sure if this is another MX7 v. 8 deal or something different. Any help?

And thanks Jason for the optimization!


Oct 29, 2008 at 2:54 PM // reply »
11,246 Comments

@Jonathan,

It means you are trying to use the column name, "1.0". I am not sure that you can have periods in column names which leads me to think that something went wrong somewhere. Are you using the index to get the column name? The first thing you need to do inside the CFLoop is get a true reference to the column name:

<cfset strColumn = arrColumns[ intColumnIndex ] />

If you don't do that, you are probably using the NUMERIC index as your column name.


Oct 29, 2008 at 4:21 PM // reply »
3 Comments

It was my fault as I was changing up the code for Cf 7. I was getting the loop count number and not the value of the array, which Cf 8 seems to do you in the cfloop tag. Now that I was able to get your code to work on my box, I'll apply the same technique to my CSV application. Really appreciate your help.


Oct 29, 2008 at 5:23 PM // reply »
1 Comments

Hello, look at the new i/o features on cf8 usually it helps...

page: 26

http://cfunited.com/presentations/2007/AC308


Feb 16, 2010 at 6:07 AM // reply »
3 Comments

Hi,
Thanks for this Ben, I was looking for something similar, I wanted to contruct a table data display from an unknown query object, I don't know the column names or the values.

This pointed me in the right direction, in terms of creating the columnlist as an Array, and looping through it. I was using a nasty Evaluate() function before that.


Feb 17, 2010 at 10:23 PM // reply »
11,246 Comments

@Shaun,

Using array-style notation on queries comes in super handy! Not only can we access queries values with dynamic names, we can also update cell values in a given record.


Aug 3, 2011 at 4:53 AM // reply »
7 Comments

I came across that "is not indexable by 1.0" error. I was thinking of the result as an array and not as a query so when I was using COUPON_FEEDS[i] within my loop, I got that error. Problem solved when I used query of queries to get TOP 1 * and then got the name by FEED.NAME. One of my brain fart moments but I hope this helps someone else!


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
May 23, 2013 at 4:26 PM
ColdFusion QueryAppend( qOne, qTwo )
@Heather, Glad people are still getting value out of this! ... read »
May 23, 2013 at 3:49 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, I meant the code at the bottom (not the video). I did try to experiment with an intermediary variable, like: value = users.id[ i ]; arrayContains( userIDs, value ); ... but t ... read »
May 23, 2013 at 11:06 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Are you talking about As Number: YES As String: YES As Java: YES? If so, that's with 3 different ways of referencing the constant 1, not users.id[1]. Query object references(*) are what seem ... read »
May 23, 2013 at 9:55 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dan, According to the CF Admin, I'm running Java "1.6.0_45". As far as the DB column, in the database it's an INT. I'll see if I can dig into what CF sees it as. @WebManWalking, But h ... read »
May 23, 2013 at 9:49 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, I think the problem is that we're used to loose typing in ColdFusion, like JavaScript. If a value is a number but it's needed in an expression to be a string, noooo problem. I've encountered ... read »
May 23, 2013 at 9:47 AM
ColdFusion QueryAppend( qOne, qTwo )
You rock! Thank you, thank you, thank you!!! ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools