Coldfusion - variable field name when looping through database query results
Don't use Evaluate()
for things like that! It's slow and should be avoided.
<cfloop index="i" from="1" to="4">
<cfset foo = query["foo" & i][query.CurrentRow]>
</cfloop>
Or, if you like:
<cfloop index="i" from="1" to="4">
<cfset foo = query["foo#i#"][query.CurrentRow]>
</cfloop>
Evaluate()
is for evaluating bits of code. Don't use it for things that can be solved more elegantly in language-integrated, more appropriate ways.
EDIT:
When accessing Query objects with the "angle bracket"-syntax, you must append the (1-based) row number index (query["foo#i#"][RowNum]
). When using the traditional "dot"-syntax (query.foo1
), the current row is implicit.
To access the current row explicitly, use the QueryObject.CurrentRow
property. But it could be any positive integer up to QueryObject.RecordCount
. A range check is advised for anything other than CurrentRow
.
This opens an interesting field: You can start to use query objects with "random access". Previously (before CFMX) all you could do was iterate them from start to end, pulling out the things that you look for. Now it's like a nested struct/array data structure that you can use in different ways.
Using column names from dynamically named variables in Coldfusion
Array notation is your friend.
<cfoutput>
<cfloop query = "ProdData">
<cfloop array = "#ProdData.getColumnList()#" index = columnName>
#prodData[columnName][currentrow]#
closing tags and formatting stuff
Looping over the query results set
Something like this should work for the data in your question.
<cfset NumberOfItems = ListLen(queryname.value[1])>
<cfoutput>
<cfloop from=1 to=NumberOfItems index=idx>
Row 1 item is #ListGetAt(queryname.value[1], idx)#<br>
Row 2 item is #ListGetAt(queryname.value[2], idx)#<br>
</cfloop>
</cfoutput>
Loop through 2 slectboxes submitted values to map to an update query. Coldfusion
Alternate Approach
Before I get to your current form, let me mention another option: using your database's import tools, like OPENROWSET or BULK INSERT. The former is a little more flexible it can be used from a SELECT
statement. So you could do a direct insert from the CSV file, no looping. (I usually prefer to insert into a temp table first. Run a few validation queries, then insert/select
the data into the primary table. But it depends on the application ..)
Anyway, once you have validated the column names, the insert with OPENROWSET
is just a single query:
<!--- see below for how to validate list of column names --->
<cfquery name="insertRawData" datasource="yourDSN">
INSERT INTO YourTable ( #theSelectedColumnNames# )
SELECT *
FROM OPENROWSET( 'Microsoft.Jet.OLEDB.4.0'
,'text;HDR=YES;Database=c:\some\path\'
, 'SELECT * FROM [yourFileName.csv]' )
</cfquery>
Current Approach
Form:
Using your current method you would need to read the CSV file twice: once on the "mapping" page and again on the action page. Technically it could be as simple as giving the db column select lists the same name. So the names would be submitted as a comma delimited list:
<cfset csvHeaders = csvData[1]>
<cfloop array="#csvHeaders#" index="headerName">
<cfoutput>
Map file header: #headerName#
to column:
<select name="targetColumns">
<option value="" selected>--- column name---</option>
<cfloop query="getColumnNames">
<option value="#column_name#">#column_name#</option>
</cfloop>
</select>
</cfoutput>
<br>
</cfloop>
Validate Columns:
Then re-validate the list of column names against your db metadata to prevent sql injection. Do not skip that step!. (You could also use a separate mapping table instead, so as not to expose the db schema. That is my preference.)
<cfquery name="qVerify" datasource="yourDSN">
SELECT COUNT(COLUMN_NAME) AS NumberOfColumns
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YourTableName'
AND COLUMN_NAME IN
(
<cfqueryparam value="#form.targetColumns#" cfsqltype="cf_sql_varchar">
)
</cfquery>
<cfif qVerify.recordCount eq 0 OR qVerify.NumberOfColumns neq listLen(form.targetColumns)>
ERROR. Missing or invalid column name(s) detected
<cfabort>
</cfif>
Insert Data:
Finally re-read the CSV file and loop to insert each row. Your actual code should contain a LOT more validation (handling of invalid column names, etcetera) but this is the basic idea:
<cfset csvData = CSVToArray(....)>
<!--- deduct one to skip header row --->
<cfset numberOfRows = arrayLen(csvData) - 1>
<cfset numberOfColumns = arrayLen(csvData[1])>
<cfif numberOfColumns eq 0 OR numberOfColumns neq listLen(form.targetColumns)>
ERROR. Missing or invalid column name(s) detected
<cfabort>
</cfif>
<cfloop from="1" to="#numberOfRows#" index="rowIndex">
<cfquery ...>
INSERT INTO ClientColumnMappings ( #form.targetColumns# )
VALUES
(
<cfloop from="1" to="#numberOfColumns#" index="colIndex">
<cfif colIndex gt 1>,</cfif>
<cfqueryparam value="#csvData[rowIndex][colIndex]#" cfsqltype="cf_sql_varchar">
</cfloop>
)
</cfquery>
</cfloop>
How can I dynamically reference column names while looping through a query's results?
It's possible using structure notation, but also requires a query row number. The general syntax is
#queryName[ "columnName" ][ rowNum ]#
<!---
.. or specifically
--->
#getCapabilityAndDescription[ DescriptionVariable ][ 1 ]#
To output all of the query columns dynamically. Use GetMetaData() to retrieve an array of query column properties (in select
order). Then use the name
property to output each column value:
<!--- Demo query --->
<cfset yourQuery = queryNew("EntryID,Baker3Description"
, "integer,varchar"
, [[1,"Descrip A"],[2,"Descrip B"]]
)>
<cfset meta = getMetaData(yourQuery)>
<cfoutput query="yourQuery">
<cfloop array="#meta#" index="props">
#yourQuery[props.name][currentRow]#
</cfloop>
<br>
</cfoutput>
Though be careful building that kind of dynamic sql. If any of the values used to build the column names are user supplied (for example getJobDesc.Type
) the query will be vulnerable to second order sql injection.
Double evaluation - How do I access my query column based on a variable that holds the column name?
When referencing column names as a structure, you need to also tell the query which row you want to get. You should also make sure that you check that the column name exists if you didn't get the cols variable via myQuery.ColumnList.
Use the following code to dynamically reference each column in your loop:
<table>
<cfoutput query="myQuery">
<tr>
<cfloop list="#cols#" index="col">
<td>#myQuery[col][CurrentRow]#</td>
</cfloop>
</tr>
</cfoutput>
</table>
Coldfusion looping over query and display html tag only one time
You'll want to try to go farther than I did and reduce this to one query overall if possible, but this should get you on the right track
Combine your two inner queries into one query, add an order by attribute on the duplicate row type, and then add a group
by attribute to your cfloop
. If you're on CF10? or lower you may need to use cfoutput
instead of cfloop
to use the group
attribute
<cfquery name="client_id" datasource="#application.dsn#">
Select borrower_id,client_id,id from contracts where borrower_id = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(url.ID)#">
</cfquery>
<cfloop query="client_id">
<cfquery name="Payment_log" datasource="#application.dsn#">
SELECT pl.log_id log_id
FROM paymentLog pl
INNER JOIN paymentLog_file plf ON pl.log_id = plf.log_id
WHERE contract_id = <cfqueryparam value="#client_id.id#" maxlength="36" cfsqltype="CF_SQL_BIGINT">
AND DateDelete IS NULL
ORDER BY pl.log_id, date_log, id
</cfquery>
<cfloop query="Payment_log" group="log_id">
<th>file name</th>
</cfloop>
</cfloop>
Related Topics
In Which Sequence Are Queries and Sub-Queries Executed by the SQL Engine
Grouped String Aggregation/Listagg for SQL Server
SQL Performance: Where VS Where(Row_Number)
Core Data Performance with Single Parent Entity
SQL Inner Join More Than Two Tables
The Used Select Statements Have a Different Number of Columns (Redux!!)
Get Topn of All Groups After Group by Using Spark Dataframe
Linux Python3 - Can't Open Lib 'SQL Server'
How to Read the Contents of an .SQL File into an R Script to Run a Query
Getting a Rank from Activerecord
Sqlite3 "Forgets" to Use Foreign Keys
Finding Rows That Don't Contain Numeric Data in Oracle
Why Is My T-SQL Left Join Not Working
Can an Inner Join Offer Better Performance Than Exists
Group by with Clob in Select-Statement
Why Execute Stored Procedures Is Faster Than SQL Query from a Script