ColdFusion ListAppend - coldfusion

I am having difficulties trying to use ListAppend.
I have data from a table and this is my code.
<cfquery name="getData" datasource="test">
select * from test;
</cfquery>
Now what I want to do is to make all the values in the column name which I have named it as nm_column into a list using ListAppend.
<cfset dataList = ListAppend('', '#getData.nm_column#')>
<cfoutput>#dataList#</cfoutput>
What this does is that it only shows the 1st value of the nm_colum. I do understand that I am missing the loop part, thats why its only showing me just the 1st value. So how do I loop it and get all the values to it?
I tried this, but it did not work.
<cfset dataList = ListAppend('', '<cfloop query="getData">#getData.nm_column#</cfloop>')>
So can someone please teach me the way to properly write that code?

There's a built in function in ColdFusion that will do this for you.
<cfset dataList = valueList(getData.nm_column)>
As far as the issue with your code, listAppend's first argument is the list you're adding things to. Also, you cannot nest ColdFusion tags like that. The code will not compile.
If you want to loop through something to append to a list, this is what you would do.
<cfset dataList = ''>
<cfloop query="getData">
<cfset dataList = listAppend(dataList, nm_column)>
</cfloop>
This would be terrible for performance though since a string is immutable. If you really needed to add items to a list through a lip I would create an array and then use arrayToList to convert that array to a list.

Related

ColdFusion - Reference variables in cfloop using query generated from UDF

I'm new to ColdFusion and have an interesting question regarding accessing variables inside a cfloop using a query that is generated from a query function.
I know I can create a variable, assign the result of the query function to the variable, and then loop over the variable containing the query result and access the data using the variable name given to the query attribute inside the loop as follows:
<cfscript>
q = createObject("component", "cfc.myDBquery");
result = q.myQuery();
</cfscript>
<cfloop query="result">
<cfoutput># result.MY_DATA #</cfoutput>
</cfloop>
However, consider this example:
<cfscript>
q = createObject("component", "cfc.myDBquery");
</cfscript>
<cfloop query="#q.myQuery()#">
<cfoutput># ???.MY_DATA #</cfoutput>
</cfloop>
Other than just outputting the data using the column name from the query (e.g. MY_DATA), how would I go about referencing this specific query when outputting data in the loop?
FWIW, Adobe shows this type of scenario in their documentation, however fails to show outputting data inside of the loop using this method:
https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-j-l/cfloop-looping-over-a-query.html
I know I'm being a bit neurotic for trying to eliminate one line from my code, I'm just curious if this is even possible while adhering to best practices.
This is a long formatted comment. Here:
<cfscript>
q = createObject("component", "cfc.myDBquery");
result = q.myQuery();
</cfscript>
Creating the object makes the myQuery() function available. It doesn't actually run it. You might be able to do this:
result = createObject("component", "cfc.myDBquery").myQuery();
Next, since you asked about best practices, don't do this:
<cfloop query="result">
<cfoutput># result.MY_DATA #</cfoutput>
</cfloop>
You are executing the cfoutput tag each time through the loop. Instead, do this:
<cfoutput>
<cfloop query="result">
#result.MY_DATA #
</cfloop>
</cfoutput>
or this
<cfoutput query="result">
#MY_DATA#
</cfoutput>
It behaves like a loop. Other comments about best practices are simply opinions. One of mine is that readable code is good code.
I believe there are 2 possibilities. First, ColdFusion doesn't require the scope when looping over a query so you could just reference the field name you need from the query like so:
<cfloop query="#q.myQuery()#">
<cfoutput>#MY_DATA#</cfoutput>
</cfloop>
Knowing non-scoped variables cause confusion and anger, I believe you can reference the name of the original query from your function call. For instance, if your 'myQuery()' function is something like:
<cffunction name="myQuery">
<cfquery datasource="myDS" name="myQry">
SELECT * FROM Names
</cfquery>
<cfreturn myQry>
</cffunction>
Then your can reference 'myQry' like so:
<cfloop query="#q.myQuery()#">
<cfoutput>#myQry.MY_DATA#</cfoutput>
</cfloop>

<cfif> a value is in an array without looping?

This seems like it should be an easy one, but CF seems to have different ways of accomplishing the same thing when it comes to Arrays, so I want to be sure I'm doing it the best way;
I have a simple cfoutput;
<cfoutput query="getusers">
Username: #username# ID:#fnid#
</cfoutput>
I'm also using cfwebsocket elsewhere on the page, and the value of the 'fnid' (which is a session.auth variable) is part of the subscription. A dump of wsGetSubscribers gives me the following;
What I'd like to do is during the output of the 'getusers' query, check to see if the FNID under subscriberinfo.fndid is there - the goal being to add a note to say that user is subscribed at that point.
I know I can do this using a cfloop, but that seems longwinded!
What I'm trying to avoid is having to use cfloop (on the array) within the cfoutput query
So you want a kind of look-up for fnId? How about:
<cfset subscribersById = StructNew()>
<cfloop array="#wsGetSubscribers()#" index="subsciption">
<cfset subscribersById[subsciption.subscriberInfo.fnId] = subsciption>
</cfloop>
<cfoutput query="getusers">
Username: #username# ID: #fnid# Subscribed: #StructKeyExists(subscribersById, fnid)#
</cfoutput>
CF10+ has arrayEach() as well, if you prefer this syntax over the <cfloop>.

Check values in a list are all identical

Ok here's a thing, I have a form which when submitted can only update a table when a particular checkbox form variable values are identical, if one is different then it should proceed to update the table. So basically the user is selecting different rows and hitting the submit button.
So for example a good submission would be
form.claimid = 12,12,12,12,12
a bad submission would be
form.claimid = 1,5,77,33,4,
I'm not sure how to check in a list if all the values in the form.claimid list are identical or not?
I would appreciate any ideas on this.
You can also use CF's native ListRemoveDuplicates() if the version is sufficient (CF10, Railo/Lucee 4)
https://wikidocs.adobe.com/wiki/display/coldfusionen/ListRemoveDuplicates
<cfif ListLen(ListRemoveDuplicates(mylist)) eq 1>
I'll leave this as an alternate means for older CFs.
I'm not sure how to check in a list if all the values in the form.claimid list are identical or not?
There are a couple ways, most of which involve looping, but I'm going to show you a regular expression that can do this. We're going to get the first value using ListFirst, and use a ReReplace on that value to see if every other value matches.
I use a loop here but only as a demonstration.
<cfoutput>
<cfset mylist = "11,22,33,44|44,44,33,44|557,557,557">
<cfloop list="#mylist#" index="m" delimiters="|">
<cfset matchele = listfirst(m)>
<cfset eradicate = rereplace(m,"(?:(?:^|,)#matchele#(?=,|$))+","","ALL")>
"#m#", "#matchele#", "#eradicate#"<br />
</cfloop>
</cfoutput>
If you're stuck on CF 9 or lower, you could loop over the list and put the elements into a struct:
<cfset list_coll = structNew() />
<cfloop list="#mylist#" index="myitem">
<cfset list_coll[myitem] = "dummy" />
</cfloop>
<cfif structCount(list_coll) EQ 1>
<!--- This is good --->
<cfelse>
<!--- This is bad --->
</cfif>
The reason this works is that structs can't have duplicate keys.

coldfusion query loop not cooperating

I'm trying to create a function to create csv files from queries. After I run the query, I'm looping through it and appending each row's fields to a StringBuffer object. To that end, I'm putting the column names into an array:
<cfset indexes = #ListToArray(Arguments.header)# />
where the argument is currently a string like:
"col1, col2, col3...."
I've verified that both the query and the array are what they should be by dumping.
The trouble comes when looping through the query results. Given:
<cfset indexes_length = #ArrayLen(indexes)# />
<cfloop query="query_for_csv">
<cfloop index="i" from="1" to="#indexes_length#">
<cfset attr = #indexes[i]# />
<cfset sbOutput.Append(
"#query_for_csv[attr][query_for_csv.CurrentRow]#") />
</cfloop>
</cfloop>
Only the first value of the first row is output before I get the error message:
[Table (rows 10 columns col1, col2, col3):
[col1: coldfusion.sql.QueryColumn#6f731eba]
[col2: coldfusion.sql.QueryColumn#6ee67e7f]
[col3: coldfusion.sql.QueryColumn#5c6647cb]
is not indexable by col2
If I replace the variable #attr# with the literal "col2":
#query_for_csv['col2'][query_for_csv.CurrentRow]#
then the loop sails through with no problem, and spits out all the values indexed by 'col2'. Any ideas?
I would guess it's the spaces in your header list that is the problem, so probably this would work:
<cfset attr = trim(indexes[i]) />
However, since you're not using them, you probably don't need that and can just do this...
<cfloop query="QueryName">
<cfloop index="CurCol" list=#QueryName.ColumnList# >
<cfset sbOutput.Append(
QueryName[CurCol][QueryName.CurrentRow]
)/>
</cfloop>
</cfloop>
p.s.
You'll note here that there's only one pair of hashes - there only needs to be one pair in your original code snippets too (in the to attribute) - the rest are unnecessary noise.
As has already been said before, try to avoid spaces before or after a list element.
In case you want to compare notes, check out the approach Ben Nadel chose to implement such a Query2CSV converter: http://www.bennadel.com/blog/1239-Updated-Converting-A-ColdFusion-Query-To-CSV-Using-QueryToCSV-.htm

Combining query rows in a loop

I have the following ColdFusion 9 code:
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
</cfloop>
The EXEC executes a stored procedure on the database server, which returns rows of data, depending on what the parameter is. What I am trying to do is combine the queries into one query object. In other words, if it loops 3 times and each loop returns 4 rows, I want a query object that has all 12 rows in one object. How do I acheive this?
You might want to take a different approach (modify your stored procedure to accept multiple arguments or use a list and fnSplit) and return the dataset all at once. However, to directly answer your question, this is how you could combine the queries as you're asking to:
You can use UNION in a Query of Queries to combine all of the datasets.
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs#i#" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
</cfloop>
<cfquery name="combined" dbtype="query">
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
select * from qryGetSPFAQs#i#
<cfif i lt arrayLen(tagArray)>UNION</cfif>
</cfloop>
</cfquery>
A more direct way might be something like this:
<cfset bigQ = queryNew("column")>
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
<cfset queryAddRow(bigQ)>
<cfset querySetCell(bigQ, "column". qryGetSPFAQs)>
</cfloop>
You will need a querySetCell() assignment for each column. Check out the query functions in the live docs for more information.
Here is an out of the box solution, abandoning the StoredProc for a SQL View (I'll explain).
Disclaimer: without seeing the SP source code, I can't tell if my solution fits. I'm assuming that the SP is fairly basic, and I admit I usually prefer the compiled execution of an SP over a view, but the one-time execution of a SQL View should outperform the looping of the SP x times.
First make a view that looks like the SELECT statement in the SP (minus the parameterization, of course -- you'll cover that in a WHERE clause within the CFQUERY of your new view.
Second, set up your loop to do no more than build a data set we're going to use for the WHERE clause. You'll need to use ArrayToList and a little bit of string manipulation to tidy it up, with the end product being a string stored in a single CF variable looking like this:
('ValueOfArrayElement1','ValueOfArrayElement2','Value_And_So_On')
Building the string is pretty easy, using the delimeter attribute of ArrayToList, and after the loop is complete, append a Left Parenthesis & Single Quote to the Left most position of the string; and append a Single Quote & Right Parenthesis to the Right most position in the string.
Now, write the CFQUERY statement to SELECT the columns you need from your view (instead of executing your SP). And instead of passing a parameter to the SP, you're going to put a WHERE clause in the CFQUERY.
Oh, BTW, I am stating you need a SQL View, but the entire SELECT could be built in CFQUERY. Personally, when I have a multi-table JOIN, I like to define that in a SQL View where it's executed more quickly than a JOIN in CFQUERY. Ultimately a StoredProc is even faster, but our WHERE clause is much friendlier to code and read like this than it would be to send into StoredProc without looping in and out of SQL.
It's a good goal to make only one trip out to the database and back if possible. That's why we looped through the array to write a string equating to all the values in the dataset. This way, we'll only execute one query, one time.
SELECT Col1, Col2, Col_etc
FROM SQL_View_Name
WHERE ColumnName in #BigStringWeMadeFromArrayToList#
when our CFQUERY is rendered, the clause will look just like this in SQL:
WHERE ColumnName in
('ValueOfArrayElement1','ValueOfArrayElement2','Value_And_So_On')
So there you have it. Like I said, this is nice because it makes only one trip to the DB, and since we are building a view, the performance will still be pretty good -- better than running a StoredProc 4+ times. (no offense)
I'll must repeat... without having seen the SP code, I'm not sure if this is do-able. Plus, it's kind of odd to abandon a StoredProc for a SQL View, a "lesser" entity in the RDBMS, but I'm sure we will achieve greater performance and I think it's pretty readable, too.