returning a struct of queries - coldfusion

If a series of queries are created like so:
<cfloop list="#platform_list#" index="x">
<cfquery name="#trim(x)#" dbtype="query">
<!--- stuff to build the query --->
</cfquery>
</cfloop>
and I then return the queries in a struct like so:
<cfset queries_RET = StructNew() />
<cfloop list="#platform_list#" index="x">
<cfif StructKeyExists(args, #trim(x)#)>
<!--- here's where I think things go horribly wrong --->
<cfset queries_RET[x] = #x# />
</cfif>
</cfloop>
<cfreturn queries_RET />
And then when returned to the calling function as "graphData", I try to access it like so:
<cfloop list="#platform_list#" index="x">
<cfif StructKeyExists(url, x) and StructKeyExists(graphData, x)>
<cfloop query="graphData[x]">
I get an error at the last line:
Attribute validation error for tag cfloop.
The value of the attribute query, which is currently graphData[x], is invalid.
The value of the struct at graphData[x] is a string having the same value as the element name... so what do I do to assign the query with that name instead? I'm sure it's ridiculously obvious. :(
EDIT:
I'll give the answer to Shawn, although I finally figured out what my underlying issue(s) are. First, I didn't realize that
<cfset queries_RET[x] = #x# />
doesn't actually assign the query to the element at x, but instead a reference to it. To assign the actual query object, I need to do this:
<cfset queries_RET[x] = #Evaluate(x)# />
Second, when the struct is returned to the calling function, calling
<cfloop list="#Application.platform_list#" index="x">
<cfloop query="#graphData[x]#">
didn't work because the query attribute of cfloop is looking for a reference to a query object --- and the query object it was looking for didn't exist since it hadn't been returned.
Finally, now that I am actually returning a valid query object, that query attribute still doesn't work, because now graphData[x] isn't a reference. To make it work, I have to first assign a reference, and use that as the query attribute in the cfloop:
<cfloop list="#Application.platform_list#" index="x">
<cfset thisQuery = #graphData[x]#>
<cfloop query="thisQuery">
I think that my fundamental problem was not understanding the the query attribute isn't an actual query object, but a reference to one instead. It's been a learning curve!

Try
<cfloop list="#platform_list#" index="x">
<cfif StructKeyExists(url, x) and StructKeyExists(graphData, url[x])>
<cfset q = graphData[x]>
<cfloop query="q">
If this throws
The value of the attribute query, which is currently q, is invalid
Then you should cfdump the q make sure it is a query object.

The <CFLOOP> query attribute takes the name of a query (string), not an actual query object.
When you pass something into that attribute, CF expects it to be a string.
If it's a literal, CF will expect it to be the name of a valid query.
If it's a variable, CF will expect the value of the variable to be equal to the name of the query.
Therefore, the output you ultimately want is:
<cfloop list="#platform_list#" index="x">
<cfloop query="#x#">
Here is a full snippet of code that confirms this, which you may refer to/compare to for your own code:
<cfset query_a = QueryNew('id,name,pass') />
<cfset QueryAddRow(query_a)>
<cfset QuerySetCell(query_a,'id',1)>
<cfset QuerySetCell(query_a,'name','joe')>
<cfset QuerySetCell(query_a,'pass','joe123')>
<cfset query_b = QueryNew('id,name,pass') />
<cfset QueryAddRow(query_b)>
<cfset QuerySetCell(query_b,'id',4)>
<cfset QuerySetCell(query_b,'name','pete')>
<cfset QuerySetCell(query_b,'pass','pete123')>
<cfset query_c = QueryNew('id,name,pass') />
<cfset QueryAddRow(query_c)>
<cfset QuerySetCell(query_c,'id',7)>
<cfset QuerySetCell(query_c,'name','frank')>
<cfset QuerySetCell(query_c,'pass','frank123')>
<cfset platform_list = 'query_a,query_b,query_c' />
<cfloop list="#platform_list#" index="x">
<cfloop query="#x#">
<cfoutput>#id# #name# #pass#</cfoutput><br/>
</cfloop>
</cfloop>
You'll notice in this snippet that if you change the query attribute back to "x" (rather than #x#) you'll produce the exact same error you are encountering now.
Why?
Answer: platform_list is a comma-delimited list of strings (which x becomes via the loop), not a comma-delimited list of Query objects.

Related

Coldfusion limit cfloop to 10

I'm working with quite a large array of items in a cfloop. I would like to pare this down and add pagination. Is there anyway in my cfloop to limit the array to the first 10?
I have
<cfloop array="#qryItems#" index="index">
I have tried turning it into a condition loop with no luck and a few other things. I haven't touched coldfusion in a while and am a little rusty. Google is not helping haha
I have tried
<cfloop from="1" to="10" array="#qryItems#" index="index">
and have also tried max_rows
<cfloop maxrows="10" array="#qryItems#" index="index">
each time I get the error message
"Attribute validation error for tag CFLOOP."
<cfloop from="1" to="10" index="index">
<!--- Then do your business with array elements qryItems[index], that is, with qryItems[1], qryItems[2],..., qryItems[10] --->
</cfloop>
There is no combination of attributes for cfloop to accomplish what your expecting. As BKBK suggested, you'll need to use a from/to loop to output a select group of records. If I understand your requirements correctly, I would update your cfloop with a new index variable as well, and then set the old variable by referencing the array element.
The two cfloops below output the same data, with the second displaying only the records in the pagination range.
<cfset qryItems = [1,2,3,4,5,6,7,8,9,10,'a','b','c','d'] />
<cfoutput>
<!--- Current loop: Outputs all records --->
<cfloop array="#qryItems#" index="index">
#index#
</cfloop>
<cfset paginationStart = 1 />
<cfset paginationEnd = 10 />
<!--- Only the range of of records requested --->
<cfloop from="#paginationStart#" to="#paginationEnd#" index="indexNumber">
<cfset index = qryItems[indexNumber] />
<!--- code remain the same --->
#index#
</cfloop>
</cfoutput>

CFSPREADSHEET read in Excel file to query can't access columns in result

I'm trying to automate processing of spreadsheets using ColdFusion 10 and CFSpreadSheet. So far I can read the file in and dump the query object without any issue.
<cfspreadsheet action="read" src="#theFile#" query="qData" headerrow="1" columns="1,8,9,11,33"/>
<cfdump var="#qData#"/>
The issue comes when I try to work with the data. If I do something like:
<cfoutput query="qData" maxrows="#qData.RecordCount#">
#qData.GROUP#<br />
#qData.DOB#<br />
</cfoutput>
I immediately get an error: "Element GROUP is undefined in QDATA."
If I dump the qData.ColumnList I get a column list:
BTBN(002),DOB ,GROUP ,MEMBER/DEPENDENT NAME ,REL
Is it the spaces and ( )s messing it up? If so, how can I get rid of those and update the column names either when reading in the file or immediately thereafter?
I initially thought it may be due to "group" being a reserved SQL keyword. (Don't try using that columnname in a query-of-queries.)
Instead of sanitizing the first row values for known values and resaving the file, you should loop through and use isValid("variablename", ColumnName) to determine if the column name is valid and then use the RenameColumn UDF to rename it. We prefer this method as it's critical that we don't modify the client's original Excel file (especially since Adobe ColdFusion has some bugs when writing files and will likely mess up other worksheets and/or formatting within the file.)
An easy way to remove illegal characters is to use rereplace:
ReReplace(NewColumnName, "[^a-zA-Z0-9!]", "", "ALL")
But you'll also need to ensure that the new column name isn't empty, starts with a letter and isn't already used for another column. If you are expecting the columns to be in a certain order, you could simply safely rename them "col_1" (or use numbering as a default fallback for any non-unique and/or illegal column names.)
Here's the RenameColumn UDF taken from this 2011 blog post:
http://www.neiland.net/blog/article/using-java-to-rename-columns-in-a-coldfusion-query-object/
<cffunction name="renameColumn" access="public" output="false" returntype="query" hint="Uses java to rename a given query object column">
<cfargument name="queryObj" required="true" type="query">
<cfargument name="oldColName" required="true" type="string">
<cfargument name="newColName" required="true" type="string">
<!--- Get an array of the current column names --->
<cfset var colNameArray = queryObj.getColumnNames()>
<cfset var i = 0>
<!--- Loop through the name array and try match the current column name with the target col name--->
<cfif arrayLen(colNameArray)>
<cfloop from="1" to="#arrayLen(colNameArray)#" index="i">
<!--- If we find the target col name change to the new name --->
<cfif compareNoCase(colNameArray[i],arguments.oldColName) EQ 0>
<cfset colNameArray[i] = arguments.newColName>
</cfif>
</cfloop>
</cfif>
<!--- Update the column names with the updated name array --->
<cfset queryObj.setColumnNames(colNameArray)>
<cfreturn queryObj />
</cffunction>
(In case folks do not read the comments ...)
The parenthesis and slashes will be problematic as they do not conform to the standard variable name rules. The simplest option is to use the "columnNames" attribute to specify valid column names instead. (Also, nothing to do with your question, but if you want to exclude the header row, use excludeHeaderRow="true")
<cfspreadsheet action="read" src="c:\path\file.xlsx"
query="qData"
columnNames="BTBN_002,DOB,GROUP_NAME,MEMBER_DEPEND_NAME,REL"
excludeHeaderRow="true"
headerrow="1"
... />
In most cases, you can also access invalid column names using associative array notation. However, using the "columnNames" attribute is simpler/cleaner IMO.
<cfoutput query="qData" maxrows="#qData.RecordCount#">
#qData["BTBN(002)"][currentRow]#<br />
....
</cfoutput>
SOLUTION - there were multiple spaces in the column names and ColdFusion does not tolerate that. This could probably be done better with a regex, and I'll work on that next but here's the quick and dirty solution.
<cfset colNameArray = qData.getColumnNames() />
<cfloop from="1" to="#arrayLen(colNameArray)#" index="i">
<cfset colNameArray[i] = colNameArray[i].replace(' ','') />
<cfset colNameArray[i] = colNameArray[i].replace('(','') />
<cfset colNameArray[i] = colNameArray[i].replace(')','') />
<cfset colNameArray[i] = colNameArray[i].replace('/','') />
</cfloop>
<cfset qData.setColumnNames(colNameArray) />

ColdFusion Query-of-Queries Wildcard LIKE condition not working with single quotes?

I have a query-of-queries that performs a LIKE condition on a variable string:
When the variable contains a single word that includes a single quote, some results are returned, but not all:
<cfset _myVar = "Women's" />
<cfquery name="_qData" dbtype="Query">
SELECT
ID
FROM MyQoQ
WHERE NAME LIKE '%#_myvar#%'
OR DESCRIPTION LIKE '%#_myvar#%'
</cfquery>
When the variable contains more than one word, and one of those words includes a single quote, no records are returned:
<cfset _myVar = "Women's Initiative" />
<cfquery name="_qData" dbtype="Query">
SELECT
ID
FROM MyQoQ
WHERE NAME LIKE '%#_myvar#%'
OR DESCRIPTION LIKE '%#_myvar#%'
</cfquery>
I've tried PreserveSingleQuotes() as well as wrapping the varaibles with CFQUERYPARAM, but, to no avail - I get the same results.
Is there a way to make this work?
Adding in a repro case
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfquery name="res" dbtype="query">
SELECT *
FROM myQuery
WHERE hello LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%$what's up%">
</cfquery>
<cfdump var="#res#">
Railo 4.1.1.009 - returns both results (rows 2 and 3)
ColdFusion 10,0,13,287689 - returns no results
If I change my SQL to
WHERE hello LIKE '%what''s up%'
I still get no results
aarh!! a classic case of royal pain in the rear.
To solve this, you have to add an extra ' to every ' in your search term, there by escaping it.
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfset x = "what's up" />
<cfquery name="res" dbtype="query">
SELECT *
FROM myQuery
WHERE hello LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="#replace(x, "'", "''", "all")#%">
</cfquery>
<cfdump var="#res#">
if you do this directly, as you mentioned,
WHERE hello LIKE '%what''s up%'
the parser is going bonkers. But, if you pass the value via a function return value, the run time assignment of values via a variable somehow makes the parser happy.
I remember using such tricks in sending multiple SQL statements delimited with ';' in cfquery. Directly writing
"DECLARE x NUMBER; SELECT 2 INTO x FROM DUAL;"
inside cfquery fails, but assigning them to a string and then sending the string as a return value for any string manipulator function (lcase, ucase, etc) worked perfectly.
Note: the problem is solved, but if my explanation and approach is diff, feel free to correct and comment.
I'm running ColdFusion 10 u13.
Modifying your repo code this seemed to work:
<cfset myQuery = queryNew('hello')>
<cfset queryAddRow(myQuery,5)>
<cfset querySetCell(myQuery,"hello","what up",1)>
<cfset querySetCell(myQuery,"hello","what's up",2)>
<cfset querySetCell(myQuery,"hello","what's up friends",3)>
<cfset querySetCell(myQuery,"hello","what u",4)>
<cfset querySetCell(myQuery,"hello","what",5)>
<cfdump var="#myQuery#">
<cfquery name="res" dbtype="query">
SELECT *
FROM [myQuery]
WHERE [hello] LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%what''s up%">
</cfquery>
Note the double single quotes in the <cfqueryparam> tag. Like Dan I would have thought that the <cfqueryparam> tag would have taken care of this for you automatically. Perhaps this is a bug in QoQ?

Set a variable dynamically into a structure via CFLOOP

I am trying to set a variable dynamically into a structure via CFLOOP. I have read Ben Nadal's blog post but cant seem to get the assignment correct. I would like to use dot notation and make the VIN a sub structure of values.
Here is my code:
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE (VIN = #VIN#)
</cfquery>
<cfset carsStruct= StructNew()>
<cfset carsStruct.[VIN].MAKE = '#carsQue.MODEL#'>
<cfset carsStruct.[VIN].MODEL = '#carsQue.MAKE#'>
</cfloop>
Any guidance would be greatly appreciated,
Thanks
Running a query inside a loop is almost always a bad idea. In your case, a better option would be:
<cfif ListLen(valuelist(vins.vin)) gt 0>
<cfquery name=CarsQue datasource = "carsData">
select vin, model, make, etc
from carsDB
where vin in (<cfqueryparam cfsqltype="cf_sql_varchar"
value="#valuelist(vins.vin)#" list="true">)
</cfquery>
<cfset carsStruct = StructNew()>
<cfloop query="carsQue">
code for your struct
</cfloop>
<cfelse>
code for vins query returning no data
</cfif>
Better yet would be to get all the data with one query. You didn't provide enough information to determine if this was possible, but it often is.
Create a structure outside loop and and setting variable within loop can solve the problem. in a current scenario each time loop run its create a new structure.
you can do some thing like this
<cfset carsStruct= StructNew()>
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE VIN = <cfqueryparam cf_sql_type="cf_sql_varchar" value="#VINs.VIN#">
</cfquery>
<cfset carsStruct[VINs.VIN].MAKE = carsQue.MODEL>
<cfset carsStruct[VINs.VIN].MODEL = carsQue.MAKE>
</cfloop>
Based on the limited information you've given you should be able to run one query and loop through that to add to your struct.
<cfset carsStruct= {}> //new struct
<cfif VINs.RecordCount> //your VINs query has records
<cfquery name="carsQueue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
// Quoted list of all your VINs. cfqueryparam prevents against SQL injection
WHERE VIN IN (<cfqueryparam cf_sql_type="cf_sql_varchar" value="#ValueList(VINs.VIN)#" list="true">
</cfquery>
<cfloop query="carsQueue">
<cfset carsStruct.[carsQueue.VIN].MAKE = carsQueue.MODEL>
<cfset carsStruct.[carsQueue.VIN].MODEL = carsQueue.MAKE>
</cfloop>
<cfelse>
// if VINs query return nothing a blank struct will be returned.
//You do NOT need this <cfelse> unless you are returning something when the query is blank
</cfif>

Can I get a query row by index in ColdFusion?

I want to get a specific row in a ColdFusion Query object without looping over it.
I'd like to do something like this:
<cfquery name="QueryName" datasource="ds">
SELECT *
FROM tablename
</cfquery>
<cfset x = QueryName[5]>
But it's giving me an error saying that the query isn't indexable by "5". I know for a fact that there are more than 5 records in this query.
You can't get a row in CF <= 10. You have to get a specific column.
<cfset x = QueryName.columnName[5]>
It's been 8 years since I posted this answer, however. Apparently CF11 finally implemented that feature. See this answer.
This can now be accomplished in coldfusion 11 via QueryGetRow
<cfquery name="myQuery" result="myresult" datasource="artGallery" fetchclientinfo="yes" >
select * from art where ARTID >
<cfqueryparam value="2" cfsqltype="CF_SQL_INTEGER">
</cfquery>
<cfdump var="#myQuery#" >
<cfset data = QueryGetRow(myQuery, 1) >
<cfdump var="#data#" >
I think there is a simpler solution...
I am guessing you know your column names and only want this column or that one. Then you don't need to put the whole row in a struct. You can reference the query by row number (remember its 1 based not 0).
yourQueryName["yourColumnName"][rowNumber]
<cfoutput>
#mycontacts["Name"][13]#
#mycontacts["HomePhone"][13]#
</cfoutput>
You have to convert the query to a struct first:
<cfscript>
function GetQueryRow(query, rowNumber) {
var i = 0;
var rowData = StructNew();
var cols = ListToArray(query.columnList);
for (i = 1; i lte ArrayLen(cols); i = i + 1) {
rowData[cols[i]] = query[cols[i]][rowNumber];
}
return rowData;
}
</cfscript>
<cfoutput query="yourQuery">
<cfset theCurrentRow = GetQueryRow(yourQuery, currentRow)>
<cfdump var="#theCurrentRow#">
</cfoutput>
Hope this points you in the right direction.
I know I come back to this thread any time I Google "cfquery bracket notation". Here's a function I wrote to handle this case using bracket notation. Hopefully this can help someone else too:
<cffunction name="QueryGetRow" access="public" returntype="array" hint="I return the specified row's data as an array in the correct order">
<cfargument name="query" required="true" type="query" hint="I am the query whose row data you want">
<cfargument name="rowNumber" required="true" hint="This is the row number of the row whose data you want">
<cfset returnArray = []>
<cfset valueArray = []>
<cfset cList = ListToArray(query.ColumnList)>
<cfloop from="1" to="#ArrayLen(cList)#" index="i">
<cfset row = query["#cList[i]#"][rowNumber]>
<cfset row = REReplace(row, "(,)", " ")>
<cfset returnArray[i] = row>
<cfset i++>
</cfloop>
<cfreturn returnArray>
</cffunction>
The REReplace is optional, I have it in there to cleanse commas so that it doesn't screw up the arrayToList function later on if you have to use it.
I wanted to extract a single row from a query, and keeping the column names (of course). This is how I solved it:
<cffunction name="getQueryRow" returntype="query" output="no">
<cfargument name="qry" type="query" required="yes">
<cfargument name="row" type="numeric" required="yes">
<cfset arguments.qryRow=QueryNew(arguments.qry.columnlist)>
<cfset QueryAddRow(arguments.qryRow)>
<cfloop list="#arguments.qry.columnlist#" index="arguments.column">
<cfset QuerySetCell(arguments.qryRow,arguments.column,Evaluate("arguments.qry.#arguments.column#[arguments.row]"))>
</cfloop>
<cfreturn arguments.qryRow>
</cffunction>
Methods previously described for obtaining query data by column name and row number (variables.myquery["columnName"][rowNumber]) are correct, but not convenient for getting a full row of query data.
I'm running Railo 4.1. And this is a cool solution. Too bad this can't be done the way we would want outright to get a full row of data, but the following method allows us to get what we want through a few hoops.
When you serializeJSON(variables.myquery) it changes the query to a JSON formatted cfml struct object with two items: "Columns" and "Data". Both of these are arrays of data. The "data" array is a two-dimensional array for rows and then columnar data.
The issue is that now we have an unusable string. Then if we re-serialize it it's NOT a query, but rather usable regular struct in the format described above.
Assume we already have a query variable named 'variables.myquery'. Then look at the following code:
<cfset variables.myqueryobj = deserializeJSON(serializeJSON(variables.myquery)) />
Now you get the two dimensional array by getting this:
<cfset variables.allrowsarray = variables.myqueryobj.data />
And you get one query row array by getting this:
<cfset variables.allrowsarray = variables.myqueryobj.data[1] />
OR the last row this way:
<cfset variables.allrowsarray = variables.myqueryobj.data[variables.myquery.recordCount] />
And you can get individual column values by column order number iteration:
<cfset variables.allrowsarray = variables.myqueryobj.data[1][1] />
Now this might be slow and possibly unwise with large query results, but this is a cool solution nonetheless.
Check out the documentation for queryGetRow. It accepts a query object and an index of the row with the first row being referenced with the index of 1 (NOT 0) The index used this way is required to be a positive integer.
<cfquery name="QueryName" datasource="ds">
SELECT *
FROM tablename
</cfquery>
<!---
This would retrieve the first record of the query
and store the record in a struct format in the variable 'x'.
--->
<cfset x = queryGetRow(QueryName, 1) />
<!---
This is an alternative using the member method form of queryGetRow
--->
<cfset x = QueryName.getRow(1) />