using continue on a loop over a query with the group attribute breaks the loop - lucee

The goal is to use cfcontinue within a nested loop over a query that uses the group attribute.
The expected result is that the loop will continue with the next iteration after calling cfcontinue.
The actual result is that calling cfcontinue behaves like cfbreak. It will stop the inner loop completely and move on the the next iteration in the outer loop.
The situation is as follows:
I have a query result containing something like the following:
testQuery = queryNew( "id , LOJVal" , "numeric , varchar" ,
{
id: [ 1, 1, 1, 2, 2, 2, 3, 3, 3 ] ,
LOJVal: [ "val1" , "", "","val1" , "val2", "val3","val1" , "", "val3" ]
});
this query is a simplification of the result from a query like:
SELECT table.id, leftOuterJoinTable.LOJVal
FROM table
LEFT OUTER JOIN leftOuterJoinTable ON leftOuterJoinTable.id = table.id
where leftOuterJoinTable can contain multiple records for one record in table
when processing this query we want to loop over the records and group by id. We also want to validate the records, and if the record is not valid, we want to continue to the next. This looks something like the following:
<cfset var resultArr = []>
<cfset var outerLoopArr = [1,2,3]>
<cfset var outerLoopVal = "">
<cfloop array="#outerLoopArr#" item="outerLoopVal">
<cfloop query="testQuery" group="id">
<!--- this should iterate once per id --->
<cfset var newVal = {}>
<writeOutput>test</writeOutput>
<cfif true><!--- record invalid --->
<cfcontinue>
</cfif>
<!--- record valid --->
<cfloop>
<!--- do stuff with LOJVal --->
</cfloop>
<cfset arrayAppend(resultArr, newVal)>
</cfloop>
</cfloop>
this code outputs the following:
test test test
yet the expected output is: test test test test test test test test test
for every iteration in the outer loop (3 iterations total) we have 3 iterations in the inner loop (one per group on id) so we'd expect 9 ''test''
When we remove the group attribute of the inner loop, the code behaves as expected: 3 * 9=27 * "test"
At first I thought maybe continue just triggers on the outer loop, so I added a label to the inner loop and continue like so:
<cfset var resultArr = []>
<cfset var outerLoopArr = [1,2,3]>
<cfset var outerLoopVal = "">
<cfloop array="#outerLoopArr#" item="outerLoopVal">
<cfloop query="testQuery" group="id" label="innerloop">
<!--- this should iterate once per id --->
<cfset var newVal = {}>
<writeOutput>test</writeOutput>
<cfif true><!--- record invalid --->
<cfcontinue innerloop>
</cfif>
<!--- record valid --->
<cfloop>
<!--- do stuff with LOJVal --->
</cfloop>
<cfset arrayAppend(resultArr, newVal)>
</cfloop>
</cfloop>
as can be found in the docs https://docs.lucee.org/reference/tags/continue.html. But this doesn't seem to work eiter.
The only thing we can think of that could cause this is that continue will go to index + 1, but that doesnt exist because its grouped, so the next index would be + 3. And then the loop would be considered done because the index doesn't exist.
I haven't found any other information about this.

Related

Using dynamic variables with reReplaceNoCase in a cfloop

I'm using a cfloop to insert dynamic form values into a session struct. I use reReplaceNoCase to sanitize the input and am running into an issue trying to use dynamic form field names in the reReplaceNoCase method. I've tried different naming schemes but can't nail down the syntax. In the posted code, FORM.RTchoice[r] is what is failing and shows the error: Element RTCHOICE is undefined in a Java object of type class [Ljava.lang.String;.. How do I get the form fields to increment by "r" correctly?
<cfloop index="r" from="1" to="#APPLICATION.theCount#">
<cfset a = StructInsert(SESSION.USER_OBJECT, "RTchoice#r#", "#reReplaceNoCase(FORM.RTchoice[r], "[^a-zA-Z0-9.,(\s)-]", "", "all")#", 1)>
</cfloop>
You could use
<cfset a = StructInsert(SESSION.USER_OBJECT, "RTchoice#r#", reReplaceNoCase(FORM['RTchoice#r#'], "[^a-zA-Z0-9.,(\s)-]", "", "all"), 1)>
Or
<cfset a = StructInsert(SESSION.USER_OBJECT, "RTchoice#r#", reReplaceNoCase(FORM['RTchoice'&r], "[^a-zA-Z0-9.,(\s)-]", "", "all"), 1)>
Eg: https://cffiddle.org/app/file?filepath=cafebd5c-f4b5-4fc7-93bf-ff81ca97c234/00f6a79c-7f5f-42b2-b567-8a8a371fa8aa/3c7d3229-f65f-4afe-8538-306d98adf25f.cfm

Is cfstoredproc.statusCode returned?

When ReturnCode=true, it should populate cfstoredproc.statusCode with the status code returned by the stored procedure.
But I can only see cached and executionTime.
Here is an example that I tested and works. Perhaps the issue is with the way the stored procedure is written or with the JDBC Driver you are using to connect with the database.
Another option you have at hand is to not use the tag to call the storedprocedure. You can also call stored procedures using the cfquery tag. This often gives more predictable results.
Here is the code for the test procedure:
CREATE PROCEDURE dbo.PTest
#Somename nvarchar(50) = NULL, -- NULL default value
#SomeResponse nvarchar(50) = NULL OUTPUT
AS
IF #Somename IS NULL
BEGIN
RETURN(1)
END
ELSE
SET #SomeResponse = 'Hello World:' + #Somename;
BEGIN
RETURN(2)
END
Then on the coldfusion side using the cfstoredproc tag:
<cfstoredproc datasource="yourdatasource" procedure="dbo.PTest" returncode="yes">
<cfprocparam type="in" variable="Somename" cfsqltype="cf_sql_varchar" value="John Smith">
<cfprocparam type="out" variable="SomeResponse" cfsqltype="cf_sql_varchar">
</cfstoredproc>
<cfoutput>
#SomeResponse#
<cfdump var="#cfstoredproc#">
</cfoutput>
Using cfquery this would look like this:
<cfquery name="qFoo" datasource="yourdatasource">
SET NOCOUNT ON
DECLARE #SomeResponse varchar(50), #return_code int;
EXECUTE #return_code = dbo.PTest 'John Smith', #SomeResponse = #SomeResponse OUTPUT
SET NOCOUNT OFF
SELECT #return_code as returnCode,
#SomeResponse as someResponse
</cfquery>
<cfoutput>
#qFoo.returnCode# | #qFoo.someResponse#
<cfdump var="#qFoo#">
</cfoutput>

CFspreadsheet looping

I have a question regarding cfspreadsheet....So I'm using cfspreadshseet to create excel spreadsheets for reporting purposes. My page allows a user to select whatever columns from the database to include in the report. So here is an example:
The spreadsheet could look like this:
First Name---Last Name---Organization---Address---City---State---Zip---Concern
Joe Smith Sample 12 main denver co 80513 concerns go here
My question is this, if Joe has more than 1 concern I get multiple rows with joe's info...is there a way I can loop the concerns and only have 1 row for joe?
Thanks,
Steve
Using the "group" feature of cfoutput is perfectly fine for this task. Just to throw out another possibility, you could also generate the list within your database query.
For example, MySQL has the GROUP_CONCAT function. I do not know the structure of your tables, but say you have two tables User and UserConcern, you could use GROUP_CONCAT like so to concatenate the "Concern" values into a single string:
SQLFiddle
SELECT
u.UserID
, u.FirstName
, ... other columns
, GROUP_CONCAT( uc.Concern ) AS ConcernList
FROM UserTable u INNER JOIN UserConcern uc
ON uc.UserID = u.UserID
GROUP BY
u.UserID
, u.FirstName
, ... other columns
For SQL Server, a standard trick is to use XML Path:
SQLFiddle
SELECT
u.UserID
, u.FirstName
, ... other columns
, STUFF( ( SELECT ',' + uc.Concern
FROM UserConcern uc
WHERE uc.UserID = u.UserID
ORDER BY uc.Concern
FOR XML PATH('')
)
, 1, 1, ''
) AS ConcernList
FROM UserTable u
GROUP BY
u.UserID
, u.FirstName
, ... other columns
Then simply generate your spreadsheet as usual.
You need a unique row ID to do this safest, the outer group working with lastname, or something, could cause conflict. UserID is a placeholder variable. Make sure to replace it with an accurate ID name. Of course, a few of these variable names are just guessed.
<cfoutput query ="thequery" group="UserID">
<cfset cList="">
<cfoutput group="concern">
<cfset cList=ListAppend(cList,Concern)>
</cfoutput>
<cfset temp = spreadsheetAddRow(my_spreadsheet,"'#fn#','#ln#',...,'#cList#'">
</cfoutput>
Assuming you want separate lines for each comment, something like this would work:
<cfset current_id = "">
<cfloop query = "my_query">
<cfset next_id = user_id>
<!--- or whatever else forms the primary key --->
<cfif next_id neq current_id>
<cfset current_id = next_id>
<cfset SpreadsheetAddRow(my_spreadsheet, "#first_name#,#last_name#,etc, #comment#">
<cfelse>
<cfset SpreadsheetAddRow(my_spreadsheet, ",,#comment#">
</cfif>
</cfloop>
This is based on the information provided. If you have a unique ID the group attribute would work better.

Using cfc with threads in a coldfusion page

I have a component "bulletin.cfc" which contains lots of functions.
My main page has two threads running on it using the cfthread tag.
Coming from .net I thought I would create two instrances of the component, and use one in each thread. This way they wouldnt mess with each other and I wouldnt need to worry about putting locks in the functions.
<cfset bullObj = new bulletin()>
<cfset bullObj2 = new bulletin()>
Is this correct?
EDIT:
Thanks for the answers so far, I still can't understand a problem that is happening with this though. I have the following code inside two seperate cfthread elements:
<cfset listCount = 1>
<cftry>
<cfquery name="ins" datasource="#datasourceVar#" >
INSERT INTO element_user_shown
(elementid, userid, date_shown)
(
<cfloop list="#elementIDList#" index="lcv">
SELECT #lcv#, #tmpuserid#, GETDATE()
<cfif listCount LT listlen(elementIDList)>
UNION ALL
</cfif>
<cfset listCount = listCount + 1>
</cfloop>
)
</cfquery>
This runs about 70,000 times a night but gets about 3-4 errors each time. Checking the sql that errors it looks like
INSERT INTO element_user_shown
(elementid, userid, date_shown)
(
SELECT 621, 267509, GETDATE()
UNION ALL
SELECT 586, 267509, GETDATE()
UNION ALL
SELECT 594, 267509, GETDATE()
UNION ALL
SELECT 613, 267509, GETDATE()
SELECT 622, 267509, GETDATE()
SELECT 599, 267509, GETDATE()
SELECT 602, 267509, GETDATE()
)
You are correct that your instances are passed by reference so the potential might be there to have a concurrency problem. But if your function arguments are properly paramed and you are not changing properties as a part of the instance (in other words this is an interface and not a bean) you can safely reuse the same instance. Each function call is it's own scope and variables within and returned are for the life of the function call.

The sum of a struct within an array

I am in the process of putting together a shopping cart, I am holding the cart data in an array, inside the array is a struct which holds the individual product information. I need get the sum of the totalprice column within the struct, please see my dump below, I have tried
<cfset carTotal = ArraySum(session.mycart[ "totalPrice" ])> but this creates an error
the value totalprice cannot be converted to a number
Is this because I am using arraysum in a struct?
Any help would be appreciated, thank you.
If mycart was a Query object, it'd be a simple ArraySum(mycart.totalPrice)
Since it's an array of struct, you've got to loop it yourself, which is still rather easy:
<cfset sum = 0>
<cfloop array="#session.mycart#" index="item">
<cfset sum += item.totalPrice>
</cfloop>
<cfdump var="#sum#">
Don't forget to use PrecisionEvaluate() when you need full precision.
As it's an array you'll have to loop yourself, keeping track of the sum.
<cfset cartTotal = 0 />
<cfloop array=#session.mycart# index="i">
<cfset cartTotal += i.totalPrice />
</cfloop>
<cfdump var="#cartTotal#" />
In Coldfusion 10 or Railo 4, you could use the reduce function of Underscore.cfc:
_ = new Underscore();
cartTotal = _.reduce(session.mycart, function(total, item){
return total + item.TotalPrice;
}, 0);
reduce is a common higher-order function that "reduces" a set of values to a single value. In this case, we're "reducing" the price of all items in a collection to a single sum.
Using a well-understood function likereduce, rather than a custom solution, yields more readable code.
Note: I wrote Underscore.cfc
Additional to the answer by Russ, in Coldfusion 11 or Lucee you can simply do arrayReduce() as well. No explicit need for underscore anymore.
cartTotal = arrayReduce(session.mycart, function(total, item){
return total + item.TotalPrice;
}, 0);

Resources