循环通过2个选择框提交的值以映射到更新查询。 ColdFusion的

时间:2013-06-04 18:48:57

标签: string coldfusion cfloop

我有两列选择框。第一个(左侧)由上载的CSV文件的所有列填充。 第二个(右)是他们可以导入的“客户”表的所有列。对的数量由上传文件中的总列数确定。

然后,用户可以浏览并设置其数据的哪些列将更新Clients表中的哪些列。 例如,他们会将左侧的第一个框设置为“电子邮件”,将右侧的第一个框设置为“电子邮件”,他们的电子邮件将更新为我们数据库中的电子邮件列。

如果他们有一个名为“组织”的列,我们只有“公司”,那么他们可以相应地设置它以进行更新。 基本上映射其导入的客户端,以便他们可以使用更广泛的列名约定。

我已经有了循环设置来填充这里的一些帮助。

现在我正在尝试更新查询。 这是文件上传后的选择框。

<form class="formContent960" id="csvmap" name="csvmap" method="post" action="custom_upload_update.cfm">
    <table class="form960" cellpadding="5">
        <tbody>
            <!--- Set Uploaded file to Array --->
            <cfset arrCSV = CSVToArray(CSVFilePath = #form.UploadedFile#,Delimiter = ",",Qualifier = """") />
            <!--- Create Key array from column names --->

            <cfloop from="1" to="#ArrayLen(arrCSV[1])#" index="t">
                <!--- Variable Headers --->
                <cfif Len(form.UploadedFile) GTE 5>
                <cfoutput>
                    <select name="upfield[#t#]" class="search" id="Header">
                </cfoutput>
                    <option selected value="">--- Headers Uploaded ---</option>
                <cfoutput>
                <cfloop from="1" to="1" index="i">
                    <cfloop from="1" to="#ArrayLen(arrCSV[i])#" index="j">
                    <option value="#arrCSV[i][j]#">#arrCSV[i][j]#</option>

                    </cfloop>
                </cfloop>
                </cfoutput>
                    </select> =
                </cfif>
                <!---Column Constants--->
                <cfoutput>
                    <select name="bofield[#t#]" class="search" id="Column">
                </cfoutput>
                    <option selected value="">--- Headers Clients ---</option>
                        <cfoutput>
                            <cfloop query="clientsCols">
                            <option value="#Column_name#">#Column_name#</option>
                            </cfloop>
                        </cfoutput>
                    </select><br /><br />
                </cfloop>
            </tbody>

        <cfoutput>
        <input type="hidden" name="filelength" id="filelength" value="#ArrayLen(arrCSV[1])#">
        </cfoutput>
        <input type="submit" name="csvmapsubmit" id="csvmapsubmit">

    </table>
</form>

所以我想我需要设置一个包含Clients(Right)列的值的变量select string来设置在循环内的查询中要更新的列。

然后设置上传的字段以更新值在子循环内的那些行中的数据。

像:

<cfloop>
<cfset bostring = "#bofields#"/> 
</cfloop>
<cfloop>
<cfquery name="addclientubmit" datasource="#request.dsn#">
        INSERT INTO Clients
        (
            #bostring#
        )
        VALUES 
        (
        <cfloop>
            #uploaded Values#
        </cfloop>   
        )
        </cfquery>
</cfloop>

没有使用正确的语法,只是为了讨论目的而试图包含我的问题的一般逻辑。

任何帮助将不胜感激。 先谢谢你,

史蒂夫

2 个答案:

答案 0 :(得分:0)

看看这是否对您有所帮助。请注意,我已经修改了您的初始代码以用于演示目的,但已经表示您应该能够连接回测试。 这可能很棘手......但应该给你一个很好的起点。

请注意,Coldfusion中有新工具可用于处理CSV文件 - 我在2008年为CF 8编写了我的实用程序,但它们至今仍在使用。比较和对比什么对你有用。

希望这有帮助。

=== cfm page

<!---import csv utility component (modify for your pathing)--->
<cfset utilcsv = CreateObject("component","webroot.jquery.stackoverflow.csvColumnMap.utils_csv_processing_lib")>
<!---declare the csv file (modify for your pathing)--->
<cfset arrCSV = utilcsv.readinCSV(ExpandPath('./'),'Report-tstFile.csv') />
<!---declare the header row column values--->
<cfset headerRow = listToArray(arrCSV[1],',')>
<!---declare the column names query--->
<cfset q = QueryNew('offer,fname,lname,address,city,state,zip',
                    'CF_SQL_VARCHAR,CF_SQL_VARCHAR,CF_SQL_VARCHAR,CF_SQL_VARCHAR,CF_SQL_VARCHAR,CF_SQL_VARCHAR,CF_SQL_VARCHAR')>
<cfset colList = q.columnList>  

<!---form submission processing--->
<cfif isdefined("form.csvmapsubmit")>

    <cfset collection = ArrayNew(1)>
    <!---collect the column and column map values : this step could be eliminated by 
    just assigning the the arrays in the next step, however this allows reference for 
    dump and debug--->
    <cfloop collection="#form#" item="key">
        <cfif FIND('BOFIELD',key) && trim(StructFind(form,key)) neq "">
            <cfset fieldid = ReREPLACE(key,"\D","","all")>
            <cfset valueKey = 'UPFIELD[' & fieldid & ']'>
            <cfset t = { 'column'=StructFind(form,key),'value'=StructFind(form,valueKey) }>
            <cfset arrayappend(collection,t)>
        </cfif>
    </cfloop>

    <!---collect the column and column map values : this ensures that the table column is in the same position as the mapped column for the sql statement--->
    <cfset tblColsArr = ArrayNew(1)>
    <cfset valColsArr = ArrayNew(1)>
    <cfloop index="i" from="1" to="#ArrayLen(collection)#">
        <cfset arrayappend(tblColsArr, collection[i]['column'])>
        <cfset arrayappend(valColsArr, collection[i]['value'])>
    </cfloop>

    <!---convert the uploaded data into an array of stuctures for iteration--->
    <cfset uploadData = utilcsv.processToStructArray(arrCSV)>

    <!---loop uploaded data--->
    <cfloop index="y" from="1" to="#ArrayLen(uploadData)#">

        <!---create sql command for each record instance--->
        <cfset sqlCmd = "INSERT INTO Clients(" & arraytolist(tblColsArr) & ") Values(">
        <cfloop index="v" from="1" to="#ArrayLen(valColsArr)#">
            <!---loop over the column maps to pull the approriate value for the table column--->
            <cfif isNumeric(trim(valColsArr[v])) eq true>
                <cfset sqlCmd &= trim(uploadData[y][valColsArr[v]])>
             <cfelse>
                <cfset sqlCmd &= "'" & trim(uploadData[y][valColsArr[v]]) & "'">
             </cfif>
             <cfset sqlCmd &=  (v lt ArrayLen(valColsArr)) ? "," : ")" >
        </cfloop>

        <!---perform insert for record--->
        <!--- 
        <cfquery name="insert" datasource="">
        #REReplace(sqlCmd,"''","'","ALL")# <!---In the event that the quotation marks are not formatted properly for execution--->
        </cfquery>
        --->
    </cfloop>

</cfif>

<form class="formContent960" id="csvmap" name="csvmap" method="post">
<table class="form960" cellpadding="5">
    <tbody>
    <cfloop from="1" to="#ArrayLen(headerRow)#" index="t">
    <tr>
        <td>
        <!--- Variable Headers --->
        <cfif ArrayLen(headerRow) GTE 5>
            <cfoutput>
            <select name="upfield[#t#]" class="search" id="Header">
                <option selected value="">--- Headers Uploaded ---</option>
                <cfloop from="1" to="#ArrayLen(headerRow)#" index="j"><option value="#headerRow[j]#">#headerRow[j]#</option></cfloop>
            </select> =
            </cfoutput>                
        </cfif>
        </td>
        <td>
        <!---Column Constants--->
        <cfoutput>
        <select name="bofield[#t#]" class="search" id="Column">            
            <option selected value="">--- Headers Clients ---</option>
            <cfloop list="#colList#" index="li" delimiters=","><option value="#li#">#li#</option></cfloop>
        </select>
        </cfoutput>
        </td>
    </tr>
    </cfloop>
    <tr>
    <td>&nbsp;</td>
    <td>
    <cfoutput>
    <input type="hidden" name="filelength" id="filelength" value="#ArrayLen(headerRow)#">
    </cfoutput>
    <input type="submit" name="csvmapsubmit" id="csvmapsubmit">
    </td>
    </tr>
    </tbody>
</table>
</form>

== utils_csv_processing_lib.cfc

<!---////////////////////////////////////////////////////////////////////////////////
////                    CSV File Processing - Read In File                      /////
////                Return is array with each array item being a row            /////
////                            9.22.08 BP                                      /////
////                                                                            /////
/////////////////////////////////////////////////////////////////////////////////---> 
    <cffunction name="readinCSV" access="public" returntype="array">
        <cfargument name="fileDirectory" type="string" required="yes">
        <cfargument name="fileName" type="string" required="yes">  
        <!---/// 1. read in selected file ///--->
        <cffile action="read" file="#fileDirectory##fileName#" variable="csvfile">
    <!---/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  2. set csv file to array ***Note; the orginal csv file ListToArray only used the carrige return/line return as delimiters, ///
//  so each array value/member is a full record in comma delimited format (i.e.: 01, Fullname, Address1, City, etc) //////////--->    
        <cfset csvList2Array = ListToArray(csvfile, "#chr(10)##chr(13)#")> 

        <cfset ret = checkCSVRowLengths(csvList2Array)>
        <cfreturn ret>
    </cffunction>

<!---////////////////////////////////////////////////////////////////////////////////
////                    Create Structured Array of CSV FILE                     /////
//// Return is a structured array uing the colmn header as the struct element name //
////                            9.22.08 BP                                      /////
////                                                                            /////
////                    ****UPDATED 1.6.09**********                            /////
////            Added empty field file processing - takes empty value           /////
////                        and replaces with "nul"                             /////
////                                                                            /////
/////////////////////////////////////////////////////////////////////////////////---> 
    <cffunction name="processToStructArray" access="public" returntype="array">
        <cfargument name="recordFile" type="array" required="yes">


     <!---retrieve the placeholder we are setting for strings containing our default list delimiter (",")--->
        <cfinvoke component="utils_csv_processing_lib" method="SetGlobalDelimiter" returnvariable="glblDelimiter">

    <!---/// 1. get length of array (number of records) in csv file ///--->
        <cfset csvArrayLen = ArrayLen(recordFile)>

        <!---/////////////////////////////////////////
        ////        EMPTY VALUE Processing          //
        //////////////////////////////////////////--->
                <!---// a. create array to hold updated file for processing--->
                    <cfset updatedRowsFnlArr = ArrayNew(1)>

                <!---// b. loop entire csv file to process each row--->
                    <cfloop index="li2" from="1" to="#csvArrayLen#">

                    <!---// c. grab each column (delimited by ",") for internal loop. *******The value of each array index/item is a comma delimited list*******--->
                    <cfset currRecRow = #recordFile[li2]#>

                    <!---/// d. loop each row in file--->
                    <cfloop list="#currRecRow#" index="updateRowindex" delimiters="#chr(10)##chr(13)#">
                          <!---// e. find and replace empty column values in list with a set value for processing--->
                          <!---consolidated for single list output per array index: regenerates a value of val,val,val for a value of val,,val--->

                          <!---// process middle positions in list //--->
                          <cfset currRowListed = updateRowindex>
                          <cfset updatedRowListed = REreplace(currRowListed,",,",",nul,","ALL")>
                          <cfset updatedRowListed = REreplace(updatedRowListed,",,",",nul,","ALL")>
                          <!---// process 1st position in list //--->
                          <cfset frstpos = REFIND(",",updatedRowListed,1)>
                          <cfif frstpos EQ 1>
                          <cfset updatedRowListed = REReplace(updatedRowListed,",","nul,","one")>
                          </cfif>
                          <!---// process last position in list //--->
                          <cfset rowStrngLen = Len(updatedRowListed)>
                          <cfset lastpos = REFIND(",",updatedRowListed,rowStrngLen)>
                          <cfif lastpos EQ rowStrngLen>
                          <cfset updatedRowListed = updatedRowListed & "nul">
                          </cfif>

                          <!---// f. append current row with updated value of 'nul' for empty list positions to array--->
                          <cfset ArrayAppend(updatedRowsFnlArr, updatedRowListed)>
                     </cfloop>
                </cfloop>

        <!---/// 2. get number of records in updated array--->
        <cfset updatedRowsFnlLen = ArrayLen(updatedRowsFnlArr)>

        <!---/// 3. set the first item in the array to a variable (at postion 1). This will set the entire first record to the variable, delimited by commas ///--->
        <cfset getRecColumns = updatedRowsFnlArr[1]>

        <!---/// 4. get length of 1st record row, which will tell us hom many columns are in the csv file ///--->
        <cfset ColumnCount = ListLen(updatedRowsFnlArr[1],",")>

        <!---/// 5. create array to hold value for return and start loop of list *****Loop started at 2 to exclude header row***** ///--->
        <cfset recordArr = ArrayNew(1)>
        <cfloop index="i" from="2" to="#updatedRowsFnlLen#">

        <!---/// 6. grab each column (delimited by ",") internal loop. The value of each array index/item is a comma delimited list ///--->
        <cfset currRecRow = #updatedRowsFnlArr[i]#>

        <!---/// 7. We now create a structure and assign each row value to the corresponding header within the structure ///--->
        <cfset recordStruct = StructNew()>
        <cfloop index="internal" from="1" to="#ColumnCount#">
            <!---conditional to set the 'nul' value added for empty list position values in order to process back to empty values--->
            <cfif listGetAt(currRecRow,internal,",") NEQ 'nul'>

                    <!---check for global placeholder delimiter and reset to ","--->
                    <cfif FIND(glblDelimiter,listGetAt(currRecRow,internal,",")) NEQ 0>
                        <cfset resetDelimiterVal = Replace(listGetAt(currRecRow,internal,","),glblDelimiter,',','All')>
                    <cfelse>
                        <cfset resetDelimiterVal = listGetAt(currRecRow,internal,",")>
                    </cfif>

                <cfset recordStruct[listGetAt(getRecColumns,internal,",")] = resetDelimiterVal>
            <cfelse>
                <cfset recordStruct[listGetAt(getRecColumns,internal,",")] = "">
            </cfif>
        </cfloop>
        <!---/// 8. append the struct to the array ///--->
        <cfset ArrayAppend(recordArr,recordStruct)>
        </cfloop>
        <cfreturn recordArr>
    </cffunction>

<!---////////////////////////////////////////////////////////////////////////////////
////                              SetGlobalDelimiter                            /////
////    Sets a placeholder for strings containing the primary delimiter (",")   /////
////                                    02.6.11 BP                              /////
/////////////////////////////////////////////////////////////////////////////////--->     
    <cffunction name="SetGlobalDelimiter" access="public" returntype="string" hint="set a placeholder delimiter for the strings that contain the primary list comma delimiter">
        <cfset glblDelimiter = "{_$_}">
      <cfreturn glblDelimiter>
    </cffunction>  

===缺少cfc功能

<!---////////////////////////////////////////////////////////////////////////////////////////////////////////
////                        checkCSVRowLengths                                                          /////
////  due to some inconsistencies in excel, some csv files drop the delimiter if list is empty          /////
////                            7.20.11 BP                                                              /////
/////////////////////////////////////////////////////////////////////////////////////////////////////////---> 
<cffunction name="checkCSVRowLengths" access="public" returntype="array">
        <cfargument name="readArray" type="array" required="yes">

 <cfset column_row = readArray[1]>
       <cfset column_row_len = listlen(column_row,',')>

       <cfloop index="i" from="2" to="#ArrayLen(readArray)#">
            <cfset updateRowindex = readArray[i]>


            <cfif listlen(updateRowindex) lt column_row_len>

                         <!---// process middle positions in list //--->
                          <cfset currRowListed = updateRowindex>
                          <cfset updatedRowListed = REreplace(currRowListed,",,",",nul,","ALL")>
                          <cfset updatedRowListed = REreplace(updatedRowListed,",,",",nul,","ALL")>
                          <!---// process 1st position in list //--->
                          <cfset frstpos = REFIND(",",updatedRowListed,1)>
                          <cfif frstpos EQ 1>
                          <cfset updatedRowListed = REReplace(updatedRowListed,",","nul,")>
                          </cfif>
                          <!---// process last position in list //--->
                          <cfset rowStrngLen = Len(updatedRowListed)>
                          <cfset lastpos = REFIND(",",updatedRowListed,rowStrngLen)>
                          <cfif lastpos EQ rowStrngLen>
                          <cfset updatedRowListed = updatedRowListed & "nul">
                          </cfif>
             <cfelse>

                <cfset  updatedRowListed  = updateRowindex>    

            </cfif>

            <cfif listlen(updatedRowListed) lt column_row_len>

                <cfset lc = column_row_len - listlen(updatedRowListed)>

                <cfloop index="x" from="1" to="#lc#">
                    <cfset updatedRowListed = updatedRowListed & ',nul'>
                </cfloop>


            </cfif>


            <cfset readArray[i] = updatedRowListed>
       </cfloop>



        <cfreturn readArray>
    </cffunction>

答案 1 :(得分:0)

替代方法

在我查看您当前的表单之前,请允许我提及另一个选项:使用数据库的导入工具,例如OPENROWSETBULK INSERT。前者更灵活,可以从SELECT语句中使用。所以你可以从CSV文件直接插入,没有循环。 (我通常更喜欢先插入临时表。运行一些验证查询,然后将insert/select数据放入主表。但这取决于应用程序..)

无论如何,一旦验证了列名,带OPENROWSET的插入只是一个查询:

<!--- 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>

当前方法

<强>形式:

使用您当前的方法,您需要两次读取CSV文件:一次在“映射”页面上,再一次在操作页面上。从技术上讲,它可以像给db列选择列表一样简单。因此,名称将以逗号分隔列表的形式提交:

<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>

验证列:

然后根据db元数据重新验证列名列表以防止sql注入。 不要跳过这一步!。 (您也可以使用单独的映射表,以免暴露数据库模式。这是我的偏好。)

<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>



插入数据:

最后重新读取CSV文件并循环以插入每一行。您的实际代码应该包含更多的验证(处理无效的列名称等),但这是基本的想法:

<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>