ColdFusion Optimizing Case Study: Writing Orders To A CSV File

<!---
	The first optimization is to combine the two queries
	(orders and order items) into one query. This will make
	the following CFLoop a bit more complicated but will only
	require going to the database once vs. N-times where N is
	the number of the orders.
 
	I am ONLY using SELECT * because I am not sure of the
	database strucutre. Otherwise, I would NEVER use SELECT *
	as more often than not, you don't need every column (and
	for readability reasons).
--->
<cfquery name="theOrders" datasource="XXXXXX">
	SELECT
		*
	FROM
		<!---
			Create the table from the first query. I am doing
			this for two reasons:
			1. I am not sure of the DB structure.
			2. We can use a LEFT OUTER Join in this query and
			still use an INNER join on the next without any
			sort of confusion.
		--->
		(
 
			SELECT
				*
			FROM
				Orders
			LEFT OUTER JOIN
				Loger
			ON
				Loger.UserID = Orders.CartID
 
		) AS o
	INNER JOIN
		realcart rc
	ON
		rc.WebOrderID = o.WebOrderID
</cfquery>
 
 
 
<!---
	Get the name of the temp file that we are going
	to write our tab delimited data to.
--->
<cfset strTempFile = (
	ExpandPath( "allOrders" ) &
	DateFormat( Now(), "yyyy_mm_dd" ) &
	".csv"
	) />
 
 
<!---
	Create a string buffer to hold our output. By writing to
	a string buffer rather than doing CFFile actions for every
	loop iteration we are going to increase our speed greatly.
	Writing to the file system is one of the most costly actions
	you can take in programming. Try to minimize it when
	possible (within reason).
--->
<cfset sbOutput = CreateObject(
	"java",
	"java.lang.StringBuffer"
	).Init() />
 
 
<!---
	Start off, instead of writing our column headers to
	the file system (costly), let's just append them to
	our string buffer.
 
	Notice that we are adding a line break at the end of the
	data line. This is because there is nothing inherent to
	the String Buffer about line breaks. In the other method,
	we were Appending data using CFFile. CFFile adds a new
	line character by default.
--->
<cfset sbOutput.Append(
	"WebOrderID CartID Accountnumber Shipping SubTotal Total Order-Hold" &
	Chr( 13 ) & Chr( 10 )
	) />
 
 
<!---
	Get the initial order ID. We are going to need to keep track
	of the order ID so that we know when to output a new order
	row (when that ID changes). To start with, set it to a value
	we know cannot possibly be valid.
--->
<cfset intOrderID = -1 />
 
 
<!--- Loop over the order/cart detail query. --->
<cfloop query="theOrders">
 
	<!---
		Check to see if we have a new order ID. If we do,
		then we need to output a mid-header row for this
		particular order.
	--->
	<cfif (theOrders.WebOrderID NEQ intOrderID)>
 
		<!---
			Store the order ID so that we don't recreate this
			mid-header row.
		--->
		<cfset intOrderID = theOrders.WebOrderID />
 
 
		<!---
			Instead of outputing the mid-header row to a file,
			let's add it to our string buffer.
		--->
		<cfset sbOutput.Append(
			"#theOrders.WebOrderID# #theOrders.CartID# #theOrders.Accountnumber# #theOrders.Shipping1# #theOrders.Subtotal# #theOrders.Total# #theOrders.Hold#" &
			Chr( 13 ) & Chr( 10 )
			) />
 
	</cfif>
 
 
	<!---
		ASSERT: At this point, we know that we are IN an order
		detail loop and that our header row has already been
		taken care of. That means that for the rest of the loop
		body, all we need to care about are the order details.
	--->
 
 
	<cfset sbOutput.Append(
		"#theOrders.cartID# #theOrders.Vendor# #theOrders.Delivery# #theOrders.Item# #theOrders.ItemNumber# #theOrders.Quantity# #theOrders.Price# #theOrders.Attention#" &
		Chr( 13 ) & Chr( 10 )
		) />
 
</cfloop>
 
 
<!---
	Now that we have all of our tab delimited data in the
	Java StringBuffer, we can write it to the file system.
	This is a bigger file write that before, but since we
	are only doing one, rather than ONCE per data row, it
	will be crazy faster.
 
	When writing the string buffer to the file, we have
	to convert it to a single string using ToString().
--->
<cffile
	action="WRITE"
	file="#strTempFile#"
	output="#sbOutput.ToString()#"
	/>

For Cut-and-Paste