Resetting cached query with cachedwithin - coldfusion

Inside a cffunction I have a query that I want to long cache by setting the cachedwitin to a big value. However, I also want to clear that cache under a few circumstances.
I've done this in a number of places, but in one of them the cache is never updated and I always get the original value if I ask it to give me the cached value.
The cfquery documentation states:
To use cached data, the current query must use the same SQL statement, data source, query name, user name, and password.
This is the case with the query, since it's a single sql statement with no non-sql except the cfqueryparam; datasource and query name don't change and username and password are not specified.
The relevant code is:
<!--- Long cache the query since the values rarely change, but allow the cache to be cleared. --->
<cfif Arguments.ClearCache EQ false>
<cfset local.CachedWithin = CreateTimeSpan(7,0,0,0)>
<cfelse>
<cfset local.CachedWithin = CreateTimeSpan(0,0,0,-1)>
</cfif>
<cfquery name="local.qryName" datasource="#Variables.DSN#" cachedwithin="#local.CachedWithin#">
SELECT
[User].[Name]
FROM
[User]
WHERE
[User].[UserID] = <cfqueryparam value="#Arguments.UserID#" cfsqltype="cf_sql_integer">
</cfquery>
Why does this work in other places, but not here?

There is an additional "sameness" requirement for cached queries that isn't mentioned in the documentation. That note should end with:
... password, and underlying data types of any cfqueryparam values.
Change your code so that the Arguments.UserID is forced to a data type. In your case this can be accomplished by putting the following code before the cfquery:
<!--- Make sure data types used in query are consistent --->
<cfset local.UserID = Int(Arguments.UserID)>
and change the cfqueryparam value to value="#local.UserID#".
I've phrased the above as an omission in the documentation, but I suspect it's a bug in CF9 instead.

Related

Using Memcached with Coldfusion

I am trying to use memcached for the first time but I have a few questions when it comes to queries with dynamic variables.
This is a sample of my code so far. I got everything working but if you see any mistakes on my code please let me know.
<cfoutput>
<cfset variables.memcachedFactory = createObject("component","memcachedfactory").init("127.0.0.1:11211")>
<cfset variables.memcached = variables.memcachedFactory.getmemcached()>
<cfset email = "sbcglobal"> //Pretend that this is a list with multiple choices for the user to choose from.
//The query below would have multiple outputs based on the selection of the user above
<cfquery name="test" datasource="#ds#">
select top 10 * from contact where email like '%#email#%'
</cfquery>
//Setting the key
<cfset set100 = variables.memcached.set(key="queryTest2",value=test,expiry="5000")>
//Getting the key
<Cfset test6 = variables.memcached.get("queryTest2")>
<br><br>this is the query test - did we retrieve it correctly? - <br />
<Cfdump var="#Test6#"><br />
</cfoutput>
Now, do i have to set a key for every possible outcome of the query? (which in my case would be a few hundred keys to cover all possible outcomes)
My understanding is that memcached saves the value to a key after the first request and after that you get the output without db calls.
If I use memcached in my case would it just be a waste of code since each query would be different based on the user's choice?
Also, how do you update the values in cache in case the output changes? Is this something that I would have to do manually? My database get 100's of transactions daily, a select * from contacts at 10 am will not have the same output at 5pm
The idea behind caching (in this case) is that you first check memcached, and if the key exists, you get the data from the cache. If it doesn't exist, you go to the database, get the data, stick it in memcached, then return it to the user.
For your use case here, you want a unique key for each unique record set you're returning. Take your example. If email address is the unique key you are using to identify a record set, then use that as the key in memcached. Pseudo-code:
<cfset results = variables.memcached.set(key="foo_#email#",value=test,expiry="5000")>
foo_ lets you provide a "namespace" for the keys you are putting into memcached, which makes it easier to manage and avoid key collisions.

Avoiding Magic values

I have some code that only does updates
<cfif tags NEQ "skip">
<cfquery>
UPDATE myTable
SET tags = <cfqueryparam cfsqltype="CF_SQL_varchar" value="#arguments.tags#">
WHERE ID = <cfqueryparam cfsqltype="CF_SQL_INTEGER" value="#arguments.ID#">
</cfquery>
What would be a better approach than using "skip" as a magic value? Note that blank is a valid value.
It depends on where the data is coming from. Ideally, I'd set a variable to true or false and check that. I'm guessing that in your example tags is coming from a form and that 'skip' is the value in a for 'None of these'. You've probably got to use something as a magic value, but I'd favour using something like __SKIP_TAGS__ which is very clear.
You could also split up your page logic, so that you process the form submission first, checking for magic values and setting up a boolean for whether to insert data from #tags#, then use the boolean later on for your check. The overall logic would be the same, but better laid out.
You might want to consider adding a bit more context to your question, like where the data is coming from, so that others can give you better advice

How to check for a null value from the return value in ColdFusion query loop

<cfloop query="GET_ALL_STUDENTS>
<cfif #student_id# is NOT NULL>
<!--- do something--->
</cfif>
</cfloop>
Above is how I am looping my cf query which returns null value and I want to check if the student_id is null or not. This is what I have tried and it failed. Can anyone tell me a better way?
You can use your database's ifNull() or the like. However, in ColdFusion, queries are returned as strings. Given your situation, easiest way is to check for a non-empty string:
<cfif len(student_id)>
By the way, you don't need the pound signs inside of an evaluation: only when using a variable as a literal (such as when outputting)
In Adobe ColdFusion 9, you can do:
<cfif IsNull(student_id)>
</cfif>
Or since you're doing the opposite:
<cfif NOT IsNull(student_id)>
</cfif>
It looks like the query is retrieving all of the students and then cfloops over the records to find the student_id fields that are NULL.
It would be more efficient to write a query that specifically queried the records that have student_id IS NULL.
The method of grabbing all the student table records will work great when you have 100 or so students. What happens when it is put into production and there are 25,000 students?
While the java class of CFQuery object (coldfusion.sql.QueryTable) will return empty string for any null value, it's parent class coldfusion.sql.Table is providing a method getField(row, column) to access the query table values directly, which return "undefined" if the value is null. We can make use of the IsNull to identify the "undefined" hence able to detect NULL.
<CFLOOP query="GET_ALL_STUDENTS">
Row = #CurrentRow#
<CFIF IsNull(GET_ALL_STUDENTS.getField(GET_ALL_STUDENTS.CurrentRow, GET_ALL_STUDENTS.findColumn('student_id')))>
[NULL]
<CFELSE>
#GET_ALL_STUDENTS.student_id#
</CFIF>
<br>
</CFLOOP>
Reference: http://laxmanthota.blogspot.com/2010/11/cfquery-and-underlying-java-objects.html

Combining query rows in a loop

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.

Session Variables, welcome messages

Why does this not work? My welcome message, it just doesn't show up:
<p>Welcome <cfoutput>#Recordset1.UserID#</cfoutput>.</p>
The session variable on the login page I created is:
<cflock timeout=999 scope="Session" type="Exclusive">
<cfset Session.IDUsers =''>
</cflock>
is this incorrect? On the index page where I'm trying to display my welcome message I have:
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
I'm not sure if this works, or is necessary?
If you set the userid stored in the session to be the empty string, when you query on it, you will only get users for whom the id is the empty string, which shouldn't be any of them. Therefore, the query is returning an empty set, and your page is (correctly) not displaying a user id.
How are you initially identifying the user? Are you querying against a database when they log in? Are you storing a cookie? Reading Tarot cards? For this to work, at some point, you have to store the correct userid, probably in the session. To do that, you need to first identify who the user is.
Also, if you are using CF6+, you probably do not need the cflock. It is now used to prevent race conditions, as CF is now thread-safe.
Looks like you're just starting with CF, welcome to the community.
My understanding of your code makes the structure look like the following, if I'm understanding you correctly:
<cfset session.idUsers = '' />
<cfquery datasource = "cfgossip" name = "recordset1">
SELECT * FROM USERS WHERE USERS.ID_USERS = <cfqueryparam cfsqltype = "cf_sql_integer" value = "#session.idUsers# />
</cfquery>
<cfoutput>Welcome #recordset1.userID#</cfoutput>
The reason this doesn't work is because your session.idUsers value is blank. Assuming you have a user in your database with an ID_USERS value of 1, you could change the CFSET to look like and it should return results.
Additionally, while it's great to see you using CFQUERYPARAM, I'd recommend including a CFSQLTYPE attribute in the tag whenever possible to provide an added line of defense against injection attacks. You can check out http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=Tags_p-q_18.html to see the list of available types.
Is there anywhere in your code where you set your session.IDUsers? You initialize it as a blank ''. Coldfusion does not populate it for you. The session scope is a place that will remember things for that user that you put there for a specified period of time inactivity, usually 20 minutes. So hopefully, somewhere before you run your query you have additional logic that fills that in, otherwise you are asking the database for a user named, ''.
This is just a point of style, but the following may work better for you:
<cfset Session.IDUsers =''>
<!--- Do something here to populate Session.IDUsers --->
<!--- Creates a blank query - not necessary, but can reduce errors later --->
<cfset Recordset1 = queryNew("UserID")>
<!--- Populate the query from the database --->
<cfquery name="Recordset1" datasource="cfGossip">
SELECT *
FROM users
WHERE users.IDUsers = <cfqueryparam value="#Session.IDUsers#">
</cfquery>
<!--- If the query has data, use it, otherwise show generic message --->
<cfoutput>
<cfif Recordset1.recordcount>
<p>Welcome #Recordset1.UserID#.</p>
<cfelse>
<p>Welcome new user!</p>
</cfif>
</cfoutput>
<!--- OR since we used queryNew("userID"), we can simplify without throwing an error. ---->
<cfoutput>
<p>Welcome <cfif len(Recordset1.userID)>#Recordset1.userID#.<cfelse>new user!</cfif></p>
</cfoutput>
Putting the cfoutput outside the paragraph block will make it easier if you have additional variables to insert into the text. (but will work either way)
Regardless of all that, unless you forgot to share a bit more of the code, I think the issue is that the session.IDUsers is blank and needs to be populated before the query. I hope this helps!