4

I created sample code using the Microsoft SQL Server Northwind demo database. If you don't have access to this demo database here is a simple (MS-SQL) script to create the table and a row of data for this question.

CREATE TABLE [dbo].[Products](
    [ProductID] [int] IDENTITY(1,1) NOT NULL,
    [ProductName] [nvarchar](40) NOT NULL,
    [SupplierID] [int] NULL,
    [CategoryID] [int] NULL,
    [QuantityPerUnit] [nvarchar](20) NULL,
    [UnitPrice] [money] NULL CONSTRAINT [DF_Products_UnitPrice]  DEFAULT (0),
    [UnitsInStock] [smallint] NULL CONSTRAINT [DF_Products_UnitsInStock]  DEFAULT (0),
    [UnitsOnOrder] [smallint] NULL CONSTRAINT [DF_Products_UnitsOnOrder]  DEFAULT (0),
    [ReorderLevel] [smallint] NULL CONSTRAINT [DF_Products_ReorderLevel]  DEFAULT (0),
    [Discontinued] [bit] NOT NULL CONSTRAINT [DF_Products_Discontinued]  DEFAULT (0),
 CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED 
(
    [ProductID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO
SET IDENTITY_INSERT [dbo].[Products] ON 
GO
INSERT [dbo].[Products] ([ProductID], [ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock], [UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES (1, N'Chai', 1, 1, N'10 boxes x 20 bags', 18.0000, 39, 0, 10, 0)
GO
SET IDENTITY_INSERT [dbo].[Products] OFF
GO

Here is the ColdFusion code:

<cfset variables.useTempVar = false>

<cfquery datasource="Northwind2014" name="qryNWProducts">
SELECT TOP 1 * from Products;
</cfquery>

<cfdump var="#qryNWProducts#" label="qryNWProducts">

<cfset variables['stProduct'] = {}>
<cfloop index="vcColName" list="#qryNWProducts.columnlist#">
    <cfif variables.useTempVar>
        <cfset variables['temp'] = qryNWProducts[vcColName]>
        <cfset variables['stProduct'][vcColName] = variables.temp>
    <cfelse>
        <cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName]>
    </cfif>
</cfloop>

<cfdump var="#variables['stProduct']#" label="variables['stProduct']">

<cfloop collection="#variables['stProduct']#" item="key"><cfoutput>
    variables['stProduct']['#key#'] JVM datatype = #getMetadata(variables['stProduct'][key]).getName()#<br>
</cfoutput></cfloop>

<br>
This always works:<br>
<cfset variables['aPhrase'] = "I ordered " &  variables.stProduct.ProductName & " for " & DollarFormat(variables.stProduct.UnitPrice) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>

<br>
With &quot;variables.useTempVar = false&quot;, the next line will throw a &quot;Complex object types cannot be converted to simple values. &quot; error.<br>
<cfset variables['aPhrase'] = "I ordered " &  variables['stProduct']['ProductName'] & " for " & DollarFormat(variables['stProduct']['UnitPrice']) & ".">
<cfoutput>#variables['aPhrase']#<br></cfoutput>

The code above has a boolean variable named "variables.useTempVar" at the top that can be flipped to see the error that I'm getting.

It looks like the direct assignment (when variables.useTempVar = false) from the query to the structure causes the structure values to be of JVM type "coldfusion.sql.QueryColumn".

Another note: if this line of code:

<cfset variables['stProduct'][vcColName] = variables.temp>

is changed to:

<cfset variables['stProduct'][vcColName] = variables['temp']>

The JVM datatype will be "coldfusion.sql.QueryColumn".

When the dot notation temp variable is used to assign the query field (when variables.useTempVar = true); the JVM datatypes are simple types that matches up pretty well with the database column types (java.lang.Integer, java.math.BigDecimal, java.lang.String, etc.).

I've also experiemented with statements like this and that provided some odd results:

<cfset variables['stProduct'][vcColName] = qryNWProducts[vcColName].toString()>

Here's the question. Is this the best way to transfer the simple values from a query to a structure? It seems odd to be forced to use a temp variable and dot notation to make this work.

Comment: I've always thought that dot notation and associative array notation were equivalent. This code example appears to contradict that opinion.

Scott Jibben
  • 2,229
  • 1
  • 14
  • 22
  • 2
    (Edit) I am heading to sleep and do not have time to read the entire post, but will say you seem to be missing row numbers ie `qryNWProducts[vcColName]`. When using associative array notation with query objects, you must supply a query row number. Otherwise you are grabbing the wrong object: a column object - instead of an individual value *within* that column. Instead use `qryNWProducts[vcColName][1]` where `1` is any valid row number within the query. Assuming you really need a structure, before reinventing the wheel, have you checked cflib.org ? http://www.cflib.org/udf/QueryRowToStruct – Leigh Jun 17 '15 at 03:14
  • (I realize there is also a question about why the two seemingly similar statements evaluate differently, but I will let someone less sleepy tackle that ;-) – Leigh Jun 17 '15 at 03:24
  • Thanks @Leigh. I should have known that... – Scott Jibben Jun 17 '15 at 20:47

2 Answers2

3

@Leigh is correct in that you need to supply the row number when using associative array notation with a query object. So you'd reference row 1 like: qryNWProducts[vcColName][1]

As for your question

Is this the best way to transfer the simple values from a query to a structure?

Are you sure you need a struct? Your question doesn't really specify the use case, so it is entirely possible that you would be better off using the query object as-is.

If you do need it to be a struct (and since you are using ColdFusion 11) might I suggest you take a look at serializeJSON/deSerializeJSON to convert this to a struct. The serializeJSON has a new attribute that will properly serialize a query object into an "AJAX friendly" JSON array of structs. You can then deSerialize the JSON into a CF array, like so:

NWProducts = deSerializeJSON( serializeJSON( qryNWProducts, 'struct' ) )[1]; Which would return a struct representation of the first row in that query object.

Although it's not obvious from the Adobe docs for serializeJSON, the second parameter can be one of: true|false|struct|row|column which will change how the resulting data is formatted.

Here's a runnable example of using the above technique showcasing each serializeQueryAs option.

It's also a better practice to start moving that kind of code into cfscript. queryExecute is quite easy to use and makes script based queries very easy to develop. See the How To Create a Query in cfscript tutorial at trycf.com for more on how to develop script based queries.

Final note, and this is a bit off topic but it is a generally accepted best practice to not use Hungarian Notation when naming variables.

Abram
  • 784
  • 5
  • 11
  • I often use the argumentCollection attribute on functions. Query objects did not work with argumentCollection in the past. – Scott Jibben Jun 17 '15 at 20:56
  • I'll probably continue to use the CF tag based language for a while because it always works. I have had problems with cfscript; http://stackoverflow.com/questions/26254498/how-to-set-debug-output-query-name-using-queryexecute. Adobe hasn't fixed this yet. – Scott Jibben Jun 17 '15 at 21:10
  • Looks like it's fixed in updater 5, though this is not really a solid case not to use cfscript, imo. Many have been developing purely cfscript for years and though it hasn't always been peaches-n-cream, its definitely a better mode than tags nowadays (especially acf11). – Abram Jun 17 '15 at 21:30
  • Read my update to that post. I don't think it is entirely fixed. – Scott Jibben Jun 17 '15 at 22:08
  • Interesting, adobe does have a bad habit of not *really* fixing things. Still, I wouldn't not use cfscript over debug labels. If its that much of a showstopper, perhaps take a look at [Lucee](http://lucee.org/). – Abram Jun 18 '15 at 04:40
  • All the queries in debug output are still given the same name. When you have 50,000+ lines of legacy code that is all tag-based, it would be a major undertaking to revise it all to script only to benefit what? Having mixed script + tag code would look even worse than just tag based imo. Eventually, I'll probably return to the Microsoft platform using C# .NET. I've worked with CF since version 1.62. Before that it was MS C++ & MFC. – Scott Jibben Jun 19 '15 at 18:49
  • Yes, having dealt with a few monster legacy apps, totally agreed there is no point in rewriting that kind of app. I suspect Abram's talking about new code. Script based is definitely my preference for new apps. It is more natural and similar to other languages like java and C#. It has come a long way, but ... yeah, it does still have some annoying flaws. – Leigh Jun 20 '15 at 19:41
2

@Abram's covered the mains answer, but just to pick up one tangential point you raise.

Dot notation and associative array notation are generally equivalent in CFML. However in the case of queries, there is a slight variation. Dot notation: query.columnName is treated as shorthand for query.columnName[currentRow] (where currentRow defaults to 1).

Associative array notation with queries does not have this "syntactic sugar", so query["columnName"] refers to the entire column, as the syntax actually indicates.

There are no functions I am aware of in CFML that take a query column as an argument, however the CFML engine will convert the column to an array if it's used in an array function. This is quite handy sometimes.

Adam Cameron
  • 29,677
  • 4
  • 37
  • 78