Skip to main content
Homestar9
Inspiring
February 13, 2014
Question

Strange Behavior When Working with Nested Query Objects

  • February 13, 2014
  • 1 reply
  • 1131 views

I just posted a comment over on Ben Nadel's site about this issue but I wanted to get some feedback from my fellow ColdFusion developers to see if anyone else has ever experienced this issue:

I have a function within a CFC that creates a query object and then nests another query object inside it (masterQuery > subQuery). The CFC returns the query object and to make things easier I loop through the query columns and bring everything into the local VARIABLES scope using the following format: "#a#" = masterQuery["#a#"] where "a" is the name of the query column.

After I had all of the query columns in the VARIABLES scope I tried to run a QofQ on the subQuery and low and behold I got an error message:

"coldfusion.sql.QueryColumn cannot be cast to coldfusion.sql.QueryTable"

However, when I tried a different method for bringing the masterQuery columns to the VARIABLES scope, I was able to get it to work: "#a#" = evaluate('masterQuery.#a#')

After doing some playing around using GetMetaData().getName() to determine what ColdFusion was seeing when a variable was set using masterQuery["#a#"] vs evaluate('masterQuery.#a#') I learned that ColdFusion continues to see the variable as a type: "QueryColumn" instead of its actual type (string, query, array, struct, etc...).

Currently the only way I can get CF to recognize the proper type is to use the evaluate() function.  What's even more annoying is that you can't reference columns in the nested query so masterQuery.subQuery.TestColumn1[2] produces an error message.

I wonder if this a bug within ColdFusion 10. I haven't tested it in any earlier versions and after extensive searching I couldn't find anyone else with a similar problem. Regardless I thought it was pretty interesting and noteworthy.

If you want to see some sample code to generate this error for yourself, here it is:

http://pastebin.com/pBkjUtxc

  1. <!--- start by building a new query object --->
  2. <cfset masterQuery = QueryNew("ID,Name,SubQuery", "INTEGER,VARCHAR,OBJECT")>
  3. <!--- add a row and some basic data --->
  4. <cfset QueryAddRow(masterQuery)>
  5. <cfset QuerySetCell(masterQuery, "ID",1)>
  6. <cfset QuerySetCell(masterQuery, "Name", "Dave")>
  7.   
  8. <!--- create a new sub query --->
  9. <cfset subQuery = QueryNew("TestColumn1,TestColumn2","VARCHAR,VARCHAR")>
  10. <!--- lets add a few rows to the subQuery --->
  11. <cfloop from="1" to="3" index="b">
  12.       
  13.         <!--- new row --->
  14.         <cfset queryAddRow(subQuery)>
  15.       
    1.     <!--- subQuery data --->
  16.         <cfset QuerySetCell(subQuery,"TestColumn1","Hello World")>
  17.         <cfset QuerySetCell(subQuery,"TestColumn2","All Your Base are Belong to Us")>
  18.       
  19. </cfloop>
  20. <!--- populate the master query column "subQuery" with the new query object we created --->
  21. <cfset QuerySetCell(masterQuery, "SubQuery", subQuery)>
  22. <!--- dump the data so we can make sure it looks good --->
  23. <cfdump var="#masterQuery#">
  24. <!--- now loop through the query, and assign all of the columns in the query to the variables scope --->
  25. <cfloop list="#masterQuery.columnList#" index="a">
  26.         <cfset "VARIABLES.#a#" = masterQuery["#a#"]>
  27. </cfloop>
  28. <!--- this generates an error in CF 10 (maybe other versions) --->
  29. <cftry>
  30.     <!--- try and run a QofQ --->
  31.     <cfquery dbtype="query" name="getSubQuery">
  32.     SELECT *
  33.     FROM subQuery
  34.     </cfquery> 
  35.   
  36.     <cfdump var="#getSubQuery#">
  37.     <cfcatch type="any">
  38.         <cfdump var="ERROR: #cfcatch.Message#">
  39.     </cfcatch>
  40. </cftry>
  41. <!--- Now try using evaluate() instead.  This doesn't generate an error for some reason --->
  42. <cfloop list="#masterQuery.columnList#" index="a">
  43.         <cfset "VARIABLES.#a#" = evaluate('masterQuery.#a#')>
  44. </cfloop>
  45. <!--- run the QofQ again --->
  46. <cfquery dbtype="query" name="getSubQuery">
  47. SELECT *
  48. FROM subQuery
  49. </cfquery>
  50. <cfdump var="#getSubQuery#">

Message was edited by: Henweigh99

This topic has been closed for replies.

1 reply

Homestar9
Homestar9Author
Inspiring
February 13, 2014

I think I may have solved my own issue... possibly. 

After doing more research I stumbled upon another Ben Nadel article:
http://www.bennadel.com/blog/214-Using-The-ColdFusion-Query-Object-As-A-Complex-Object-Iterator.htm

In this article it mentions some complications when trying to reference complex objects within queries.

What I was missing in my pastebin code when trying to assign a variable to a column in the query was the bracket notation designating the desired row.  I guess you have to do this when referring to complex object types.

So:

<cfset "VARIABLES.#a#" = masterQuery["#a#"]>

Should read:

<cfset "VARIABLES.#a#" = masterQuery["#a#"][1]>

Boom. Magic.

Hope this helps other people if they find themselves in a similar predicament.

BKBK
Community Expert
Community Expert
February 13, 2014

Henweigh99 wrote:

So:

<cfset "VARIABLES.#a#" = masterQuery["#a#"]>

Should read:

<cfset "VARIABLES.#a#" = masterQuery["#a#"][1]>

Could be better, much better! The following are just string assignments:

<cfset "VARIABLES.#a#" = masterQuery["#a#"][1]>

<cfset "VARIABLES.#a#" = evaluate('masterQuery.#a#')>

They contain a lot of complexity for the sake of complexity! A variable assignment would have been much better.

Let us therefore dissect the code. Consider the loop you reference:

<cfloop list="#masterQuery.columnList#" index="a">

        <cfset "VARIABLES.#a#" = evaluate('masterQuery.#a#')>

</cfloop>

Assume that the columnList consists of "col1,col2,col3". Also assume that the query returns the following data:

      col1 | col2 | col3

row 1: v11 | v12 | v13

row 2: v21 | v22 | v23

row 3: v31 | v32 | v33

row 4: v41 | v42 | v43

Then the loop will run 3 times, since columnList has 3 elements. The attribute index="a" implies that the variable a will successively take the values "col1", "col2", "col3" at each pass of the loop. The value of "VARIABLES.#a#" will therefore be, successively, the strings

"VARIABLES.col1"

"VARIABLES.col2"

"VARIABLES.col3"

Likewise, the variable evaluate('masterQuery.#a#') will take on the respective values

evaluate('masterQuery.col1')

evaluate('masterQuery.col2')

evaluate('masterQuery.col3')

These are, by default, the values of the first row of each column, hence:

v11

v12

v13

So, what your loop actually does is as follows:

First pass: 

     "VARIABLES.col1" = v11

Second pass:

     "VARIABLES.col2" = v12

Third pass: 

     "VARIABLES.col3" = v13

You are essentially picking out the values of the first row! It would have been much simpler, and neater, to do something like this instead:

<cfset firstRowData = structNew()>

<cfloop list="#masterQuery.columnList#" index="a">

        <cfset firstRowData = masterQuery[1]>

</cfloop>

Homestar9
Homestar9Author
Inspiring
February 15, 2014

Thanks so much for the tip and the detailed breakdown BKBK!  That's what I love about this forum, I always learn something new with each visit.