How to loop form values depending on form column name? - coldfusion

I have a form containing 6 check_num fields, 1 account number and an amount. I would like to insert those values into a database table:
<cfquery datasource="test" name="test">
insert into test (ACCOUNT_NUMBER,check_num,amount)
values ('#ACCOUNT_NUMBER#','#check_num#','#amount#')
</cfquery>
However, I don't want to insert all of the values into 1 row. If any of the check_num fields contain a number, I want to insert that value, along with with the acccount_number and amount values.
For example with the data below, I would like to insert 5 rows. The field "check_num6" would be skipped because it is empty.
ACCOUNT_NUMBER 123456789
CHECK_NUM1 56623
CHECK_NUM2 5512
CHECK_NUM3 6562
CHECK_NUM4 653
CHECK_NUM5 6623
CHECK_NUM6
AMOUNT 32.31
FIELDNAMES ACCOUNT_NUMBER,CHECK_NUM1,CHECK_NUM2,CHECK_NUM3,CHECK_NUM4,CHECK_NUM5,CHECK_NUM6,AMOUNT,SUBMIT
SUBMIT Submit

Not perfect but you can start/get some inspiration from here:
<cfloop list="#StructKeyList(form)#" index="i">
<cfif (left('#i#', 9) is "CHECK_NUM") and (trim(form['#i#']) is not "")>
<cfquery datasource="test" name="test">
insert into test (ACCOUNT_NUMBER,check_num,amount)
values ('#ACCOUNT_NUMBER#','#form['#i#']#','#amount#')
</cfquery>
</cfif>
</cfloop>
Also you should validate, see if your DB supports multiple inserts, change your query to use cfqueryparam, research about it. So, a better cfquery code would be:
<cfquery datasource="test" name="test">
insert into test (
ACCOUNT_NUMBER,
check_num,
amount
)
values (
<cfqueryparam cfsqltype="cf_sql_varchar" value="#form.ACCOUNT_NUMBER#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#form['#i#']#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#form.amount#">
)
</cfquery>

Related

Check if any field is different from database value

After I submit a form, I need to check if any field in the database table changed. If changed I create a new record, if not, I don't do anything. I can't just update the record.
Is there a way to do it without checking every single field like the code below?
<cfquery name="getData" datasource="myDS">
Select * From table Where ID = #form.ID#
</cfquery>
<Cfset changed = false />
<!-- Check every field -->
<cfif form.data1 neq getData.data1>
<cfset changed = true />
</cfif>
<cfif form.data2 neq getData.data2>
<cfset changed = true />
</cfif>
<cfif form.data3 neq getData.data3>
<cfset changed = true />
</cfif>
...
Thanks
Might depend on what database you are using but you should be able to do a query that will insert if the data does not exist.
As an example I just tested this against Oracle 12c using CF2016 Enterprise and it creates a new record if the data does not exist.
<cfquery name="Testing" datasource="Test">
INSERT INTO TESTTABLE (DATA1, DATA2, DATA3)
SELECT <cfqueryparam value="#Form.Data1#" cfsqltype="CF_SQL_VARCHAR" />, <cfqueryparam value="#Form.Data2#" cfsqltype="CF_SQL_VARCHAR" />, <cfqueryparam value="#Form.Data3#" cfsqltype="CF_SQL_VARCHAR" />
FROM dual
WHERE NOT EXISTS
(SELECT DATA1, DATA2, DATA3 FROM TESTTABLE WHERE DATA1 = <cfqueryparam value="#Form.Data1#" cfsqltype="CF_SQL_VARCHAR" />,
AND DATA2 = <cfqueryparam value="#Form.Data2#" cfsqltype="CF_SQL_VARCHAR" /> AND DATA3 = <cfqueryparam value="#Form.Data3#" cfsqltype="CF_SQL_VARCHAR" />)
</cfquery>
Can you explain this a little further? Why are you allowing a form to be submitted with changed data if you can't update the record itself? If you click Save, then compare the form data with the query data and have the action call a Create function when the data is different.
Let's say you have a thing query and form:
<cfquery name="myThing">
SELECT
thing_id,
thing_name,
thing_foo
FROM
things
where
thingID = <cfqueryparam value="#url.thingID#">
</cfquery>
<form>
<input type="hidden" name="thing_id" value="#myThing.thing_id#">
<input type="text" name="thing_name" value="#myThing.thing_name#">
<input type="text" name="thing_foo" value="#myThing.thing_foo#">
<button type="submit">Submit</button>
</form>
If you need to check the submitted data against what's already in the database, you can just run the query again on the form processing page and compare those values against the submitted form values. This example assumes you named the form fields the same as the database table columns.
<cfset delta = false>
<cfloop item="key" collection="#myThing#">
<cfif structKeyExists(form, key)>
<cfif form[key] NEQ myThing[key]>
<cfset delta = true>
</cfif>
</cfif>
</cfloop>
If any values differ, then create a new record. No idea what you need to do when the submitted values haven't changed.
<cfif delta>
<!--- Create a new record. --->
<cfelse>
<!--- ¯\_(ツ)_/¯ --->
</cfif>
I've also seen it done where the original values are stored in hidden form fields and submitted along with the editable form fields. You could do this, but there's no guarantee that the values in the DB haven't been changed between you rendering the form and then submitting it.
You'll still have some challenge of how to tell if the DB values have changed on the way to the DB, but I'm not sure if you need so granular a check.
We can use the cfquery to check the table having the existing data or not. If not having the same data means we can Insert the form. The following code may related to your scenario.
<cfif structKeyExists(form,"Submit")>
<cfquery name="checkFormExisting" datasource="myDSN">
SELECT *
FROM USERS
WHERE data1 = <cfqueryparam value="#form.data1#" cfsqltype="cf_sql_varchar">
OR data2 = <cfqueryparam value="#form.data2#" cfsqltype="cf_sql_varchar">
OR data3 = <cfqueryparam value="#form.data3#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfif checkFormExisting.recordCount EQ 0>
INSERT INTO
USERS (
data1,data2,data3
)
VALUES (
<cfqueryparam value="#form.data1#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#form.data2#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#form.data3#" cfsqltype="cf_sql_varchar">
)
</cfif>
</cfif>
Hope, you're asking like the above code for your scenario.

ColdFusion 9: how to pad list values with spaces

In the database, style_id columns is defined as CHAR(14).
I need to be able to pad values in the list to 14 with spaces.
How do I go about it?
Thanks,
<cfset style_list = replace(#FORM.style_id#," ","","all")>
select *
from T
where
<cfif IsDefined("form.style_id") and form.style_id is not "">
style_id in
(
<cfqueryparam
value="#UCASE(style_list)#"
cfsqltype="cf_sql_varchar"
list="yes" />
)
</cfif>
Why do you think you need to pad the values? It should work fine without any padding, using cfsqltype CF_SQL_CHAR. Perhaps it's not working as expected because the left side of the comparison (column) is type CHAR while the right (cfqueryparam) is type VARCHAR?
I tested your query and it worked fine with CF 9,0,2,282541 and SQL Server. It also worked correctly with CF 9.0.2 and Oracle 12G. CFQueryparam seemed to handle things automatically.
DDL:
CREATE TABLE someTable(style_id char(14))
INSERT INTO someTable VALUES ('abc'),('efg ')
CF:
<!--- worked with both CF_SQL_VARCHAR and CF_SQL_CHAR --->
<cfset style_list = "abc,efg">
<cfquery name="qTest" datasource="YourDSN">
SELECT *
FROM SomeTable
WHERE style_id in
( <cfqueryparam
value="#UCASE(style_list)#"
cfsqltype="cf_sql_char"
list="yes" />
)
</cfquery>
<cfdump var="#qTest#">
Results:
RESULTSET
query
STYLE_ID
1 abc
2 efg
Do the opposite. Trim the contents of of the style_id column in your SQL statement so that you can batter match the contents of style_list.
Use ljustify or rjustify to pad a string with spaces.
You could pre-process the list with a loop:
<cfset padded_list=""/>
<cfloop list="#style_list#" item="style">
<cfset padded_list=listappend(padded_list, rjustify(style, 14))/>
</cfloop>
If you're using a more recent version of ColdFusion, or a shim, you may also have listmap, which is less ugly:
<cfset style_list=listmap(style_list, function(style) { return rjustify(style, 14); })/>

Loop over the cfquery tag values

I have the following query where I loop over a list of values but I am getting an error at the last comma:
<cfquery datasource="#application.dsn#">
INSERT INTO #session.tablename# ( #lFields# )
VALUES (
<cfloop list="#lFields#" index="kk">
<cfqueryparam value="#TRIM(sVideoGame['#kk#'])#" cfsqltype="cf_sql_varchar" null="#NOT LEN(TRIM(sVideoGame['#kk#']))#" />,
</cfloop>
)
</cfquery>
Problem occurs with the last comma. I tried setting up a counter before the cfloop, setting it 0 and incrementing to 1 inside the cfloop. However, I am not sure how to remove the last comma based upon some condition check.
In order to keep track of the position, you will need a from/to loop instead of a list loop. Then add a comma after you pass the first query parameter.
For ColdFusion 2016+ it can be done using the "item" and "index" attributes:
...
<cfloop list="#yourListVariable#" item="keyName" index="position">
<!--- if we've passed the first parameter, add a comma --->
<cfif position gt 1>,</cfif>
<cfqueryparam value="#TRIM(sVideoGame[ keyName ])#"
cfsqltype="cf_sql_varchar"
null="#NOT LEN(sVideoGame[keyName])#" />
</cfloop>
...
CF11 and earlier require a little more work. To simplify the code, I would recommend converting the list to an array:
<cfset keyArray = listToArray(yourListVariable)>
...
<cfloop from="1" to="#arrayLen(keyArray)#" index="position">
<!--- if we've passed the first parameter, add a comma --->
<cfif position gt 1>,</cfif>
<cfqueryparam value="#TRIM(sVideoGame[ keyArray[position] ])#"
cfsqltype="cf_sql_varchar"
null="#NOT LEN(sVideoGame[ keyArray[position] ])#" />
</cfloop>
...
Side note, I noticed the query uses dynamic table and column names. Be sure those values are NOT user supplied, or the query is vulnerable to sql injection.
If your only question is how to deal with the last comma, then you can do it with
<cfset listCount = 1>
<cfloop list="#lFields#" index="kk">
<cfqueryparam value="#TRIM(sVideoGame['#kk#'])#"
cfsqltype="cf_sql_varchar" null="#NOT LEN(TRIM(sVideoGame['#kk#']))#" />
<cfif listLen(lFields) is not listCount>,</cfif>
<cfset listCount = listCount+1>
</cfloop>

CFloop query to store multiple values in database

I have few variables that contain multiple values. Basically I want to store all the values into my database. I am using this code which I got here in Stackoverflow.
<cfquery datasource="databaseName">
INSERT INTO spreadsheet
([Local.Time.Stamp],
[Energy.Delivered..kVAh.],
[Energy.Received..kVAh.],
[Energy.Received..kVARh.],
[Energy.Delivered..kVARh.],
[Real.A..kW.],
[Real.B..kW.])
VALUES
(<cfloop query="excelquery">
'#excelquery.col_1#',
'#excelquery.col_2#',
'#excelquery.col_3#',
'#excelquery.col_4#',
'#excelquery.col_5#',
'#excelquery.col_6#',
'#excelquery.col_7#'
</cfloop>)
</cfquery>
However I always get a syntax error. I believe that my cfloop part is wrong, can someone please tell me the correct way for me to write that cfloop?
The problem is with the generated query not cfloop i.e., for entering multiple values the format should be like this:
INSERT INTO TableName (col,col,...) VALUES (val,val,...),(val,val,...),...
Also, use cfqueryparam to avoid sql injection.
You can try this:
<cfquery datasource="databaseName">
INSERT INTO spreadsheet
([Local.Time.Stamp],
[Energy.Delivered..kVAh.],
[Energy.Received..kVAh.],
[Energy.Received..kVARh.],
[Energy.Delivered..kVARh.],
[Real.A..kW.],
[Real.B..kW.])
VALUES
<cfloop query="excelquery">
<!--- cf_sql_varchar is just an example. --->
(<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_1#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_2#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_3#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_4#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_5#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_6#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#excelquery.col_7#">)
#excelQuery.currentRow NEQ excelQuery.recordCount ? ',': ''#
</cfloop>
</cfquery>
You might have to add a recordCount check before generating the query to avoid errors for no records.

Using array data to drill a query

I am learning about arrays and structs, and putting together a shopping cart as a project. I am currently creating a recently viewed products module for the cart. When a user views a product i collect the product id into an array,
What is the best way to use this info to query my database, my query would be
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
</cfquery>
I thought of outputting the array as a list and using the IN operator, but not sure if that is the right way of doing it, seems a bit cumbersome? Can I use a query of queries using the array data? If so any pointers?
If you just maintain a list of productIDs then you don't need to use arrays or structs, you can just store them as a simple list and query them as follows:
<cfset recentProducts = "4,5" />
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
where productID IN (<cfqueryparam cfsqltype="cf_sql_integer" value="#recentProducts#" list="true" />)
</cfquery>
Firstly, unless you're going to put additional things into the struct, I'd say having a struct just to hold ProductID seems excessive. I'd just have the Array with the productID as each element, no struct.
So your array is structured like:
[
{ ProductID= 4 },
{ ProductID= 5 },
...
]
I'd suggest you just do:
[4,5]
This will make the next step easier. Right now I'd say you have to loop over your array, adding each productID into a list that you can then use in your query. Then modify your original query to use that list (remember and use cfqueryparam).
<cfset lstIDs = "">
<cfloop index="productStruct" array="#productsArray#">
<cfset lstIDs = listAppend(lstIDs, productStruct.ProductID)>
</cfloop>
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
WHERE productID IN <cfqueryparam value="#lstIDs#" cfsqltype="CF_SQL_INTEGER" list="yes">
</cfquery>
Or, if you simplify your array, just do ArrayToList to achieve the same thing.
<cfquery name="getRecent" datasource="#application.dsn#">
SELECT uid_product,txt_product_name
FROM tbl_products
WHERE productID IN <cfqueryparam value="#ArrayToList(productsArray)#" cfsqltype="CF_SQL_INTEGER" list="yes">
</cfquery>