But Officer... The sign said 85!!!

Goog Feed, General, Photography    Comments (2)


So the other day, LeeAnn and I were driving home when she busted out laughing. She points to this speed limit sign on the side of the road, which some vandals obviously altered.

It looked pretty believable at first glance till you notice the paint streaks running down the side. We had to turn around and get a picture, it was hilarious to see!

Maybe someone needed an excuse for the officer, who knows ;)


From a distance, or at a quick glance this would totally throw you off!

Comparing the Match Percentage of Two Strings

Goog Feed, General, ColdFusion    Comments (4)


Today at work, my boss asked me to check into "Fuzzy Match" capabilities in ColdFusion for a project we're working on here at work. Basically we want to compare two strings and find out what is the percentage of match between the two. For example does string one match string two with 70% accuracy or better?

So after a couple Google searches I came upon a partial solution. All signs pointed to the fact that I would have to use the Levenshtein distance formula which calculates the number of replacements, insertions or deletions necessary to match string one exactly up to string two.

I happened across a ColdFusion UDF written by Nicholas Zograpos which uses the Levenshtein formula to do this computation and passes back the number of characters needed to change string one into string two. You can find the UDF here.

I put this UDF at the top of my CFC.

<cfscript>
/**
* Computes the Levenshtein distance between two strings.
*
* @param s First string. (Required)
* @param t Second string. (Required)
* @return Returns a number.
* @author Nicholas Zographos (nicholas@nezen.net)
* @version 1, March 15, 2004
*/
function levDistance(s,t) {
var d = ArrayNew(2);
var i = 1;
var j = 1;
var s_i = "A";
var t_j = "A";
var cost = 0;

var n = len(s)+1;
var m = len(t)+1;

d[n][m]=0;

if (n is 1) {
return m;
}

if (m is 1) {
return n;
}

for (i = 1; i lte n; i=i+1) {
d[i][1] = i-1;
}

for (j = 1; j lte m; j=j+1) {
d[1][j] = j-1;
}

for (i = 2; i lte n; i=i+1) {
s_i = Mid(s,i-1,1);

for (j = 2; j lte m; j=j+1) {
t_j = Mid(t,j-1,1);

if (s_i is t_j) {
cost = 0;
}
else {
cost = 1;
}
d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1);
d[i][j] = min(d[i][j], d[i-1][j-1] + cost);
}
}

return d[n][m];
}
</cfscript>

Next, I created a function of my own inside of my CFC which calls the levDistance function passing in two strings, it then divides the result by the length of the string we want to match against (stringTwo) and multiplies by 100 which gives the percentage of error between the two strings in numeric format. Finally I compare the error percentage with my threshold or my max error percentage I am willing to allow, passing the user back a true/false based on the result.

You'll find my function below.

<cffunction name="stringCompare" access="public" returnType="boolean" output="false">
<cfargument type="any" name="stringOne" default="" required="yes">
<cfargument type="any" name="stringTwo" default="" required="yes">

<cfset var result = true />
<cfset var threshold = 30 /><!---This means that we are only allowing for a 30% error rate, 70% match in essence.--->
<cfset var stringCompare = levDistance(trim(arguments.stringOne),trim(arguments.stringTwo))/len(trim(arguments.stringTwo))*100 />

<!---Comparing the result.--->
<cfif stringCompare GT threshold>
<cfset result = false />
</cfif>

<cfreturn result>
</cffunction>

Finally, one last bit of code. How to call the function from the view.

<cfset st1 = "How are you doing?" />
<cfset st2 = "How are ya doing?" />

#myObj.stringCompare(stringOne=st1,stringTwo=st2)#

You can play with the percentage numbers as needed to fit your situation. This really helped me in a problem I faced, hopefully it will do the same for you!

SQL 101 - Counts By Location and Grade

Goog Feed, General, SQL    Comments (0)



The Desired Result

The other day at work there was a need for a report that our reporting system was having trouble putting out. So the fix was to write it ourselves. It's not too complex and for some it may be quite elementary, but here it is none-the-less.

Being that I work for a school district there was a need for a report by campus and grade level of the number of students that fell into a certain category. So for the sake of this report we want a count of all active students based on their campus and their grade level.

Below is the stripped down version of how this was accomplished.

SELECT Campus, Grade, COUNT(studentID) AS StudentCount
FROM ourStudentTable
WHERE studentStatus = 'A'
GROUP BY Campus, Grade
ORDER BY Campus

Hopefully this helps!

SQL - Using Sub Queries As Joining Tables

Goog Feed, General, SQL    Comments (2)


Today at work I faced a dilemma. I was tasked with developing a function in which a parent could be notified when their child has an absence or a tardy in one of their classes (I work for a school district). It involved touching 3 different tables including one twice. The three types of tables are as follows, an attendance details table which we'll call AttendanceDetail, a parent/student table which ties parents with students which we'll call ParentStudent, and finally a notify parent when an absence occurs table which we'll call AttendanceNotification.

So here is the short of how the process works. Daily a routine will run checking to see if a student whose parent requested a notification upon an attendance event had missed a class and/or was tardy to class. Sounds simple enough huh? Well it wasn't as easy as first thought.

My first query was as follows...

SELECT an.studentID, ps.studentFirstName, ps.studentLastName, ad.absence_date
FROM AttendanceNotification an
INNER JOIN ParentStudent ps ON ps.studentID = an.studentID
INNER JOIN AttendanceDetail ad ON ad.studentID = an.studentID
WHERE an.notify = 1 AND ps.status = 'A'

Basically this query grabs all prior absence events for a student where they are active and the parent needs to be notified of the last absence event. So we have a bunch of unwanted events here, we just need the most recent event.

Simple enough we'll just add a couple things to our query and we'll be good to go right? Not so fast. So I added a MAX() function to the absence date which would give me the most recent absence and I grouped by the studentID which would only give me one record back per student and I ended up with the following.

SELECT an.studentID, ps.studentFirstName, ps.studentLastName, MAX(ad.absence_date) AS absenceDate
FROM AttendanceNotification an
INNER JOIN ParentStudent ps ON ps.studentID = an.studentID
INNER JOIN AttendanceDetail ad ON ad.studentID = an.studentID
WHERE an.notify = 1 AND ps.status = 'A'
GROUP BY an.studentID, ps.studentFirstname, ps.studentLastname

Well, the problems began to start, I needed to be able to show the parent what the absence event detail was. Did their student have a tardy? An unexcused absence? A doctors appointment? You get the point. My first thought was to just add it into the SELECT statement for the above query, but then I had to include them in my GROUP BY clause which caused more than one record per student to be returned. I then thought about trying to wrap them in a MAX function, that didn't work, they needed an aggregate function to be included in my SELECT criteria and not in my GROUP BY, I was at a loss.

Then I got an idea, I wondered if I could JOIN on a sub query. I had never tried it before, but it worked perfectly! I thought I'd share it with you just in case you ever ran across a similar problem.

The final query does an INNER JOIN from the AttemdanceDetail table to the previous result set or sub query. Now we're able to get detailed absence info yet only the most recent record. Here is the final query.

SELECT
   a.studentID,
   a.studentFirstName,
   a.studentLastName,
   a.absenceDate,
   CASE WHEN ltrim(rtrim(adII.am_office_abs)) IS NULL THEN ltrim(rtrim(adII.am_teacher_abs)) ELSE ltrim(rtrim(adII.am_office_abs)) END AS am,
   CASE WHEN ltrim(rtrim(adII.pm_office_abs)) IS NULL THEN ltrim(rtrim(adII.pm_teacher_abs)) ELSE ltrim(rtrim(adII.pm_office_abs)) END AS pm
FROM AttendanceDetail adII
INNER JOIN
(SELECT an.studentID, ps.studentFirstName, ps.studentLastName, MAX(ad.absence_date) AS absenceDate
FROM AttendanceNotification an
INNER JOIN ParentStudent ps ON ps.studentID = an.studentID
INNER JOIN AttendanceDetail ad ON ad.studentID = an.studentID
WHERE an.notify = 1 AND ps.status = 'A'
GROUP BY an.studentID, ps.studentFirstname, ps.studentLastname) a
ON a.studentID = adII.studentID AND a.absenceDate = adII.absence_date

Perhaps there is a better way to do this, I just don't know how, but this worked so I ran with it.

CFUnited: My Recap

Goog Feed, General, ColdFusion, CFUnited 2008, SQL    Comments (0)


CFUnited 2008 was a great experience for me personally, I presented as a speaker for the first time which was a great experience and challenge, as well as learned a great deal of things that I would like to put into practice in my own development environment.

I wanted to put a quick post out there to go over some of the highlights that I was able to take away learning wise for others to see as well as a constant reminder to myself. So, what did I personally learn?

  • In his own words, Ray Camden will be using tables for layout until someone stops him... Should've come to my session Ray!
  • I want to start using CFToolTip more for my end-users benefit.
  • There is a cool Event Validator at RiaForge that Mark Drew referenced, I would like to check into that when I get the time.
  • From Hal Helms session, leave functions open for extention, closed for changes. Keeping things simple
  • Just because MVC is used, doesn't mean I'm doing OO. I can still be doing procedural code in an MVC format.
  • Get book called Design Patterns by Gang of Four
  • Find the things that are constantly changing in my applications and encapsulate them.
  • Good OO is all about abstraction.
  • Pass objects to be persisted.
  • From Chris Scott's session, keep ColdSpring simple - getBeans/setBeans
  • Increase the JVM default on the CF Server.
  • Increase the minimum memory on the CF Server.
  • Turn on trusted cache in production.
  • Cache queries used often in applications.
  • Read the CF8 performance brief.
  • Use SQL Server performance monitoring tools to profile, trace, look at execution plans and their costs, run database reports to see which queries are putting loads on the server.
  • If session is not available, reject the form submission, because it means they didn't come from your form.
  • From Joe's advanced Model-Glue session... you can use the include tags to include entire applications into your Model-Glue app.
  • Prefix event handlers.
  • Controllers should be as thin as possible.
  • Use views for common joins, filtering, etc... when dealing with larger queries.
  • Index all fields searched on in my SQL tables for fast searching and less load on the database. A good example of this is looking for pizza in the phone book, but searching every page of the book from A-Z. With indexing you don't need to search from A-Z simply jump to 'Pizza'.
  • SQL views can be about 75% faster than CF for complex queries.
  • Stored procedures can be about 90% faster than CF.
  • Use cfQueryParams for security as well as not making the SQL Server re-write execution plans everytime a query is run.
  • Be careful about how you index your tables, you could do more damage than good if it's not done right.

That was my quick recap of what I learned. Overall the venue was great, I loved the fact that the conference was in downtown DC instead of Bethesda, the hotel was awesome and close to the conference. Another great job by the CFUnited team. I hope to return in 2009.

More Entries