Coldfusion: How to loop over query from function - coldfusion

I have a query that gets called multiple times depending on the the variable vchrStatus
<cffunction name="getGalleriesByStatus" output="no" returntype="query">
<cfargument name="vchrStatus" type="string" required="yes">
<cfquery
name="getGalleries"
datasource="#Application.dsn#">
/// Long complicated query in here
</cfquery>
<cfset var result="#getGalleries#">
<!--- Return it --->
<cfreturn result>
</cffunction>
I tested the function with cfdump and it is outputing the desired results.
Now I want to loop over the results in a cfoutput
<cfoutput query="getGalleriesByStatus('Pending')">
But I'm getting the error: The value of the attribute query, which is currently getGalleriesByStatus('Pending'), is invalid.

<cfoutput> takes the name of the query variable (ie: a string). It does not take an expression which evaluates to a query. So you need this:
<cfset someVar = getGalleriesByStatus('Pending')>
<cfoutput query="someVar">
This is counter-intuitive, but is the case. It's also not in the docs, which sux.

Related

Can I return columns in text form from a function with sql query in coldfusion

I want to write a function in CF, that has a query that returns multiple columns. I researched and read and could only find examples of functions with sql queries only returning the table view of the struct.
How would I write a function in CF with sql queries and output each column in text format
I been googling it and looking on tutorial websites, with no luck
<cffunction name="queryListEmployee" returntype="query" output="false">
<cfargument name="EMPID" type="numeric" default="1"/>
<cfset var listEmployee = ""/>
<cfquery name="local.listContractors" datasource="DB">
SELECT E.FIRSTNAME, E.LASTNAME
FROM EMPLOYEE E
WHERE E.ID = <cfqueryparam value="#arguments.EMPID#" cfsqltype="cf_sql_decimal" scale="0"/>
</cfquery>
<cfreturn local.listEmployee/>
I want to be able to output column names like queryListEmployee.firstname and queryListEmployee.lastname
You've got the idea right. Just a few tweaks:
<cffunction name="getEmployeeByID" returntype="query" output="false">
<cfargument name="EMPID" type="numeric" default="1"/>
<cfquery name="local.employee" datasource="DB">
SELECT E.FIRSTNAME, E.LASTNAME
FROM EMPLOYEE E
WHERE E.ID = <cfqueryparam
value="#arguments.EMPID#"
cfsqltype="cf_sql_integer"/>
</cfquery>
<cfreturn local.employee/>
</cffunction>
Then call this function like so:
<cfset qEmployee = getEmployeeByID(1)>
and output the data like this:
<cfoutput query="qEmployee">
<li>#qEmployee.FIRSTNAME# #qEmployee.LASTNAME#</li>
</cfoutput>
This function will only ever return one record. You can search and figure out how to dynamically adjust the search criteria in order to return multiple records.
You might check out http://www.learncfinaweek.com/ to get learn more of the basics of ColdFusion.

returning the query row with the way the columns are called in sql query

I have a query where I am calling the columns like the way i want, I cannot use ColumnList of query because it sorts the column alphabatically, another thing i need to get the specific row of the query and its associated columns in a structure:
So here is my function which i am trying to bring the columns in the manner i want:
<cffunction name="rowToStruct" access="public" returntype="struct" output="false">
<cfargument name="queryObj" type="query" required="true" />
<cfargument name="row" type="numeric" required="true" />
<cfset var returnStruct = structNew()>
<cfset var colname = "">
<cfset arguments.queryObj = arrayToList(arguments.queryObj.getMeta().getColumnLabels())>
<cfloop list="#arguments.queryObj#" index="colname">
<cfset "returnStruct.#colname#" = arguments.queryObj[colname][arguments.row]>
</cfloop>
<cfreturn returnStruct/>
</cffunction>
before the above change the function was like this below:
<cffunction name="rowToStruct" access="public" returntype="struct" output="false">
<cfargument name="queryObj" type="query" required="true" />
<cfargument name="row" type="numeric" required="true" />
<cfset var returnStruct = structNew()>
<cfset var colname = "">
<cfloop list="#arguments.queryObj.columnList#" index="colname">
<cfset "returnStruct.#colname#" = arguments.queryObj[colname][arguments.row]>
</cfloop>
<cfreturn returnStruct/>
</cffunction>
Mine above one is giving me an error:
You have attempted to dereference a scalar variable of type class java.lang.String as a structure with members.
The getMetadata() function returns the columns in the order they were defined in the original statement. Docs: GetMetaData.
I can't see why your code would produce that error, although I'd do this:
<cfset returnStruct[colname] = arguments.queryObj[colname][arguments.row]>
Can you update your question with the exact error as it displays on the screen, including the code it focuses on, and the line numbers concerned (switch Robust Exception Handling on, if it's not on already)
The reason for the error is because you are overwriting the query object passed into the function. The new code resets the value of arguments.queryObj to a simple string here:
<cfset arguments.queryObj = arrayToList(arguments.queryObj.getMeta().getColumnLabels())>
Later in the code, you try and use the arguments.queryObj variable as if it were still a query object. Obviously, this causes an error because the variable now represents a string:
<cfset "returnStruct.#colname#" = arguments.queryObj[colname][arguments.row]>
The solution is to use a different variable to store the list of column names instead. Since it will also be function local variable, just be sure to scope it properly with local or var.
That said, as Adam mentioned in his answer, the feature you need is already supported using one of the built-in functions. So there is no really need to use the undocumented methods of the coldfusion.sql.QueryTable query class IMO. GetMetaData(queryObject) returns an array of structures containing two keys: Name (column name), IsCaseSensitive (boolean). To iterate through it, use an "array" loop instead of a "list" loop:
...
<cfset var returnStruct = {}>
<cfset var col = "">
<cfset var colArray = getMetaData(arguments.queryObj)>
<cfloop array="#colArray#" index="col">
<cfset returnStruct[col.Name] = arguments.queryObj[col.Name][arguments.row]>
</cfloop>
...
Update 1:
I cannot use ColumnList of query because it sorts the column
alphabatically
The bigger issue here is that CF structures are not ordered. Even if you add the values to the structure in the proper order, that order will not be maintained. You either need to address that in the calling code (ie use GetMetaData(query)) OR have the cffunction return both the ordered column names AND the row data. The latter seems a bit redundant, but it all depends on how you are using the function in your application.
Update 2:
IF you really do need a sorted structure .. there are also some java classes that do maintain the insertion order, such as a LinkedHashMap. Since it implements java.util.Map it can be used like a CF structure (in most ways).
<cfset var returnStruct = createObject("java", "java.util.LinkedHashMap").init()>
Runnable Example on trycf.com
NB: While having access to java objects is great, a common mistake is to forget that java is strongly typed, unlike CF. There are often subtle nuances that can easily bite you if you are not aware of them. So keep that in mind ... and be sure to read the API.

Coldfusion OutOfMemoryError (CF9/Wheels)

I have a function which loops over a query and updates a database row for each item. After about 7000 iterations it's throwing an out of memory error - Java heap space.
Is there anything obviously wrong with this code ?
<cfloop query=loc.fixItems>
<cfset loc.count = loc.count + 1>
<cfset var categoryName = loc.fixItems.categoryName>
<cfinvoke component="Item" method="updateCode"
itemId="#loc.fixItems.itemId#" code="#loc.fixItems.newCode#"/>
<!--- Increment counter for category --->
<cfif structKeyExists(categoryMap, categoryName)>
<cfset var inc = structFind(categoryMap, categoryName) + 1>
<cfset structUpdate(categoryMap, categoryName, inc)>
<cfelse>
<cfset structInsert(categoryMap, categoryName, 1)>
</cfif>
</cfloop>
and in the update component:
<cffunction name="updateCode">
<cfargument name="itemId" type="numeric" required="yes">
<cfargument name="code" type="string" required="yes">
<cfset var loc = {}>
<cfquery name="loc.update">
update items
set code = <cfqueryparam value="#code#">
where id = <cfqueryparam value="#itemId#">
</cfquery>
</cffunction>
Don't use cfinvoke to create your Item component every iteration of your fixItems query. Create it once before that using createObject and simply call the updateCode method each time directly on the object.
The following can be done:
Change your <cfqueryparam> to use the appropriate cf_sql type. Are code and id really strings?
Don't give your <cfquery> a name. You are not keeping the result anyway. var loc doesn't help either
Bump up you memory to the JVM Addtional approach Use Java 7 and G1GC
Every 100 to 1000 iterations do a forced Garbage Collect
Update your data in bulk. XML based table variables can do this.
Make your function silent
Consider ORM on this

how to determine the rollback cause in a cftransaction

I have set of inserts that are wrapped in a <cftransaction> block, and I am getting a error and the insert is being rolled back.
Here is the code in question stubbed for space:
<cffunction name="InsertTCUV" access="public">
<cfargument name="vehicle required="true" type="xml" />
//Parsing the xml document here
<cftransaction>
<cfquery name="TCUVinsert datasource="mydb">
INSERT INTO tcuv
VALUES(...)
<cfquery>
<cfquery name="qLatestTCUVID" datasource="mydb">
SELECT TOP 1 tcuv_id FROM dbo.tcuv ORDER BY tcuv_id DESC
</cfquery>
<cfset curTCUVID = qLatestTCUVID.tcuv_ID>
<cfset optionsResult = insertOptions(curTCUVID,vehicle>
<cfset imagesResult = insertImages(curTCUVID,vehicle)>
<cfset standardFeaturesResult = insertStandardFeatures(curTCUVID,vehicle
</cftransaction>
</cffunction>
<cffunction name="insertOptions" access="private">
<cfargument name="TCUVID required="true type="numeric" />
<cfargument name="vehicleInfo" required="true" type="xml" />
<cfset var result = "good">
<cftry>
<cfset optionNode = xmlSearch(arguments.vehicleInfo[1], "p:RemarketingOption">
<cfloop index="i" from="1" to="#arrayLen(optionNode)#">
<cfset optionNodeNotes = XmlSearch(optionNode[#i#], "p:OptionNotes")>
<cfset optionNotes = "">
<cfloop index="j" from="1" to="#ArrayLen(optionNotesNodes)#">
<cfoutput>
<cfset optionNotes = optionNotes & " " & #optionNotesNodes[j].xmlText#>
</cfoutput>
</cfloop>
<cfquery name="insertOptions" datasource="mydb">
INSERT INTO dbo.tcuv_options
VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.TCUVID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionID")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionTypeCode")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionShortDescription")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#optionNotes#">
)
</cfquery>
</cfloop>
<cfcatch type="database">
//dumping cfcatch.* information
<cfset result = "error"
</cfcatch>
</cftry>
<cfreturn result>
</cffunction>
This whole thing is in a loop on the calling page, and everything works fine the first time through the loop. One the second pass, the TCUVInsert works, but I get to the insertOptions function, a coldfusion error is thrown saying that, variable insertOptions is undefined, and when I get the database, the second row isn't there, which tells me there was an error and the insert rolled back. So, there is an error with the insert of the options, and coldfusion isn't giving me the right error to diagnose it. So either I have to look in the database logs, which apparently are not setup, or try to extract the error from the cftransaction block, which I'm not sure how to do.
coldfusion 9, sql server 2008 r2
Any thoughts?
<cffunction name="insertOptions" access="private">
You are overwriting the function by using the same name for a query variable:
<cfquery name="insertOptions" datasource="imports">
Interestingly it is all because the query name was not var scoped. Functions are stored in the variables scope of the component. So by failing to localize the query name, you end up overwriting the function stored in variables.insertOptions when you run the query. Because insert statements do not return a resultset, that variable ends up being undefined. Hence the error. In this case the solution is to either scope the query name, or better yet remove it entirely (since it is not populated anyway).
Just one more reason to always var/local scope function variables - yes, query names too!

display the query result

I am new to coldfusion and I am stuck with looping a query within a function. For example, I have a function within which has a query which returns the names which starts with 'a'. but i am able to get only one value (first value) from the database.Actually in the db we have more than 1 values for this query.How should i loop the query within the function?
Any help is appreciated...
<cffunction name="getNames" returntype="any">
<cfargument name="letter" required="true">
<cfquery name="getNamesfrmDB" datasource="test">
select * from employee where firstname like '#arguments.letter#%'
</cfquery>
<cfreturn getNamesfrmDB/>
</cffunction>
<cfoutput>#getNames('a').firstname#</cfoutput>
Thanks in advance...
ahh. I ready your question wrong... disregard previous answer..
You are passing the query straight out of the function, so it will be coming out as a query and you can treat it as such.
Use query="qname" in your cfouptut
<cffunction name="getNames" returntype="any">
<cfargument name="letter" required="true">
... your query ..
<cfreturn getNamesfrmDB/>
</cffunction>
<!---call the function--->
<cfset names = getNames('a')>
<!---now loop over the results using cfoutput--->
<cfoutput query="names">
<p>#firstname#</p>
</cfoutput>
<!---OR ALTERNATIVELY, as you can't use cfoutput inside cfoutput.. so if you are already inside a cfouput, you can also output query results using cfloop--->
<cfoutput>
..some other stuff...
<cfloop query="names">
<p>#firstname#</p>
</cfloop>
..some other stuff..
</cfoutput>