QUERY.ColumnList Does Not Return True Column Ordering

Posted April 18, 2007 at 8:39 AM

Tags: ColdFusion

This is a really minor point that I just happened to run into yesterday. I had to rewrite and expand upon some old code in one of our systems (originally written by a crazy man) when I came across a query that used "SELECT *" to get its columns. This really drives me crazy as not only is it slow, it also provides absolutely no insight into what the query is returning. For demonstration's stake, let's build a query to demonstrate how ColumnList ordering messed me up:

 Launch code in new window » Download code as text file »

  • <!--- Create an empty query. --->
  • <cfset qGirl = QueryNew( "" ) />
  •  
  • <!---
  • Add columns to this empty query and provide
  • default column values.
  • --->
  • <cfset QueryAddColumn(
  • qGirl,
  • "name",
  • "CF_SQL_VARCHAR",
  • ListToArray( "Libby,Kit,Sam,Niki" )
  • ) />
  •  
  • <cfset QueryAddColumn(
  • qGirl,
  • "hair",
  • "CF_SQL_VARCHAR",
  • ListToArray( "Blonde,Blonde,Blonde,Brunetted" )
  • ) />
  •  
  • <cfset QueryAddColumn(
  • qGirl,
  • "age",
  • "CF_SQL_INTEGER",
  • ListToArray( "28,24,25,27" )
  • ) />

Notice that this query now has columns added in this order: Name, Hair, Age. Assuming this was the query that was queried using "SELECT *", I dumped out the ColumnList attribute to give me the columns name. My plan was then to replace the "*" in the select statement with the column names:

 Launch code in new window » Download code as text file »

  • <!--- Get query column names. --->
  • <cfdump var="#qGirl.ColumnList#" />

This output the following:

AGE,HAIR,NAME

Notice that the order of these columns is alphabetical and is not reflective of how the query was actually constructed. Most of the time, this would not make an ounce of difference as to what you were doing, even if you were using the column list to loop over the query. In my case, however, I had to join the SAME query from two different data sources. Same query, same structure, just different data sources. After each query was performed, I needed to UNION ALL them together (pretend the qGirlOne and qGirlTwo are similar queries):

 Launch code in new window » Download code as text file »

  • <!---
  • Query the Girl queries from the different data sources
  • and union them together. Since we don't actually want
  • to eliminate any records, use UNION ALL.
  • --->
  • <cfquery name="qGirlBoth" dbtype="query">
  • (
  • SELECT
  • *
  • FROM
  • qGirlOne
  • )
  •  
  • UNION ALL
  •  
  • (
  • SELECT
  • *
  • FROM
  • qGirlTwo
  • )
  • </cfquery>

This works quite nicely. My problem was that this also makes use of the "SELECT *" statement. So, taking the column list that I CFDump'ed out above, I applied it to the first query in the union:

 Launch code in new window » Download code as text file »

  • <!---
  • Query the Girl queries from the different data sources
  • and union them together. Since we don't actually want
  • to eliminate any records, use UNION ALL.
  • --->
  • <cfquery name="qGirlBoth" dbtype="query">
  • (
  • SELECT
  • AGE,
  • HAIR,
  • NAME
  • FROM
  • qGirlOne
  • )
  •  
  • UNION ALL
  •  
  • (
  • SELECT
  • *
  • FROM
  • qGirlTwo
  • )
  • </cfquery>

Notice that I only use the column list in the first query and NOT in the second query. This was very deliberate; once you name the columns in the first query, the names of the columns in subsequent unioned queries are ignored (the column values are used, but only the column names from the first query are acknowledged). I didn't mind leaving the "SELECT *" in the second query as I felt the first one provided enough feedback to the programmer.

But, running the above query, I got the ColdFusion error:

Error Executing Database Query. Query Of Queries runtime error. All resulting columns of queries in a SELECT statement containing a UNION operator must have corresponding types. Columns with index number equal "1" have different types (INTEGER, VARCHAR). The error occurred on line 58.

At first, this really threw me through a loop. I mean, what the heck? They are the same query (just different data sources). Why would their columns not line up? But then it dawned on me - I had no idea how the actual query was constructed (as it was using "SELECT *" from the get-go). And then I thought, those column names look awfully alphabetical.

The problem, as it turns out, is that the two queries in the UNION ALL query had the same columns, but because I was naming the column explicitly in the first one, it had a different column order than the second query in the union. Had I also explicitly name the columns in the second query as well, this would not even have cropped up.

This just goes to show, I think, that "SELECT *" is the devil's tool. It provides no readability or insight to any programmer that comes after you. If you name your columns, I think it makes you a better person.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page



Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Apr 18, 2007 at 9:31 AM // reply »
95 Comments

yeah, "select *" does suck. However, ordering the columns alphabetically by default is also a pain in the ass. I've also noticed that the same thing happens when I use cfdump to dump a query - the freaking columns are ordered alphabetically even when I have explicitly specified the order. Wtf? Any idea how to remedy that?


Apr 18, 2007 at 9:46 AM // reply »
5,406 Comments

@Boyan,

I agree re: alpha listing. It makes debugging a large query more difficult because you have to search for the columns rather than just knowing that it should be the first or second column, etc. And, whats more, if you grab the underlying Java methods of the query, you can get the columns in their given order... why don't they use that?


Apr 18, 2007 at 11:58 AM // reply »
111 Comments

@Ben, What IS the underlying Java method if you want the query columns in order?


Apr 18, 2007 at 12:04 PM // reply »
19 Comments

While I agree that SELECT * does suck from a readability standpoint, The "Slowness" argument that I hear from time to time isn't really valid from my experiments. Admittedly, you should only query for the fields you're going to actually use, but if you're using every column (like you are in your examples) then SELECT * isn't any noticeably slower than it's more "specified" counterpart.


Apr 18, 2007 at 12:11 PM // reply »
5,406 Comments

@Pete,

I think you can use QUERY.GetColumnNames(). I used it once trying to get the names of the column of a query:

http://bennadel.com/index.cfm?dax=blog:357.view

... but take a look at this site:

http://www.activsoftware.com/mx/undocumentation/query.cfm

It lists out all the underlying Java methods (not all are available as far as I know though).


Apr 18, 2007 at 2:11 PM // reply »
26 Comments

Using "SELECT *" isn't slower than specifying all of the columns by name, in my experience. But the disadvantage of is that 1) it's slower if you're returning columns you don't need, and 2) if you change/add columns in the table you're reading from, your query results might not reflect the changes immediately because the database use an outdated cache of the column list in the query pre-processing stage.


Simon Free
Apr 18, 2007 at 2:54 PM // reply »
31 Comments

I remember that crazy man. That was a crazy time in the office. He should be getting Parole soon!


Apr 18, 2007 at 2:59 PM // reply »
5,406 Comments

... or chemically castrated (according to MySpace.com)


Sam
Apr 19, 2007 at 7:46 PM // reply »
76 Comments

Peter - you can use getMetaData on the query object now to get the correct order (I beleive).

Ben- ""SELECT *" is the devil's tool. It provides no readability or insight to any programmer that comes after you"

That is quite true, but it is also true that everytime your database changes if you are naming the columns in the query you've got at least one extra place to change it. (But, I still think I prefer naming them).


Apr 19, 2007 at 10:38 PM // reply »
5,406 Comments

Yeah, exactly. Even if you have to change a column name, I think that is going to be in the minority of cases when you compare it to how often you might have to open a file and review what is going on.


Post Comment  |  Ask Ben

Recent Blog Comments
Jul 4, 2009 at 9:42 AM
FLV 404 Error On Windows 2003 Server
I bookmarked this page. Thanks for given this great post.... ... read »
Jul 4, 2009 at 4:00 AM
Terms Of Service / Privacy Policy Document Generator
thanks ben, I'm not a big fan of contracts so to find your no no-nesense ToS generator has helped me no end. all the best matt ... read »
Justice
Jul 3, 2009 at 11:10 PM
Create A Running Average Without Storing Individual Values
@Ben, I think you're going about this the wrong way. You're trying to use complicated techniques when there is a simple and beautiful technique readily available (a la Gary Funk's comment). Instead ... read »
Bob
Jul 3, 2009 at 9:19 PM
Project HUGE: Huge In A Hurry - Get Big - Phase 3 / Week 1
a good technical explanation http://crossfitphoenix.typepad.com/crossfit_phoenix_forging_/the-overhead-squat.html ... read »
Jul 3, 2009 at 9:03 PM
Create A Running Average Without Storing Individual Values
If I wanted to do this and only carry two numbers, I'd keep track of the sum and N. Then you are pretty much accurate all the time. average = (sum + new_number) / (N + 1) But all this was in a for ... read »
Roland Collins
Jul 3, 2009 at 8:58 PM
Create A Running Average Without Storing Individual Values
@Martin - not just floating point though. Depending on what langauge you're working in, decimals can cause just as many headaches if they're not precise enough. But again, for most applications, th ... read »
Isnogood
Jul 3, 2009 at 7:16 PM
Project HUGE: Huge In A Hurry - Get Big - Phase 3 / Week 1
Watch this http://www.nsca-lift.org/videos/default.shtml ... read »
Aaron
Jul 3, 2009 at 7:13 PM
Project HUGE: Get Big, Phase One (Chat Waterbury - Huge In A Hurry)
I've just finished the 3rd week of phase 3, and have to agree that the overhead squats are hard. I think this is most due to the wide grip on which places more pressure on your upper back. Only this ... read »