csvToArray and insert into database not working as intended - coldfusion

Earlier in the week I was asking the community how I can insert the information from a function called csvToArray written by Ben Nadel, however, I ended up utilizing something else which seems to run just as good and or better. So here is the problem now: After using this new function I am able to grab the data from the array, however when I insert it into the Database I get the following error message from coldfusion :"The element at position 16, of dimension 2, of an array object used as part of an expression, cannot be found." Does anyone have any experience with this and know why I am receiving this message? Everything else works as intended. Here is the code in question:
<cfif request_method is "Post" AND isDefined("form.fileUpload") EQ 1>
<cffile action="read" file="#form.filecontent#" variable="csvfile">
<cfinvoke component="#application.path.cfc#.Organizations" method="csvToArray" returnvariable="getArraData">
<cfinvokeargument name="fileContent" value="#csvfile#">
</cfinvoke>
<!--- Loop the array starting on the 2 index to remove the header info from the insert --->
<cfloop from="2" to="#arrayLen(getArraData)#" index="i">
<cfset indexNum = i > <!--- Start with 2 index then increment the value by 1 on the loop --->
<cfloop from="2" to="#arrayLen(getArraData[i])#" index="j">
<cfset dimNum = j > <!--- Start with 2 index then increment the value by 1 on the loop --->
<!--- After loop ends. nunbers are dynamically placed into position [2][2] and insert the values of the index deminsion--->
<cfquery datasource ="#application.dsn.name#" name="BudgetInsert">
<!--- Insert Data here --->
INSERT INTO sales_budget
(
nOrganizationID
,nLocationID
,nBudgetTypeID
,nBudgetYear
,month1
,month2
,month3
,month4
,month5
,month6
,month7
,month8
,month9
,month10
,month11
,month12
,nActive
,tCreationDate
,tLastUpdate
,cChangedBy
)
VALUES
(
<cfqueryparam cfsqltype="cf_sql_integer" value="#url.OrgID#">
,<cfqueryparam cfsqltype="cf_sql_integer" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_integer" value="2">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_varchar" value="#getArraData[indexNum][dimNum]#">
,<cfqueryparam cfsqltype="cf_sql_bit" value="1">
,GetDate()
,GetDate()
,<cfqueryparam cfsqltype="cf_sql_integer" value="#Session.Auth.UserID#">
)
</cfquery>
<cfset dimNum = j +1 >
</cfloop>
indexNum = i +1
</cfloop>
</cfif>

You should be able to just use cfspreadsheet to read the CSV file and move it into the DB. While there are commands for most modern databases to directly import a CSV file (per Leigh's comment), if you don't have the correct permissions, then this is your best bet.
<cfspreadsheet
action="read"
src = "filepath"
columns = "range"
columnnames = "comma-delimited list"
excludeHeaderRow = "true | false"
format = "CSV|HTML"
headerrow = "row number"
name = "text"
query = "query name"
rows = "range"
sheet = "number"
sheetname = "text">
This returns a normal CF query object which you can loop over for your inserts.

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.

insert into table by looping over structure

I am trying to insert into the code but it is inserting value in every row, rather than question value in question and answer value in answer:
<cfset StructDelete(structform,'title')>
<cfset StructDelete(structform,'mode')>
<cfset StructDelete(structform,'formsubmission')>
<cfset StructDelete(structform,'file_upload')>
<cfset StructDelete(structform,'czContainer_czMore_txtCount')>
<CFSET StructDelete(structform,'action')>
<CFLOOP collection="#structform#" index="whichPair">
<cfset Questions = "question" & structform[whichPair]>
<cfset answer = "answer" & structform[whichpair]>
<cfquery name="insertData" datasource="aas">
insert into faqsquestions(question,answer,createdon,faqID)
values(<cfqueryparam cfsqltype="cf_sql_varchar" value="#Right(questions, Len(questions)-8)#">,
<cfqueryparam cfsqltype="cf_sql_longvarchar" value="#Right(answer, Len(answer)-8)#">,
<cfqueryparam cfsqltype="cf_sql_date" value="#CreateODBCDate(now())#">,
<cfqueryparam cfsqltype="cf_sql_integer" value="#getLastID#">)
</cfquery>
</CFLOOP>
can anyone tell what i am doing wrong here, i know i am using question as a static value just inside the loop as cfset and doing a right to remove that question variable which makes no sense but i will remove it when i am finished fixing my code
questions and answers are like this:
http://prntscr.com/lntu2l
That's the wrong type of loop for what you're trying to do. The reason is a structure loop iterates once - for each field. When what you want is to loop once - for each pair of fields.
A simple option is add a hidden field to your form, containing the total number of pairs.
<input type="hidden" name="NumberOfQuestions" value="#TheTotalNumberHere#">
Then use the total number with a from and to loop. On each iteration, extract the current value of the question and answer fields, and use them in your query:
<cfloop from="1" to="#FORM.NumberOfQuestions#" index="pairNum">
<cfset question = FORM["question"& pairNum]>
<cfset answer = FORM["answer"& pairNum]>
<cfquery ...>
INSERT INTO faqsQuestions(question,answer,createdon,faqID)
VALUES (
<cfqueryparam cfsqltype="cf_sql_varchar" value="#question#">
, <cfqueryparam cfsqltype="cf_sql_longvarchar" value="#answer#">
, <cfqueryparam cfsqltype="cf_sql_date" value="#now()#">
, <cfqueryparam cfsqltype="cf_sql_integer" value="#getLastID#">
)
</cfquery>
</cfloop>

Is it possible to pass stored procedure result to another cfstoredproc in ColdFusion?

I would like to pass the stored procedure result to another stored procedure in ColdFusion. If anyone would be able to help on this.
<cfif not isDefined("getYN")>
<cfstoredproc procedure="stored_proc" datasource="#dsn#">
<cfprocparam cfsqltype="cf_sql_varchar" dbvarname="#lang" type="in" value="#this.lang#"/>
<cfprocparam cfsqltype="cf_sql_varchar" dbvarname="#sqlStatement" type="in" value="#getYN#" null="#NOT len(trim(getYN))#" />
<cfprocresult name="getYN" resultset = "1">
</cfstoredproc>
</cfif>
<cfstoredproc procedure="sp_test" datasource="#dsn#">
<cfprocparam cfsqltype="cf_sql_varchar" dbvarname="#lang" type="in" value="#this.lang#"/>
<cfprocparam cfsqltype="cf_sql_varchar" dbvarname="#sqlStatement" type="in" value="#getYN#" null="#NOT len(trim(getYN))#" />
<cfprocresult name="get" resultset = "2">
</cfstoredproc>
The above is the code example. In the second stored procedure, I am passing the result of 1st stored procedure to the dbvarname sqlStatement of 2nd stored procedure. But the passed value #getYN# should be query instead of result because I am using it for FROM clause.
The 2nd stored procedure in SQL Server is like below:
ALTER PROCEDURE [dbo].[sp_test]
#lang CHAR(5),
#code VARCHAR(20),
#sqlStatement NVARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sSQL nVARCHAR(max)
SET #sSQL = ' SELECT col1
FROM '+ #sqlStatement +
' WHERE col2 = #lang
AND col3 = #code '
EXECUTE SP_EXECUTESQL #sSQL, N'#lang CHAR(5),
#code VARCHAR(20)', #lang, #code ;
SET NOCOUNT OFF;
END
In addition, the above two code is created from the below code to replace it with cfstoredproc instead of cfquery:
<cfif NOT isDefined("request.getYN")>
<cfquery name="request.getYN" datasource="#request.dsn.pqr#">
SELECT
LANGUAGE_CODE ,
YN_CODE ,
YN_DESCRIPTION
FROM
LANGUAGE_ALTS_YN
WHERE
language_code IN (
'EN','#this.lang#'
)
</cfquery>
</cfif>
<cfquery name="get" dbtype="query">
SELECT
yn_description
FROM
request.getYN
WHERE
language_code =
<cfqueryparam cfsqltype="cf_sql_varchar" value="#this.lang#" />
AND yn_code = <cfqueryparam cfsqltype="cf_sql_varchar"
value="#arguments.code#" />
</cfquery>
The second query really isn't a query. It can't be made into a stored procedure because it does not run on the database server. In other words dbtype="query" is not on the DB server
Besides, you can just filter this data down.
Was
<cfquery name="get" dbtype="query">
SELECT yn_description
FROM
request.getYN
WHERE
language_code = <cfqueryparam cfsqltype="cf_sql_varchar" value="#this.lang#" />
AND yn_code = <cfqueryparam cfsqltype="cf_sql_varchar"
value="#arguments.code#" />
</cfquery>
Should be
get = request.getYN.filter(function () {
return (lang_code == this.lang && yn_code == code);
});
Note: that code on my second line is not scoped. That is not a mistake.
For query filters see: https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-m-r/queryfilter.html
Code based on comment
get = request.getYN.filter(function () {
return (lang_code == this.lang && yn_code == code);
}).yn_description;
BTW: Unless the field are large text, varchar(max), or xml, It typically does matter if you are picking one or all

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>

CF queries using form variables entered by a user [duplicate]

This question already has an answer here:
define a list of id's for grouped item
(1 answer)
Closed 6 years ago.
I am trying to do a query matching my trans_location column with the form data the user enters. I have a dropdown that lets the user choose multiple locations. When they choose multiple it places commas in between each location. When I choose only one location the results come back correct with the correct location. But when I choose more than one location it does not find any of the locations. Do the commas make it only one name and it will not search each location?
<cfset result = {} />
<cftry>
<cfset date1 = #CREATEODBCDATETIME(form.StartDate & '00:00:00')#>
<cfset date2 = #CREATEODBCDATETIME(form.EndDate & '23:59:59')#>
<cfquery datasource="#application.dsn#" name="GetLocationInfo">
SELECT *
FROM cl_checklists
WHERE date >= #date1# AND date <= #date2#
AND trans_location = '#form.Location#'
</cfquery>
<cfoutput>#date1#</cfoutput>
<cfoutput>#date2#</cfoutput>
<cfdump var="#GetLocationInfo#">
<cfcatch type="any">
<cfset result.error = CFCATCH.message >
<cfset result.detail = CFCATCH.detail >
</cfcatch>
</cftry>
I also tried doing something like this:
AND trans_location = <cfqueryparam value='#form.Location#' />
You need to use the IN operator in conjunction with cfqueryparam with the list="true" attribute. (Here is a quick helpful tutorial for cfqueryparam: https://www.petefreitag.com/item/677.cfm)
Lastly: always, always, always use cfqueryparam when sending parameters to the database.
<cfset result = {} />
<cftry>
<cfset date1 = CREATEODBCDATETIME(form.StartDate & '00:00:00')>
<cfset date2 = CREATEODBCDATETIME(form.EndDate & '23:59:59')>
<cfquery datasource="#application.dsn#" name="GetLocationInfo">
SELECT *
FROM cl_checklists
WHERE date >= <cfqueryparam value="#date1#" cfsqltype="cf_sql_timestamp" />
AND date <= <cfqueryparam value="#date2#" cfsqltype="cf_sql_timestamp" />
AND trans_location IN ( <cfqueryparam value="#FORM.location#" cfsqltype="cf_sql_varchar" list="true" /> )
</cfquery>
<cfoutput>#date1#</cfoutput>
<cfoutput>#date2#</cfoutput>
<cfdump var="#GetLocationInfo#">
<cfcatch type="any">
<cfset result.error = CFCATCH.message >
<cfset result.detail = CFCATCH.detail >
</cfcatch>
</cftry>