I have a two lists: One that is dynamic, based off of a recordcount of students and the other is the students..i.e., 001,002,003, etc. and 123456 (student data). I need some help being able to randomly assign students one of those numbers. For example, if I have 5 students (123456,234561, etc.), I need to be able to randomly assign 001,002, etc. to those students. So far, hitting a wall. The following is what I have so far:
<cfquery name="testjd" datasource="student">
SELECT SoonerID FROM dbo.CurrentStudentData
WHERE status = 'Student' OR status = 'JD'
</cfquery>
<cfset testList = valueList(testjd.soonerid)>
<cfset totalRecordsJd = #testjd.recordcount#>
<cfloop from="001" to="#totalRecordsJd#" index="i">
<cfset examNo = i>
<cfif len(i) is 1>
<cfset examNo = "00" & i>
<cfelseif len(i) is 2>
<cfset examNo = "0" & i>
</cfif>
<cfif not listFind(usedJDExams, examNo)>
#examNo#<!---is NOT being used!---><br/>
</cfif>
</cfloop>
CF9 makes it a little less fun than later versions. I believe this should work (except for my query mockup).
https://trycf.com/gist/3667b4a650efe702981cb934cd325b08/acf?theme=monokai
First, I create the fake query.
<!---
Simple faked up query data. This is just demo data. I think this
queryNew() syntax was first available in CF10.
--->
<cfscript>
testjd = queryNew("SoonerID", "varchar", [
["123456"],
["564798"],
["147258"],
["369741"]
]);
</cfscript>
Now that I've got a list of students who need tests, I create an array of numbers for those tests.
<!--- Create array of Available Exam Numbers --->
<cfset examNos = ArrayNew(1)>
<cfloop from=1 to=100 index="i">
<cfset examNos[i] = right('00' & i,3)>
</cfloop>
We now combine the two sets of data to get the Exam Numbers assigned to the Student.
<!--- Loop through the query and build random assignments --->
<cfloop query="#testjd#">
<!---Get random exam no.--->
<cfset examNo = examNos[randrange(1,arrayLen(examNos))]>
<!---Build struct of exam assignments--->
<cfset testAssignments[SoonerID] = examNo>
<!---Delete that num from exam array. No reuse.--->
<cfset blah = arrayDelete(examNos,examNo)>
</cfloop>
And this gives us
<cfoutput>
<cfloop collection="#testAssignments#" item="i">
For User #i#, the test is #testAssignments[i]#.<br>
</cfloop>
</cfoutput>
The unused tests are: <cfoutput>#ArrayToList(examNos)#</cfoutput>.
--------------------------------------------------------------------
For User 369741, the test is 054.
For User 147258, the test is 080.
For User 564798, the test is 066.
For User 123456, the test is 005.
The unused tests are:
001,002,003,004,006,007,008,009,010
,011,012,013,014,015,016,017,018,019,020
,021,022,023,024,025,026,027,028,029,030
,031,032,033,034,035,036,037,038,039,040
,041,042,043,044,045,046,047,048,049,050
,051,052,053,055,056,057,058,059,060
,061,062,063,064,065,067,068,069,070
,071,072,073,074,075,076,077,078,079
,081,082,083,084,085,086,087,088,089,090
,091,092,093,094,095,096,097,098,099,100.
A couple of code review notes for the OP code:
1) It's easier to work with arrays or structures than it is to work with a list.
2) cfloop from="001" to="#totalRecordsJd#": from "001" is a string that you are comparing to an integer. ColdFusion will convert "001" to a number in the background, so that it can actually start the loop. Watch out for expected data types, and make sure you use arguments as they were intended to be used.
3) cfif len(i) is 1...: First, it's less processing to build this string in one pass and them trim it - right('00' & i,3). Second (and this is a personal nitpick), is and eq do essentially the same thing, but I've always found it good practice to apply is to string-ish things and eq to number-ish things.
=====================================================================
For CF10+, I would use something like
https://trycf.com/gist/82694ff715fecd328c129b255c809183/acf2016?theme=monokai
<cfscript>
// Create array of random string assignments for ExamNo
examNos = [] ;
for(i=1; i lte 100;i++) {
arrayAppend(examNos,right('00'& i,3)) ;
}
///// Now do the work. ////
//Create a struct to hold final results
testAssignments = {} ;
// Loop through the query and build random assignments
for(row in testjd) {
// Get random exam no.
examNo = examNos[randrange(1,arrayLen(examNos))] ;
// Build struct of exam assignments
structInsert(testAssignments, row.SoonerID, examNo) ;
// Delete that num from exam array. No reuse.
arrayDelete(examNos,examNo) ;
}
</cfscript>
If it is a small query, why not just sort the records (psuedo) randomly using NEWID()? Since the records will already be randomized, you cna use query.currentRow to build the "examNo".
<cfquery name="testjd" datasource="student">
SELECT SoonerID
FROM CurrentStudentData
WHERE status IN ('Student', 'JD')
ORDER BY NewID()
</cfquery>
<cfoutput query="yourQuery">
#yourQuery.SoonerID# #NumberFormat(yourQuery.currentRow, "000000")#<br>
</cfoutput>
This is a formatted comment. After you run this query:
<cfquery name="testjd" datasource="student">
SELECT SoonerID FROM dbo.CurrentStudentData
WHERE status = 'Student' OR status = 'JD'
</cfquery>
Do this:
<cfset QueryAddColumn(testJd, "newcolumn", arrayNew(1))>
Then loop through the queries and assign values to the new column with QuerySetCell.
I am getting an error after upgrade from coldfusionOX to coldfusion 10.
Error Occurred While Processing Request Complex object types cannot be
converted to simple values.
The expression has requested a variable or an intermediate expression
result as a simple value. However, the result cannot be converted to a
simple value. Simple values are strings, numbers, boolean values, and
date/time values. Queries, arrays, and COM objects are examples of
complex values. The most likely cause of the error is that you tried
to use a complex value as a simple one. For example, you tried to use
a query variable in a cfif tag.
It occurs at line " cfloop index="local.thisRight" list="#rights#" ". It seems like ColdFusion does not like the "rights" here.
Anyone can give me some help? Thanks so much.
<cfif local.profile.rights.profile.self is not "">
<cfquery name="local.getAffiliations" datasource="#Request.readerDSN#">
SELECT tblPersonsToAffiliations.affiliationID, tblPersonsToAffiliations.rights, tblAffiliations.affiliationType, tblPersonsToAffiliations.relationshipType
FROM tblPersonsToAffiliations INNER JOIN tblAffiliations
ON tblPersonsToAffiliations.affiliationID = tblAffiliations.affiliationID
WHERE tblPersonsToAffiliations.personID IN (#local.profile.rights.profile.self#)
AND (tblPersonsToAffiliations.archived IS NULL
OR tblPersonsToAffiliations.archived = '')
</cfquery>
<cfif local.getAffiliations.recordCount is not 0>
<cfloop query="local.getAffiliations">
<cfif local.getAffiliations.relationshipType is "interested">
<cfset local.thisRelationshipType = "provisionalMember">
<cfif IsDefined("local.profile.rights.#affiliationType#.#local.thisRelationshipType#")>
<cfset local.profile.rights[affiliationType][local.thisRelationshipType] = ListAppend(local.profile.rights[affiliationType][local.thisRelationshipType], affiliationID)>
<cfelse>
<cfset local.profile.rights[affiliationType][thisRelationshipType] = affiliationID>
</cfif>
<cfelse>
<cfset local.thisRelationshipType = "fullMember">
<cfif IsDefined("local.profile.rights.#affiliationType#.#local.thisRelationshipType#")>
<cfset local.profile.rights[affiliationType][local.thisRelationshipType] = ListAppend(local.profile.rights[affiliationType][local.thisRelationshipType], affiliationID)>
<cfelse>
<cfset local.profile.rights[affiliationType][local.thisRelationshipType] = affiliationID>
</cfif>
<cfif isNull(rights)>
<cfelse>
<cfloop index="local.thisRight" list="#rights#" >
<cfif IsDefined("local.profile.rights.#affiliationType#.#local.thisRight#")>
<cfset local.profile.rights[affiliationType][local.thisRight] = ListAppend(local.profile.rights[affiliationType][local.thisRight], affiliationID)>
<cfelse>
<cfset local.profile.rights[affiliationType][local.thisRight] = affiliationID>
</cfif>
</cfloop>
</cfif>
</cfif>
</cfloop>
</cfif>
</cfif>
A bit earlier in your code you do this:
<cfif local.getAffiliations.relationshipType is "interested">
I think you need the same query name prefix in front of "rights" that is used when evaluating "relationshipType".
Try this:
#local.getAffiliations.rights#
HTH!
I am betting it is failing on this line:
<cfloop index="local.thisRight" list="rights" >
You are attempting to use the string "rights" as a list. My first reaction would be that you need to make that:
<cfloop index="local.thisRight" list="#rights#" >
I created this for some reason neither one of the queries are being updated
<cfloop index="i" from="1" to="#ArrayLen(location)#">
<cfif location[i] NEQ "" AND #locationID# EQ "" >
<cfquery Name="UpdateAddActivity" DATASOURCE="#DS#">
INSERT INTO tblProjectLocations
(
projectID,
locationID
)
VALUES
(
#ProjectName#,
#location[i]#
)
</cfquery>
</cfif>
<cfif location[i] EQ "" AND #locationID# NEQ "" >
<cfquery Name="UpdateAddActivity" DATASOURCE="#DS#">
DELETE FROM tblProjectLocations
WHERE locationID = #locationID# AND projectID = #ProjectName#
</cfquery>
</cfif>
</cfloop>
Am I looping correctly? It doesn't seem like to me that the accumulator is ever going to be updated but loops are done this way every place that I've looked.
Your cfloop tag is fine - you only need index/from/to attributes for a basic loop.
The index variable is incremented (and the loop re-processed) at the position of the closing tag. Or to put it another way, the body code is executed once for each index value between from and to (inclusive).
For information, you can change the default increment (of 1) by specifying the step attribute (though that obviously doesn't make sense for an array loop).
When your code isn't performing as expected, you can debug it with the dump tag:
<cfloop ... >
...
<cfdump var=#locationID# abort />
...
</cfloop>
The abort attribute will stop processing - the loop will not iterate and the current page content will be returned (it's shorthand for specifying cfabort tag separately.
You can use multiple dumps, and the label attribute to help identify which is which, but obviously if using abort attribute make sure only the last one has it.
As has been mentioned locationID isn't defined in the snippet you've provided, so may be the issue.
Sometimes spaces can cause issues - you may want to use the trim function to ensure you're dealing with empty strings (though blindly wrapping trim functions everywhere is ugly - always try if possible avoid introducing spaces).
Shortcut Array Looping
The from/to loop you've got there is only one type of cfloop - there are others.
Specifically, when you don't need the numeric index, there is a shorthand array loop:
<cfloop index="CurLocation" array=#Location# >
...
</cfloop>
Which is equivalent to:
<cfloop index="i" from=1 to=#ArrayLen(Location)# >
<cfset CurLocation = Location[i] />
...
</cfloop>
But without the unused i variable. (If you need the i variable, stick to from/to.)
Note that inside a function you should almost always write index="local.i" and index="local.CurLocation" to ensure the variables are appropriately scoped. This isn't unique to loops - it applies to any tags that create variables. You can also do <cfset var i = 0 /> prior to the loop to do the same thing.
Unrelated Issues
There are a couple of other issues with your code.
Most importantly, the code you're showing is potentially at risk of SQL injection. You should almost never write SQL with bare hashes in, and instead parameterise your queries - using cfqueryparam tag - to solve this. (In the situations where you can't use parameters (e.g. within ORDER BY) make sure you have appropriately sanitized any dynamic text.
Less important -- it doesn't change how the code works, but does betray a lack of experience and understanding -- are the superfluous hashes around locationID. A simplified explanation is that you generally only need #s inside strings (i.e. where the contents would otherwise be treated as text, rather than being a variable's value.)
When in doubt, look at your data.
<cfoutput>
<cfloop index="i" from="1" to="#ArrayLen(location)#">
i is #i# <br>
<cfif location[i] NEQ "" AND locationID EQ "" >
true location is #location[i]# <br>
<cfelse>
false location [i] is is #location[i]# and
locationid is #locationID# <br>
</cfif>
<cfif location[i] EQ "" AND locationID NEQ "" >
same as above
</cfif>
</cfloop>
</cfoutput>
Then you'll know why you are not getting the results you expect.
As long as there are items in your location array, the loop will run and the CFLoop tag will take care of incrementing i.
What I'd guess is happening is that you are checking two conditions within the loop and if neither match, no code will run. You're handling these:
location[i] NEQ "" AND #locationID# EQ ""
location[i] EQ "" AND #locationID# NEQ ""
but not these:
location[i] EQ "" AND #locationID# EQ ""
location[i] NEQ "" AND #locationID# NEQ ""
Could that be it?
I need to join the output of two separate lists together to output in a CFMAIL, and I'm wondering what the best way to approach this is.
I have two form fields: first_name and last_name
The fields have up to 5 names in each. I need to loop through those names and join the first and last names, then output them to unordered list. I am having trouble visualizing what the right approach to accomplish this is.
Can someone suggest a method in CFML (I don't know CFSCRIPT very well).
Thanks!
EDIT: I should have added that both fields will always have the exact same number of entries. Thanks to all that answered -- proof that there are a lot of ways to skin a cat :)
I would do something like
<cfloop from="1" to="#ListLen(firstnames)#" index="i">
#ListGetAt(firstnames,i)# #ListGetAt(lastnames,i)#<br>
</cfloop>
If this were a list of 5000 you would be better off putting it in a structure or an array, but for a list of ~5 this should be sufficient.
I think this would be the easiest way to accomplish this.
<!--- Create a names container --->
<cfset names = "<ul>">
<!--- Fill some dummy containers --->
<cfset first = "thomas,henry,philip,john,rony">
<cfset last = "smith,baker,crowe,ryan,jones">
<!--- Loop through the lists and append them to the container string --->
<cfloop index="name" to="#listLen(first)#" from="1">
<cfset names &= "<li>" & ListGetAt(first,name) & " " & ListGetAt(last,name) & "</li>">
</cfloop>
<cfset names &= "</ul>">
<cfoutput>#names#</cfoutput>
I would add in a check to make sure that your list values exists at each index, otherwise you will get errors. I would also add in a check to loop through whichever list is greater so that you get all values just in case someone doesn't enter exactly 5 in both:
<Cfset firstnames="Matt,Ian,Brandon,Sam,Tom">
<cfset lastnames="Jones,Smith,Weiss">
<!--- SEE WHICH LIST IS LONGER AND SET THAT AS THE ONE THAT WE WILL USE FOR THE LOOP --->
<cfif ListLen(firstnames) gte ListLen(lastnames)>
<cfset primary=firstnames>
<cfelse>
<cfset primary=lastnames>
</cfif>
<cfset myOutput="<ul>">
<cfloop from="1" to="#ListLen(primary)#" index="i">
<Cfset myOutput &= "<li>">
<cfif ListLen(firstnames) gte i>
<cfset myOutput &= ListGetAt(firstnames,i)>
</cfif>
<cfif ListLen(lastnames) gte i>
<cfset myOutput &= " " & ListGetAt(lastnames,i)>
</cfif>
<Cfset myOutput &= "</li>">
</cfloop>
<Cfset myOutput &= "</ul>">
<cfoutput>#myOutput#</cfoutput>
You could use the "list" attribute with CFLOOP although it means combining list functions within the output. Here is an example though of how it could be done and it makes the assumption the two lists will always have the same lengths. If these names are keyed in by users then I might be afraid of if they put in a comma since that would throw things off with any sort of looping.
<cfset lstFirstNames = "John,Bob,Tom,Jeff" />
<cfset lstLastNames = "Smith,Doe,Rodriguez,Horan" />
<cfloop list="#Variables.lstFirstNames#" index="FirstName" />
#FirstName# #ListGetAt(Variables.LastNames, ListFind(Variables.lstFirstNames, FirstName))#<br />
</cfloop>
try:
<cfset lstFirstNames = "John,Bob,Tom,Jeff" />
<cfset lstLastNames = "Smith,Doe,Rodriguez,Horan" />
<cfloop list="#Variables.lstFirstNames#" index="FirstName">
<cfoutput>#FirstName# #ListGetAt(Variables.lstLastNames, ListFind(Variables.lstFirstNames, FirstName))#</cfoutput><br />
</cfloop>
I have a variable <cfset takeFour = 0 >. I then have a loop that I want to run 4 times.
<cfloop query="getVids" condition="takeFour LTE 4">
<cfset takeFour= takeFour + 1/>...
The CF debugger says that there is a attribute validation error for this tag, however this syntax should be correct. Any ideas?
When using cfloop to loop over a query, the acceptable attributes are, query, startRow, and endRow. Condition is not used when query is, which is why the complier is giving you an attribute validation error.
http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=Tags_j-l_10.html
You can't use the condition attribute on a cfloop with a query attribute, you can, however, break out of a loop when your condition is met. You could do this:
<cfloop query="getVids">
<cfset takeFour = takeFour + 1 />
<cfif takeFour GT 4>
<cfbreak />
</cfif>
</cfloop>
Of course, if you just want to loop over the first four rows, you can do this:
<cfloop query="getVids" startrow="1" endrow="4">
</cfloop>