I'm using a cfspreadsheet read to read a sheet into a query object.
<cfspreadsheet action="read" src="TestExcel.xls" sheet="1" query="spreadsheetData" headerrow="1" excludeHeaderRow="true">
The problem is, some of the headers contain more than one word. So I end up with a query a bit like this:
ID Name Start Date End Date
3 Test 1/1/2009 1/1/2013
17 Test 2 11/11/2010 11/11/2012
If I try to access one of the columns that have a space in the column name, I get an error.
<cfoutput query="spreadsheetData">
#start date#
</cfoutput>
I've tried #[start date]# as well, but that didn't work. I cannot control the format of the excel sheet that I receive. Is there any way to access the multiple-worded-header columns?
When using bracket notation the contents must end up as a string, so:
<cfoutput query="spreadsheetData">
#spreadsheetData['start date'][CurrentRow]#
</cfoutput>
If you don't use quotes, you are passing in a variable, which is done like so:
<cfset ColumnName = 'start date' />
<cfoutput query="spreadsheetData">
#spreadsheetData[ColumnName][CurrentRow]#
</cfoutput>
Note that you must use the query name before the brackets - if you simply write [ColumnName] then this is inline array creation notation, not accessing the variable.
Also, if using this outside of a query loop (i.e. not within cfoutput/cfloop with query attribute), you also need to scope the CurrentRow variable, i.e.
spreadsheetData[ColumnName][spreadsheetData.CurrentRow]
(or provide your own explicit number/variable).
As Leigh notes below, for cfspreadsheet-specific behaviour, you can also specify the columnnames attribute, to rename the column to something directly accessible, e.g.
<cfspreadsheet query=".." columnNames="Foo,Bar,StartDate,Etcetera" ..>
Related
'excelFileQuery' contains a query.
<cfset spreadsheetAddRows(s, excelFileQuery)>
<cfset spreadsheetAddRows(s,"#volunteeralias# Export Report (#dateformat(now(),'mmmm d, yyyy')# at #timeformat(now(),'h:mm tt')#)",1,1)>
<cfheader name="content-disposition" value="attachment; filename=myexcel.xlsx">
<cfcontent type="application/msexcel" variable="#spreadsheetReadBinary(s)#" reset="true">
But when the file is generated, on the query rows are showing up, not the second row. Am I doing it wrong?
I am not quite sure about your goal, but spreadSheetAddRows expects a query or an array. So the second statement should pass in an array, not a string. If you prefer to use a string, you must use the spreadsheetSetCellValue function instead.
If your goal is to append that last string after the query results, then convert the string to an array first. Also, remove the row/column number so the data is appended to the first empty row after the query results.
<cfset spreadsheetAddRows(s, excelFileQuery)>
<cfset spreadsheetAddRows(s, ["#volunteeralias# Export Report (#dateformat(now(),'mmmm d, yyyy')# at #timeformat(now(),'h:mm tt')#)"] )>
...
If you want to prepend it (as a header), do the same thing, but obviously you swap the two statements.
Just wondering, given a query and output like so:
<cfoutput query="someItems" group="someColumnName">
... doing some stuff here ..
<cfoutput> doing stuff with some sub items </cfoutput>
</cfoutput>
if there's a way to change the order of elements in the 'inner' cfoutput ?
Can the query be both grouped and sorted by?
You will need to add ORDER BY clauses in your query for this to work, but you can nest cfoutput tags that use the group attribute.
<cfoutput query="someItems" group="someColumnName">
... doing some stuff here ..
<cfoutput group="someOtherColumnName> doing stuff with some sub items </cfoutput>
</cfoutput>
This assumes that in your query you have something that looks like:
ORDER BY someColumnName, someOtherColumnName
Keep in mind that the group attribute of cfquery is not the same as the GROUP BY clause in a SQL statement. You can use the group attribute of cfoutput for ANY column that is in the ORDER BY clause in your query.
One solution is to restructure your code to use the query-of-queries approach. Here is a good example of doing so:
http://www.bennadel.com/blog/2211-ColdFusion-Query-Of-Queries-vs-The-Group-Attribute-In-CFOutput.htm
Basically, you pull out all the data you care about in one master query (probably the query you have already written). You add a second query (against your first query, not against the database) that does the group by and aggregation of data that you need at the top level loop. Inside the loop driven by your second query, you use the row data in the group as a parameter to yet another query (against your first query again, not against the database) to pull out all the data relating to the current row ordered however you desire.
This idea of querying your query seems odd at first, but I have not had performance problems with it and it gives you a lot of flexibility to do what you want in your inner loop. Good luck!
The function for adding a row to a coldfusion spreadsheet is SpreadsheetAddrow which accepts data as "A comma delimited list of cell entries, one per column."
Some of my data has commas within it. How do I escape the commas within my data without escaping the commas in the list?
I am currently creating an array with the contents of the row, then converting it to a list to add to the spreadsheet:
<cfset row = ArrayNew(1)>
<cfloop list="#structKeyList(setRecord.columns)#" index="key">
<cfset ArrayAppend(row, "#Evaluate(key)#")>
</cfloop>
<cfset spreadsheetAddRow(xlsObj, "#ArrayToList(row)#")>
Looks like the ability to specify a different delimiter is not supported yet. Since you are already looping, you may as well skip the array and use SpreadsheetSetCellValue instead. You should be able to eliminate the evaluate() as well.
<cfset cols = structKeyArray(yourStruct) >
<cfloop from="1" to="#arrayLen(cols)#" index="c">
<cfset SpreadsheetSetCellValue(xlsObj, yourStruct[ cols[c] ], lastRow, c)>
</cfloop>
<cfset lastRow++>
...
Update: However, if the base object is a query, not a structure, then it is more efficient to use CfSimplicity's suggestion of SpreadSheetAddRows.
If the data you want to add to the sheet is in a query object (recordset) then the simplest solution is to use SpreadSheetAddRows(), (as opposed to SpreadSheetAddRow - singular).
<cfset SpreadSheetAddRows( xlsObj,query )>
The sheet columns are mapped from the query columns, so commas in the data won't matter.
Even if the data is in another format or you are only adding a single row, converting it to a query object is an effective way of getting round the issue, see http://cfsimplicity.com/30/workaround-for-spreadsheetaddrow-limitation-when-column-values-contain-commas
You can replace the commas with the character "#130;" (see the source: https://www.petefreitag.com/cheatsheets/ascii-codes/), which looks the same as comma but doesn't create any problems.
I'm totally unused to Cold Fusion, I'd like to know how to handle a multiple CFSELECT, in particular how to know how many rows I've selected and taking them one by one.
Actually I've managed to see all the rows togheter:
<!--- page_a.cfm --->
<cfform name="fooform" ........>
<cfselect query="myquery" name="fornitori" multiple="yes"></cfselect>
<!--- page_b.cfm --->
<cfoutput>#form.fornitori#</cfoutput>
And, if it's not too much, I'd like to know why it's not correct to write:
<!--- page_b.cfm --->
<cfoutput>#fooform.fornitori#</cfoutput>
Since it is a multiple selection list, the options you select will be submitted to page_b.cfm as a comma delimited list. This means you can use list functions to calculate the total items selected and cfloop to iterate through the selections individually. Note, when using "multiple" select lists, if you select nothing the form field will not exist.
<cfparam name="form.fornitori" default="">
<cfoutput>
Total Items Selected = <cfoutput>#listLen(form.fornitori)#</cfoutput>
Individual Selections:<br>
<cfloop list="#form.fornitori#" index="theSelection">
#theSelection#<br>
</cfloop>
</cfoutput>
why it's not correct to write ..
Because FORM refers to a special system structure, not the name of your html form (ie fooform). FORM contains any form fields submitted via method=POST.
#myquery.recordcount#
btw, use cfdump to display anything. you see some really interesting stuff (I'm not sure whether recordcount is in there though)
getting to the elements:
<cfoutput query="queryname">#title#<br />#content#</cfoutput>
inside the cfoutput you have access to the variables of an element.
coldfusion 9 help
I have the following ColdFusion 9 code:
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
</cfloop>
The EXEC executes a stored procedure on the database server, which returns rows of data, depending on what the parameter is. What I am trying to do is combine the queries into one query object. In other words, if it loops 3 times and each loop returns 4 rows, I want a query object that has all 12 rows in one object. How do I acheive this?
You might want to take a different approach (modify your stored procedure to accept multiple arguments or use a list and fnSplit) and return the dataset all at once. However, to directly answer your question, this is how you could combine the queries as you're asking to:
You can use UNION in a Query of Queries to combine all of the datasets.
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs#i#" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
</cfloop>
<cfquery name="combined" dbtype="query">
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
select * from qryGetSPFAQs#i#
<cfif i lt arrayLen(tagArray)>UNION</cfif>
</cfloop>
</cfquery>
A more direct way might be something like this:
<cfset bigQ = queryNew("column")>
<cfloop from="1" to="#arrayLen(tagArray)#" index="i">
<cfquery name="qryGetSPFAQs" datasource="#application.datasource#">
EXEC searchFAQ '#tagArray[i]#'
</cfquery>
<cfset queryAddRow(bigQ)>
<cfset querySetCell(bigQ, "column". qryGetSPFAQs)>
</cfloop>
You will need a querySetCell() assignment for each column. Check out the query functions in the live docs for more information.
Here is an out of the box solution, abandoning the StoredProc for a SQL View (I'll explain).
Disclaimer: without seeing the SP source code, I can't tell if my solution fits. I'm assuming that the SP is fairly basic, and I admit I usually prefer the compiled execution of an SP over a view, but the one-time execution of a SQL View should outperform the looping of the SP x times.
First make a view that looks like the SELECT statement in the SP (minus the parameterization, of course -- you'll cover that in a WHERE clause within the CFQUERY of your new view.
Second, set up your loop to do no more than build a data set we're going to use for the WHERE clause. You'll need to use ArrayToList and a little bit of string manipulation to tidy it up, with the end product being a string stored in a single CF variable looking like this:
('ValueOfArrayElement1','ValueOfArrayElement2','Value_And_So_On')
Building the string is pretty easy, using the delimeter attribute of ArrayToList, and after the loop is complete, append a Left Parenthesis & Single Quote to the Left most position of the string; and append a Single Quote & Right Parenthesis to the Right most position in the string.
Now, write the CFQUERY statement to SELECT the columns you need from your view (instead of executing your SP). And instead of passing a parameter to the SP, you're going to put a WHERE clause in the CFQUERY.
Oh, BTW, I am stating you need a SQL View, but the entire SELECT could be built in CFQUERY. Personally, when I have a multi-table JOIN, I like to define that in a SQL View where it's executed more quickly than a JOIN in CFQUERY. Ultimately a StoredProc is even faster, but our WHERE clause is much friendlier to code and read like this than it would be to send into StoredProc without looping in and out of SQL.
It's a good goal to make only one trip out to the database and back if possible. That's why we looped through the array to write a string equating to all the values in the dataset. This way, we'll only execute one query, one time.
SELECT Col1, Col2, Col_etc
FROM SQL_View_Name
WHERE ColumnName in #BigStringWeMadeFromArrayToList#
when our CFQUERY is rendered, the clause will look just like this in SQL:
WHERE ColumnName in
('ValueOfArrayElement1','ValueOfArrayElement2','Value_And_So_On')
So there you have it. Like I said, this is nice because it makes only one trip to the DB, and since we are building a view, the performance will still be pretty good -- better than running a StoredProc 4+ times. (no offense)
I'll must repeat... without having seen the SP code, I'm not sure if this is do-able. Plus, it's kind of odd to abandon a StoredProc for a SQL View, a "lesser" entity in the RDBMS, but I'm sure we will achieve greater performance and I think it's pretty readable, too.