coldfusion query loop not cooperating - coldfusion

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

Related

ColdFusion cfloop issue

I'm running ColdFusion 2016. I have a cfloop which pulls in data from a query, all other ColdFusion queries work fine on the page and if I pull in the same variable in an output outside the loop, it works fine, except the loop is giving me an error. The error output says:
Variable GPS_LATITUDE is undefined.
Which is correct, as in the database there is no GPS_LATITUDE but there is a GPS_LATITUDE1.
I need to add the loop number on the end of the variable so as it loops pulls in the data gps_latitude1, gps_latitude2, gps_latitude3 not just gps_latitude.
My loop code is...
<cfoutput>
<cfloop index="i" from="1" to="3">
<td><p>#gps_latitude[i]#</p></td>
<td><p>#gps_longitude[i]#</p></td>
</cfloop>
</cfoutput>
Any guidance much appreciated.
#elixieru, You can't directly give gps_latitude[i]. which is not check your query which is consider it as Array. I can imagine your scenario and give my sample code about how to get an same columnName with
<cfquery name='test' datasource="mytest">
select * from test
</cfquery>
This is my sample query. It's having column name as address1, address2 etc... I'm going to get the data about address1 & address2 like your scenario.
<cfloop query="test">
<cfloop from="1" to="2" index="i">
<cfset a = test["address#i#"]>
<cfoutput> #a# </cfoutput> <br/>
</cfloop>
</cfloop>
Here I'm looping over the query and so some simple / index loop based on my count ( Address1,2,3,4,5 etc ). For now I'm just use 2 like from 1 to 2.
Here I've store the test['address#i#'] in variable a and print that variable. Now test['address#i#'] it will consider as test.address1
I hope my sample help you more.

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.

Merging a array of queries into a single query

I want to merge an array of queries into one query
I have tried this
<cfquery name="MergedData" dbtype="query">
<cfloop from="1" to="#arrayLen(arData)#" index="k">
SELECT *
FROM arData[k]
<cfif k LT ArrayLen(arData)>UNION</cfif>
</cfloop>
ORDER BY EID
</cfquery>
I get an error that looks like
<br><b>Query Of Queries syntax error.</b><br> Encountered "[k].
Try an alternative way of looping over your array. You need to create your own counter though to figure out the logic of if you need to keep appending 'UNION' to your SQL statement.
<cfset i = 1>
<cfquery name="MergedData" dbtype="query">
<cfloop index="k" array="#arData#">
SELECT *
FROM k
<cfif i LT ArrayLen(arData)>UNION</cfif>
<cfset i++>
</cfloop>
ORDER BY EID
</cfquery>
NB: you wouldn't need to calculate a counter yourself if you're using Railo instead of Adobe CF, as you can then do both index and item like so, as Peter mentioned in the comments above:
<cfloop index="i" item="k" array="#arData#">
If you'd like to accomplish this with functional programming, you could use the Underscore.cfc library (requires CF 10+ or Railo 4+):
// instantiate Underscore library
_ = new Underscore();
// convert the array of queries to a single array of structs
mergedArray = _.reduce(arrayOfQueries, function (memo, query) {
// convert current query to an array of structs
// and concatenate it to the rest of the result
return _.concat(memo, _.toArray(query));
}, []);
// convert the array of structs back to a query
mergedQuery = _.toQuery(mergedArray);
This solution utilizes reduce() to combine the array of queries into a single array of structs. The anonymous function passed to reduce() converts each query in the array of queries to an array of structs using toArray(), then concatenates that array with the rest of the array of structs (the memo value).
Once the array of queries has been converted to a single array of structs, it is a simple matter to convert it back to a query using toQuery() (assuming that is necessary).
Note: I wrote the Underscore library
Try like this:
<cfsavecontent variable="testing">
<cfset j = 1>
<cfloop array="#test#" index="i">
SELECT id FROM #i# LIMIT 10
<cfif j LT ArrayLen(test)> UNION </cfif>
<cfset j++>
</cfloop>
</cfsavecontent>
<cfquery name="qTest" datasource="#application.dsn#">
#testing#
</cfquery>
This is what end up working
<cfquery name="local.qryMergedData" dbtype="query">
<cfloop array="#arguments.Data#" index="k">
<cfset local.currentIndex++>
<cfset setvariable("Data_#local.currentIndex#", k)>
SELECT *
FROM Data_#local.currentIndex#
<cfif local.currentIndex LT local.MaxIndex>UNION</cfif>
</cfloop>
ORDER BY EID
</cfquery>
I really don't like that I am setting a variable inside of a <cfquery>, but at least it is only one query

How can we add a new row in middle of a cfquery result?

I have a query result set from cfquery. I just want to add a new after a particular row number. But When tried each time it inserts the row at the end.
Is there any way I can insert row at the middle of the query?
<cfquery datasource="cse" name="abc">
select * from grade
</cfquery>
<cfset i = 0>
<cfloop query="abc">
<cfset i = i+1>
<cfif i eq 2>
<cfset queryAddRow(abc)>
</cfif>
</cfloop>
You cannot, easily. You have a coupla options.
<cfquery name="resultSet" dbtype="query">
SELECT col1, col2, etc
FROM yourQuery
WHERE [somecondition matching the "top" rows]
UNION
SELECT 'value' AS col1, 'value' AS col2, etc
UNION
SELECT col1, col2, etc
FROM yourQuery
WHERE [somecondition matching the "bottom" rows]
</cfquery>
Or you could simply loop over the original query, building a new query, using queryNew(), queryAddRow() and querySetCell(). At the appropriate point in the loop... add the row you want to insert, then continue adding the rest of them.
There's no elegant way I can think of.
Not knowing the goal you are trying to accomplish, my first advice would be along the lines of the other answers. Add rows that you can subsequently sort using Order By. However, if you really just want to inject a row at a specific position in the existing query, this should do it for you. Note you'll need to define the columns in the QueryNew(), so I've provided a sample case.
<cfquery datasource="cse" name="abc">
select student, teacher from grade
</cfquery>
<cfset abc_new = QueryNew("student,teacher","varchar,varchar")>
<cfloop query="abc">
<!--- ADD NEW DATA TO QUERY AT ROW 2 --->
<cfif CURRENTROW eq 2>
<cfset QueryAddRow(abc_new) />
<cfset QuerySetCell(abc_new,"STUDENT","Tommy Oliver") />
<cfset QuerySetCell(abc_new,"TEACHER","Ms. Appleby") />
</cfif>
<!--- COPY ORIGINAL DATA TO QUERY, ROW NUMBERS NOT PRESERVED --->
<cfset QueryAddRow(abc_new) />
<cfset QuerySetCell(abc_new,"STUDENT",abc.STUDENT) />
<cfset QuerySetCell(abc_new,"TEACHER",abc.TEACHER) />
</cfloop>
<cfdump var="#abc_new#">
The best way would be to create a new query, copying each row from the original but adding the new row at the required point.
Use QueryNew( ) to create the new query.
A bit like copying text files line-by-line and inserting a new line in the middle!
Again, not quite sure you'd need to do this - you could simply add a sortorder column, incrementing in 10s for each row. The new row would then have a sortorder in between the row before and the row after. eg sortorder would be 15 to insert between 10 and 20.
Hope this helps!
I assume row positions in your recordset are based on what you ORDER BY'd in your initial CFQUERY.
Therefore, I'd add the row to the recordset, then do a query of that recordset (query of query) using the same ORDER BY as the initial query, which should then return all rows including your new one, in the (presumed) proper order, in a new recordset.

ArrayMin on query column with a null value

CF8
I was using this line to get the MIN value of a query column. I just noticed a null value in a recordset causes an error. Is there a simply way to tell ArrayMin to skip nulls w/o having to loop the column and load an array with all non-null values?
<cfset temp_data_min = #round(ArrayMin(query_x["some_field"]))#>
thanks!
Building off of what Al said with using the query-of-queries, just adding the Min() call into the query.
<cfquery name="query_x_fixed" dbtype="query">
SELECT Min(some_field) as some_field
FROM query_x
WHERE some_field IS NOT NULL
</cfquery>
<cfset temp_data_min = round(query_x_fixed.some_field)>
Tested to work in CF9
You could loop through your array and create a new array that doesn't contain any null values. Then apply the ArrayMin function to the new array.
<cfset newArray = []>
<cfloop index="x" array="#query_x["some_field"]#">
<cfif x NEQ 'null'>
<cfset arrayAppend(newArray, x)>
</cfif>
</cfloop>
<cfset temp_data_min = round(ArrayMin(newArray))>
NOT TESTED
Simplest way is probably to do a query-of-queries with just that column and remove the nulls.
<cfquery name="query_x_fixed" dbtype="query">
SELECT some_field
FROM query_x
WHERE some_field IS NOT NULL
</cfquery>
<cfset temp_data_min= round(ArrayMin(query_x_fixed["some_field"]))>
(not tested)
You can keep the solution at one line by converting the column to a list then into an array. ListToArray defaults to ignoring empty list items, which is what the null values will be.
<cfset temp_data_min = Round(ArrayMin(ListToArray(ValueList(query_x.some_field, Chr(7)), Chr(7))))>
This should be faster than any of the other proposed solutions and is less code.
I've specified the delimiter for locales that use the comma as the decimal separator in numbers. If you're in the US or another locale that uses the "." then you can remove the delimiter arguments.
Additional functions used:
ListToArray
ValueList
Chr