How to get associative arrays notation to assign simple values - coldfusion

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 "variables.useTempVar = false", the next line will throw a "Complex object types cannot be converted to simple values. " 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.

#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'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.

Related

How to show full number in Excel sheet?

I'm exporting a query to an Excel file using cfspeadsheet. It's working and creating the Excel sheet. However, the problem is that one of the columns, ie card_number, contains a 15 digit number, which is displayed like this: 4.5421E+15. Is there a way I can display the full number instead: 4254218068670980 ?
<!--- create manual query for demo --->
<cfset qData = queryNew("")>
<cfset queryAddColumn(qData, "NumericCol", "BigInt",["4254218068670980"])>
<cfset queryAddColumn(qData, "StringCol", "Varchar",["4254218068670980"])>
<cfset queryAddColumn(qData, "DecimalCol", "Decimal",["4254218068670980"])>
<!--- export to file --->
<cfspreadsheet action="write"
filename="c:/path/to/myFile.xls"
query="qData"
overwrite="true">
You need to define and use a format for the cell to show complete number. Below is a sample code snippet for your code:
<cfscript>
theFile=GetDirectoryFromPath(GetCurrentTemplatePath()) & "new_data.xls";
//Create a new Excel spreadsheet object.
theSheet = SpreadsheetNew("Expenses");
//Set the value a cell.
SpreadsheetSetCellValue(theSheet,"4254218068670980",1,4);
//Set value into another cell.
SpreadsheetSetCellValue(theSheet,"4254218068670980",2,4);
// Define a format class for for number.
longNum=StructNew();
longNum.dataformat = "0";
//Now use this class to format cell
SpreadsheetFormatCell(theSheet,longNum,2,4);
</cfscript>
There are many supported formats available; for a complete list you may check here.
Also, just like SpreadsheetFormatCell you may want to use SpreadsheetFormatColumn or other related functions.
(Too long for comments...)
FWIW, CFSpreadsheet is designed for very simple exports, without a lot of bells and whistles. If you need special formatting, you must use spreadsheet functions instead.
The closest equivalent to your current code is probably the SpreadsheetAddRows(sheet, query) function. It populates a worksheet with the data in the supplied query object. As Viv's answer mentions, you can then format the columns as desired. For example, if you want the value to be treated as text, use {dataformat = "#"}:
<cfscript>
SpreadsheetAddRows(theSheet, qData);
SpreadsheetFormatColumns(theSheet, {dataformat = "#"}, "1-3");
SpreadSheetWrite(theSheet, "c:/path/to/myFile.xls", true);
</cfscript>
As an aside, the examples in the documentation are not always the best or cleanest. Consider them a starting point, rather than using the code exactly "as is" ..

How to get sum of array for specific position?

I have an array:
<cfset fullarray = listtoArray (listofcollumnvalues)>
And it had for example:
fullarray[1]=20
fullarray[2]=11
fullarray[3]=4
fullarray[4]=12.2
etc.
And I wanted to add the sum of the the X position below number:
for example if I wanted the sum of the second element and below
2 + 3 + 4= 27.2
ColdFusion 10 introduced the ArraySlice function. It returns an array when you give it an array, a starting location, and an optional length. So ArraySlice(myArray,3,4) would return a "sub-array" with the elements that start at position 3 and includes 4 elements.
Based on your example:
mySum = ArraySum(ArraySlice(fullarray,2))
If you're on CF 9 or below, you can use a UDF. At CFLib.org there is
arraySlice
arraySlice2
Warning! arraySlice uses "start" and "end" element arguments, while arraySlice2 uses "start" and "length" arguments, like the built-in CF10 function.
Here's something that will work on older versions.
<cfscript>
function listSum(listStr)
{
var delim = ",";
if(ArrayLen(Arguments) GTE 2)
delim = Arguments[2];
return ArraySum(ListToArray(listStr, delim));
}
</cfscript>
Simply envoke it as #listSum(MyListOrArray)#
This is a formatted comment. You define your array as this:
<cfset fullarray = listtoArray (listofcollumnvalues)>
The variable, listofcollumnvalues looks like it might represent results of a query object. If that's the case, You might be doing more work than necessary, because each column of a query is effectively an array. Consider for example this query:
<cfquery name = "queryName">
select field1, numericalField
from etc
</cfquery>
You can actually do stuff like this:
numericalFieldSum = ArraySum(queryName['numericalField'];
It's not exactly what you want, but it illustrates that you might not need your initial command.
Continuing with the assumption that you started with a query, a query of queries might give you what you want.
<cfquery name = "mySum" dbtype = "query">
select sum(numericalField) theSumIWant
from queryName
where your conditions are met
</cfquery>
Of course, if listofcollumnvalues does not come from a query, you should declare this answer to be useless twaddle and downvote accordingly.

ColdFusion Syntax

I'm trying to modify existing codes in my ColdFusion application left by previous programmer. I don't understand the meaning of this line of code (the one with question marks):
<cfset Application[#form.username#] = 0> ??????
<cfset Session.loggedin="Yes">
<cfset Session.username="#Trim(Form.username)#">
Maybe I haven't been working with CF long enough to see this syntax so I don't know what this mean.
When setting an application variable I usually use this syntax:
<cfset application.variableName = "some value">
Can someone explain to me what is this ?
Thank you
As well as explicitly stating variable names at "code time" with dot notation, CFML allows one to reference them dynamically at runtime via a string value.
This is done via associative array notation (using square brackets), eg:
myVariableName = "foo";
variables[myVariableName] = "moo"; // equivalent to variables.foo = "moo"

ColdFusion 9: int and type="numeric" nasty bug?

I've just experienced a behaviour that defies any logic and could potentially lead to serious issues and was wondering if it was a bug or if the behaviour was itended and what are the best practices to circumvent the issue? If it's a bug, is there a patch?
Here's the two wierd behaviours that when put together are a threat to any system's data integrity.
int('1 2') -> 41276
isValid('numeric', '1 2') -> true
Why? Well let's see...
<cffunction name="deleteSomething" access="public" returntype="void">
<cfargument name="somethingId" type="numeric" required="yes">
<cfquery datasource="#dsn()#">
DELETE
FROM Something
WHERE id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.somethingId#">;
</cfquery>
</cffunction>
<cfset deleteSomething('1 2')>
Here, the type="numeric" arguments validation (which perhaps is based on the same algorithm as isValid?) doesn't throw with '1 2'. Even worse, cfqueryparam cfsqltype="cf_sql_integer" seems to be using int to convert the value which will end up being 41276.
In other words, deleteSomething('1 2') will delete the entity with id 41276 instead of throwing an exception since the value 1 2 is obviously not numeric.
Now, the only fix I thought of is to perform additionnal argument validation using isValid('integer', ... or a regular expression, but that's a real pain and besides, I never understood why they haven't implemented type="integer"?
Obviously, I also always made the false assumption that cfqueryparam type="cf_sql_integer" would validate that the value passed is a valid integer.
EDIT:
It seems that even isvalid('integer', ... is also not reliable as we can see in
Why isvalid("integer","1,5") = YES?
EDIT2:
I know that I could add additionnal arguments validation for every expected integer argument in every function, however that would require to fix a huge code base in my case and it's also very error-prone. It also makes the built-in argument validation completely useless in this case.
I would rather prefer a solution where I could create and apply an unofficial patch. Is that a realistic option? If so I would like to be pointed out in the right direction.
EDIT3: It doesn't solves all the problems, but CF11 added support for a strictNumberValidation application level configuration.
"Starting from ColdFusion 11, this function evaluates on a more strict
basis. Setting this value to false makes the isValid function to
behave in the older way. This setting effects cfargument, cfparam and
cfform tags wherever integer & numeric validation is used. Based on
this setting, the validation reflects in those tags as well."
This is a variation on that theme from the other question. See this code (or run it on cflive.net):
<cfscript>
s = "1 2";
i = int(s);
v = isValid("numeric", s);
d = createOdbcDate(s);
writeDump([s,i,v,d]);
</cfscript>
s converts to 41276 when calling int(), and when using it as an input for createOdbcDate(), we get:
January, 02 2013 00:00:00 +0000
So "1 2" is being interpreted as "m d", with an implied year of the current year.
Which is utterly stupid. But there you go.
You can use regular expressions to find out if there are any non numeric characters in a given form field:
reFind( "[^\d-]", "1 2")
That will match any character that is not a number, not a -
If you want to check only positive numbers, you can use
reFind( "[^\d]", "1 2")
If this returns true, you do not have an integer.

Why is ListAppend non destructive, while ArrayAppend and StructInsert are both destructive?

I just spent almost an hour trying to figure out an issue with having a list that would always return an empty string. I was using ListAppend just like one uses ArrayAppend or StructInsert, but apparently ListAppend works differently. What, if any, is the reasoning behind having ListAppend work differently from everything else?
<cfset ListAppend(list, item)>
list = ''
<cfset ArrayAppend(array, item)>
array[1] = item
<cfset StructInsert(struct, 'key', item)>
struct.key = item
Possibly because a list is just a big String. Unlike arrays and structures, Strings are immutable, meaning they cannot be changed. To "append" a new value, you need to create an entirely new String. Arrays and structures are mutable. So you can modify them "in place".
Lists in ColdFusion are just Strings and strings in ColdFusion (and Java) are immutable. They cannot be changed. So ListAppend() must return the a new string with the value instead of modifying the existing string.
<cfset newList = listAppend(oldList, "New Value") />