Posts Tagged ‘Comparisons’

Performing arithmetic with SQL or Coldfusion

Saturday, September 18th, 2010

I’ve been running through some online courses concerning Oracle systems.  One of the earlier lessons discusses performing arithmetic with your SQL statement so that not just the data in the table is returned, but manipulated table data is returned as well.  This peaked my interest since it’s so simple, and yet I’ve never really considered using SQL to do basic arithmetic work from within the SQL statement as opposed to just manipulating the base values with Coldfusion.

Just out of curiosity I ran some tests on my local system, which of course doesn’t run Oracle, but does run MySql.  The test consists of the idea that a company has a database of job applications, but after 30 days they want them to be marked as expired.  So essentially what I want to end up with is an application date plus 30 days, and then compare if that date is greater than today’s date.  All we want to potentially use the database for is adding the 30 additional days to each date in the database.  Because I didn’t have any data to work with I used the following function to enter 500 dummy records into MySql.

<cffunction name="buildQuery" displayname="Build Query" hint="Needed to build a simple set of tables with applications and creation dates." access="public" output="false" returntype="void">

	<cfoutput>
		<cfloop from="1" to="500" index="i">
			<cfset var startDate = dateFormat(now()) />
			<cfset var endDate = dateFormat(now() - 365) />

			<cfset var randomDate = randRange(startDate, endDate) />

			<cfquery name="insertApps" datasource="apps">
				INSERT INTO application_tbl (application_created)
				VALUES (#randomDate#)
			</cfquery>
		</cfloop>
	</cfoutput>

</cffunction>

This function simply takes a random date between now and one year ago and inserts it into the application_tbl table. Now we have some data to process with Coldfusion and SQL. The two functions that return the same thing, but one use CF and the other SQL are.

<!--- Process with Coldfusion --->
<cffunction name="processWithCF" displayname="Process with Coldfusion" hint="Processes the expiration date with Coldfusion" access="public" output="false" returntype="array">

	<cfset var appsQuery = getQuery() />
	<cfset var appsAry = arrayNew(1) />
	<cfset var finalDate = "" />

	<cfloop query="appsQuery">
		<cfset finalDate = application_created + 30 />

		<cfset arrayAppend(appsAry, dateFormat(finalDate, "full")) />
	</cfloop>

	<cfreturn appsAry />

</cffunction>

<cffunction name="getQuery" displayname="Get Query" hint="Gets a query full of applications and dates created." access="private" output="false" returntype="query">

	<cfset var thisQuery = "" />

	<cfquery name="thisQuery" datasource="apps">
		SELECT *
		FROM application_tbl
	</cfquery>

	<cfreturn thisQuery />

</cffunction>
<!--- Process with SQL --->>
<cffunction name="processWithSQL" displayname="Process with SQL" hint="Processes the expiration date with SQL" access="public" output="false" returntype="array">

	<cfset var appsQuery = getQueryProcessed() />
	<cfset var appsAry = arrayNew(1) />

	<cfloop query="appsQuery">
		<cfset arrayAppend(appsAry, dateFormat(application_created, "full"))>
	</cfloop>

	<cfreturn appsAry />

</cffunction>

<cffunction name="getQueryProcessed" displayname="Get Query" hint="Gets a query full of applications and dates created." access="private" output="false" returntype="query">

	<cfset var thisQuery = "" />

	<cfquery name="thisQuery" datasource="apps">
		SELECT application_id, application_created + 30 AS application_created
		FROM application_tbl
	</cfquery>

	<cfreturn thisQuery />

</cffunction>

The only difference between the two is that with the function getQueryProcessed() the SQL statement returns ‘application_created + 30 AS application_created’. This is returning a date created 30 days later than the actual date created. In the getQuery() function the statement is returning the raw data, but I am then using Coldfusion ‘finalDate = application_created + 30′ to set the date to 30 days in advance of the actual date.

In order to time the processing of both simple formats I use the following index page which outputs the average processing time for both according to 1000 iterations over both methods. The index page that monitors performance is the following.

<!--- set of lists to keep up with each time of processing --->
<cfset variables.cfProcessingList = "" />
<cfset variables.sqlProcessingList = "" />

<cfset VARIABLES.process = createObject("component", "processExpiration").init() />

<cfset variables.startCount = getTickCount() />

<cfloop from="1" to="1000" index="variables.i">
	<cfset VARIABLES.processedWithCF = VARIABLES.process.processWithCF() />
</cfloop>

<cfset variables.endCount = getTickCount() />

<!--- time to process and output instance --->
<cfset variables.processingTime = variables.endCount - variables.startCount />

<!--- append processing time to list --->
<cfset variables.cfProcessingList = listAppend(variables.cfProcessingList, variables.processingTime, ",") />

<cfset variables.startCount = getTickCount() />

<cfloop from="1" to="1000" index="variables.i">
	<cfset VARIABLES.processedWithSQL = VARIABLES.process.processWithSQL() />
</cfloop>

<cfset variables.endCount = getTickCount() />

<!--- time to process and output instance --->
<cfset variables.processingTime = variables.endCount - variables.startCount />

<!--- append processing time to list --->
<cfset variables.sqlProcessingList = listAppend(variables.sqlProcessingList, variables.processingTime, ",") />

<!--- average processing times --->
<cfset variables.cfProcessingAvg = arrayAvg(listToArray(variables.cfProcessingList, ",")) />
<cfset variables.sqlProcessingAvg = arrayAvg(listToArray(variables.sqlProcessingList, ",")) />

<cfoutput>
	<br /><br /><br />
	CF Processing Average: #variables.cfProcessingAvg#<br /><br />
	SQL Processing Average: #variables.sqlProcessingAvg#
</cfoutput>

The results:
Average Coldfusion processing time – 23509 ms
Average SQL processing time – 34163 ms

Considering that this was 1000 iterations we’re talking marginal differences at best. As such, when I see two ways of performing the same process that takes roughly the same amount of time to process, I immediately begin looking at design. This is an instance where I do feel the context in which this type of performing is needed should determine the method that is used. Nonetheless, with my experience I think the majority of the time using Coldfusion to perform the data manipulation portion is best practice.

I say this because what does take time are database connections and the process of calling data from a database. Let’s take our theoretical example above and say that we want to not only see which applications are being thrown away, but also those that would be thrown away if our cutoff was 60 days old and 90 days old. To do this the really bad way, we would write three different SQL statements, each adding 30, 60, and 90 days to each returned result. A much better way of doing it would be to return each set within the same SQL statement so that we are only making one database call. This statement would look like:

SELECT application_id, application_created + 30 AS application_created_thirty, application_created + 60 AS application_created_sixty, application_created + 90 AS application_created_ninety
FROM application_tbl

Even better and more flexible is to simply return the data with SQL and then use Coldfusion to do all data manipulation. It will simply run faster and be more flexible to change easily over time. An abstract function can be written that adds a passed in number to a passed in value and returns the result. This doesn’t restrict us to statements. The abstract function could look like the following.

<cffunction name="addStuff" displayname="Add Stuff" hint="Adds any passed in number to any passed in value and returns it." access="public" output="false" returntype="Numeric">

	<cfargument name="value" displayname="Value" hint="Value to add to." type="numeric" required="true" />
	<cfargument name="toBeAdded" displayname="To Be Added" hint="Number to be added to value." type="numeric" required="true" />

	<cfset var result = ARGUMENTS.value + ARGUMENTS.toBeAdded />

	<cfreturn result />

</cffunction>

Now after returning the raw data from the database the above function can be used for manipulation as such.

<cfset VARIABLES.result1 = addStuff(application_created, 30) />
<cfset VARIABLES.result2 = addStuff(application_created, 60) />
<cfset VARIABLES.result3 = addStuff(application_created, 90) />

The beauty of this is that we can continue to add to it simply and quickly. Let’s say our boss came to us and wanted to see how many applications would be left under 45 and 50 days old. We would just add to our code:

<cfset VARIABLES.result4 = addStuff(application_created, 45) />
<cfset VARIABLES.result5 = addStuff(application_created, 50) />

So this is how I see it. There are probably situations where it would make sense to process arithmetic with SQL, but for the most part I feel performing data manipulation with Coldfusion is the way to go. Nonetheless, having the ability to do these things with SQL alone is certainly a powerful tool to have in one’s arsenal.

Coldfusion functions vs. custom tags for repetitive processing

Thursday, September 9th, 2010

While I believe that everything has a time and place, I can’t think of many places where I would choose to use a custom tag over a component.  Beyond the fact that on average performing processing with a function in a component is between 20 and 30 times faster than processing and displaying with a custom tag (the details of which I explain below), there are a number of design arguments for executing any type of processing (business logic or display) with a function as opposed to a custom tag.

Let’s begin with the limitations of how a custom tag can be used vs. how a function can be used. Custom tags are written onto a page any number of times, and for that tag to execute any existing page dependencies must also execute. While many public functions are used in the same exact capacity, that same function can be refactored into a truly encapsulated private function only accessible by other functions within the same component.

This brings me to my next point – access. Custom tags are only as good as themselves and the data passed to them via the page from which they are called. CFCs and public functions on the other hand can be used in that same capacity, but in mere moments be made private or exposed as a webservice. This flexibility and ability to conceal or reveal processing at will is a major advantage over the custom tag that will always render the same way. Also concerning access, is the variety of ways in which a function can use data to perform processing. If using a component framework like Coldspring data can be set into a CFC on application start which then becomes available to any functions within or extending the component from anywhere in the application. On this note, let’s look at speed.

Inevitably functions in CFCs will run faster than custom tags since running functions returns instances while custom tags will fully process with each call. As such the more repetitive the processing the function is doing the more performance there is to be gained. To illustrate this difference I wrote the following small test:

index.cfm

<!--- set of lists to keep up with each time of processing --->
<cfset variables.instanceProcessingList = "" />
<cfset variables.tagProcessingList = "" />

<cfloop from="1" to="1" index="j">
	<!--- create an instance of our plan --->
	<cfset variables.futureInstance = createObject("component", "cfc_generateFuture").generateFuture() />

	<cfset variables.startCount = getTickCount() />

	<!--- display instance --->
	<cfloop from="1" to="1000" index="i">
		<cfoutput>#variables.futureInstance#</cfoutput>
	</cfloop>

	<cfset variables.endCount = getTickCount() />

	<!--- time to process and output instance --->
	<cfset variables.processingTime = variables.endCount - variables.startCount />

	<!--- append processing time to list --->
	<cfset variables.instanceProcessingList = listAppend(variables.instanceProcessingList, variables.processingTime, ",") />

	<br /><br /><br />

	<cfset variables.startCount = getTickCount() />

	<!--- call custom tag --->
	<cfloop from="1" to="1000" index="i">
		<cf_generateFuture randomDate="#dateFormat(now())#" randomPlaceIndex="23" randomNounIndex="30">
	</cfloop>

	<cfset variables.endCount = getTickCount() />

	<!--- time to process and output custom tag data --->
	<cfset variables.processingTime = variables.endCount - variables.startCount />

	<!--- append processing time to list --->
	<cfset variables.tagProcessingList = listAppend(variables.tagProcessingList, variables.processingTime, ",") />
</cfloop>

<!--- average processing times --->
<cfset variables.instanceProcessingAvg = arrayAvg(listToArray(variables.instanceProcessingList, ",")) />
<cfset variables.tagProcessingAvg = arrayAvg(listToArray(variables.tagProcessingList, ",")) />

<cfoutput>
	<br /><br /><br />
	Instance Average: #variables.instanceProcessingAvg#<br /><br />
	Custom Tag Average: #variables.tagProcessingAvg#
</cfoutput>

Custom Tag File

<cfset var listOfPlaces = "Anguilla,Antigua and Barbuda,Australia,
Australian Antarctic Territory,Bahamas,Bangladesh,Barbados,Belize,Bermuda,
Botswana,British Antarctic Territory,British Indian Ocean Territory,British Virgin
Islands,Brunei,Cameroon,Canada,Cayman Islands,Channel Islands,Cook Islands,
Cyprus,Dominica,Falkland Islands,Falkland Islands Dependencies,Fiji,Gambia,
Ghana,Gibraltar,Grenada,Guyana,India,Isle of Man,Jamaica,Kenya,Kiribati,Lesotho,
Malawi,Malaysia,Maldives,Malta,Mauritius,Montserrat,Mozambique,Namibia,
Nauru,New Zealand,Nigeria,Niue,Norfolk Island,Pakistan,Papua New Guinea,
Pitcairn Islands,Ross Dependency,Seychelles,Sierra Leone,Singapore,Solomon
Islands,South Africa,Sri Lanka,St Christopher-Nevis,St Helena,St Lucia,St Vincent
and the Grenadines,Swaziland,Tanzania,Tokelau,Tonga,Trinidad and Tobago,
Turks and Caicos Islands,Tuvalu,Uganda,Vanuatu,Western Samoa,Zambia,
Zimbabwe" />

<cfset var listOfNouns = "alarm,animal,aunt,bait,balloon,bath,bead,
beam,bean,bedroom,boot,bread,brick,brother,camp,chicken,children,crook,
deer,dock,doctor,downtown,drum,dust,eye,family,father,fight,flesh,food,frog,
goose,grade,grandfather,grandmother,grape,grass,hook,horse,jail,jam,kiss,
kitten,light,loaf,lock,lunch,lunchroom,meal,mother,notebook,owl,pail,parent,
park,plot,rabbit,rake,robin,sack,sail,scale,sea,sister,soap,song,spark,space,
spoon,spot,spy,summer,tiger,toad,town,trail,tramp,tray,trick,trip,uncle,vase,
winter,water,week,wheel,wish,wool,yard,zebra" />

<cfset variables.randomPlace = listGetAt(variables.listOfPlaces, attributes.randomPlaceIndex, ",") />
<cfset variables.randomNoun = listGetAt(variables.listOfNouns, attributes.randomNounIndex, ",") />

<cfoutput>#attributes.randomDate#: Visit the #variables.randomNoun#s of #variables.randomPlace#.<br /></cfoutput>

Component

<cfcomponent displayname="Generate Future Plans" hint="Generates a set of plans for the next year" output="false">

	<cffunction name="generateFuture" displayname="Generate Future Plans" output="false" access="public" returntype="string">

		<cfset var listOfPlaces = "Anguilla,Antigua and Barbuda,Australia,
Australian Antarctic Territory,Bahamas,Bangladesh,Barbados,Belize,Bermuda,
Botswana,British Antarctic Territory,British Indian Ocean Territory,British Virgin
Islands,Brunei,Cameroon,Canada,Cayman Islands,Channel Islands,Cook Islands,
Cyprus,Dominica,Falkland Islands,Falkland Islands Dependencies,Fiji,Gambia,
Ghana,Gibraltar,Grenada,Guyana,India,Isle of Man,Jamaica,Kenya,Kiribati,Lesotho,
Malawi,Malaysia,Maldives,Malta,Mauritius,Montserrat,Mozambique,Namibia,
Nauru,New Zealand,Nigeria,Niue,Norfolk Island,Pakistan,Papua New Guinea,
Pitcairn Islands,Ross Dependency,Seychelles,Sierra Leone,Singapore,Solomon
Islands,South Africa,Sri Lanka,St Christopher-Nevis,St Helena,St Lucia,St Vincent
and the Grenadines,Swaziland,Tanzania,Tokelau,Tonga,Trinidad and Tobago,
Turks and Caicos Islands,Tuvalu,Uganda,Vanuatu,Western Samoa,Zambia,
Zimbabwe" />

		<cfset var listOfNouns = "alarm,animal,aunt,bait,balloon,bath,bead,
beam,bean,bedroom,boot,bread,brick,brother,camp,chicken,children,crook,
deer,dock,doctor,downtown,drum,dust,eye,family,father,fight,flesh,food,frog,
goose,grade,grandfather,grandmother,grape,grass,hook,horse,jail,jam,kiss,
kitten,light,loaf,lock,lunch,lunchroom,meal,mother,notebook,owl,pail,parent,
park,plot,rabbit,rake,robin,sack,sail,scale,sea,sister,soap,song,spark,space,
spoon,spot,spy,summer,tiger,toad,town,trail,tramp,tray,trick,trip,uncle,vase,
winter,water,week,wheel,wish,wool,yard,zebra" />

		<cfset var startDate = dateFormat(now()) />
		<cfset var endDate = dateFormat(now() + 365) />

		<cfset var randomDate = dateFormat(randRange(startDate, endDate), "full") />

		<cfset var numOfPlaces = listLen(listOfPlaces) />
		<cfset var numOfNouns = listLen(listOfNouns) />

		<cfset var randomPlaceIndex = randRange(1, numOfPlaces) />
		<cfset var randomNounIndex = randRange(1, numOfNouns) />

		<cfset var randomPlace = listGetAt(listOfPlaces, randomPlaceIndex, ",") />
		<cfset var randomNoun = listGetAt(listOfNouns, randomNounIndex, ",") />

		<cfreturn "#randomDate#: Visit the #randomNoun#s of #randomPlace#.<br />">

	</cffunction>

</cfcomponent>

If iterating over this code only once, I got the following results:

Custom Tag: Processed in 80ms
Component: Processed in 3ms

If iterating over this code 100 times, I got the following results:

Custom Tag: On average processed at 198.27ms
Component: On average processed at 6.52ms

This was run locally on CF9. Clearly there are major performance differences here.

I’ll quickly mention the ability to return data upon completed processing. Let me state that if you are returning data with your custom tag you should really be using a CFC. While you can return data with a custom tag it is not strongly typed leaving much room for errors that become difficult to debug, as well as being slower, less extendible, and generally less flexible.

Difficulty debugging brings me to my last point which is the inability to run unit tests on a custom tag. Functions are easily maintained and monitored, while custom tags only can not be hooked up to unit testing software.

Clearly I have a lop-sided view of what should be used for all types of processing, be it display or business logic processing. I would probably only use a custom tag if my only options were between a custom tag and an include. Nonetheless, we have a clearly more superior way of processing available that improves the performance, design, and general code readability and organization.