Coldfusion dynamic query output - dynamic

Not sure if this is possible or not. What I am trying to do is build an output string via queries. I am concatenating the output "name" and appending the "value" to the end. Then outputting the string. I don't think this is possible. But I am looking for any alternatives.
So this is what I have:
qry1 is the main query. qry2 gets the value to append to the end of the string.
So the value of variable test would look like this: "variables.qry1.100"
Which would make sense to qry1 as this is part of the query object. So then this string would return a correct value from the database as there is a subquery called 100
<cfoutput>
<cfloop query="variables.qry2">
<cfset test = variables.qry1. & variables.qry2.#valueID#>
<td>#test#</td>
</cfloop>
</cfoutput>
Many thanks.
JC

So basically - given your example - you have a column in qry1 called 100 (etc, perhaps also 200, 300 etc), and the values 100, 200, 300 etc are row values in the valueID column of qry2? And qry1 is a single-row query? Is that right?
If you have the name of the column in a dynamic string, you use this syntax:
queryName[columnName][rowNumber]
Where queryName is the variable that is the query, columnName is the string holding the column name, and rowNumber is the row number (either an actual number, or a variable containing one).
So using your example variables, you code would be:
<td>#variables.qry1[variables.qry2.valueID][1]#</td>
There is no need to use evaluate() to do this, and it is not a good solution here. There is very seldom a need to use evaluate() in CFML, since the days of CF5.
I have no idea of the background of how your data structures came to be the way they are, but if you are needing to write the sort of code you are suggesting you need to... I'd be looking long and hard at how you're going about things.

What your trying to do is possible, but you need to build a variable name first.
Instead of
<cfset test = variables.qry1. & variables.qry2.#valueID#>
Try
<cfset test = "variables.qry1.#variables.qry2.valueID#">
Test will then be variables.qry1.[valueID value]. Note that [valueID value] is what is getting returned from the query, so the actual value inside the variable.
Then to display the value of variables.qry1.[valueID value].
#evaluate(test)#
UPDATE As stated by Adam Cameron's answer. You should really try to avoid the evaluate() function, it's quite a performance hit and not considered good practice. Instead rather use the following code (This is copied from Adam Cameron's answer)
#variables.qry1[variables.qry2.valueID][1]#
NOTE: Go look at Adam Cameron's answer for a better description of whats going on.

Adam has the correct solution. Here is a modified version of your original code that does what I think you are trying to do.
<cfoutput query="variables.qry1">
<tr>
<cfloop query="variables.qry2">
<cfset test = variables.qry1[variables.qry2.valueID][variables.qry1.currentrow]>
<td>#test#</td>
</cfloop>
</tr>
</cfoutput>

Related

Problem converting WriteOutput to usable CFOutput Variables

I am pretty sure I have done something similar before when creating dynamic reports, but this has had me stumped. I have a set of variable names that are being dynamically created, so I am performing a WriteOutput nested within a cfoutput
<cfoutput query="GetData">
<cfscript>
writeoutput(#variabnas#);
</cfscript>
</cfoutput>
But all this does is repeats the variable names for the number of records returned by the query, rather than the data.
#FieldValue1# #FieldValue2# #FieldValue1# #FieldValue2# #FieldValue1# #FieldValue2#
Do I need to wrap the WriteOut in anything else to identify it as an actual variable? Any help gratefully received, as I don't support CF that often.
To get dynamic driven variable names work, I would make use of the evaluate() function. I also wouldn't use cfoutput and writedump with cfscript at the same time. This should work:
<cfoutput>
<cfloop query="GetData">
#evaluate("#variabnas#")#
</cfloop>
</cfoutput>
But please use evaluate() with caution because of security and overhead. For further information please read this.

SSRS if field value in list

I've looked through a number of tutorials and asks, and haven't found a working solution to my problem.
Suppose my dataset has two columns: sort_order and field_value. sort_order is an integer and field_value is a numerical (10,2).
I want to format some rows as #,#0 and others as #,#0.00.
Normally I would just do
iif( fields!sort_order.value = 1 or fields!sort_order.value = 23 or .....
unfortunately, the list is fairly long.
I'd like to do the equivalent of if fields!sort_order.value in (1,2,21,63,78,...) then...)
As recommended in another post, I tried the following (if sort in list, then just output a 0, else a 1. this is just to test the functionality of the IN operator):
=iif( fields!sort_order.Value IN split("1,2,3,4,5,6,8,10,11,15,16,17,18,19,20,21,26,30,31,33,34,36,37,38,41,42,44,45,46,49,50,52,53,54,57,58,59,62,63,64,67,68,70,71,75,76,77,80,81,82,92,98,99,113,115,116,120,122,123,127,130,134,136,137,143,144,146,147,148,149,154,155,156,157,162,163,164,165,170,171,172,173,183,184,185,186,192,193,194,195,201,202,203,204,210,211,212,213,263",","),0,1)
However, it doesn't look like the SSRS expression editor wants to accept the "IN" operator. Which is strange, because all the examples I've found that solve this problem use the IN operator.
Any advice?
Try using IndexOf function:
=IIF(Array.IndexOf(split("1,2,3,4,...",","),fields!sort_order.Value)>-1,0,1)
Note all values must be inside quotations.
Consider the recommendation of #Jakub, I recommend this solution if
your are feeding your report via SP and you can't touch it.
Let me know if this helps.

VisualBasic OleDb accessing Excel spreadsheet, can't set column in query using parameter?

I'm working in Visual Basic and using OleDb to access an Excel spreadsheet. I'm importing the data from the sheet into my DataGridView, and that works fine, but now I'm working on filtering. For the most part it works great, but I'm trying to use parameters ("#p1" and so on), and I'm getting a very strange issue.
I can have the following (excluding a bunch of irrelevant stuff before, in between, and after)
query = query & "Project" & " LIKE #Gah1"
...
MyCommand.SelectCommand.Parameters.AddWithValue("#Gah1", "%House%")
and it gives me the results I'm looking for. But I can't seem to get a parameter for the name of the column itself, for example
query = query & "#Gah1" & " LIKE #Gah2"
...
MyCommand.SelectCommand.Parameters.AddWithValue("#Gah1", "Project")
MyCommand.SelectCommand.Parameters.AddWithValue("#Gah2", "%House%")
does not work (and I've tried enclosing Project in different brackets and stuff in different ways, can't get it to work). I've found plenty of examples on using parameters, but none that use them to give the column name.
I'm guessing the parameter changes how the string is represented, seeing as you don't need to have the ' ' around string literals.
Is it not possible to give column names in parameter? If you can, what do I need to do?
Well it won't let me post comment, so here
a) Oops, no, I guess not
b) The string query that I end up sending in my test query here is
"select * from [Bid Summary$] where #Gah1 LIKE #Gah2"
I can post the procedure if absolutely need be, but it isn't the problem because the whole thing works perfectly fine if I replace #Gah1 with Project or [Project], so I just showed the lines that I change.
I'm very new to parameterized queries, can you explain how to avoid query strings using it? If there's a better way to do what I'm doing I'm happy to use it =)
And thanks for response and edit
I use combination of string methods and parameters, like this:
//replace field name in a query template
query = String.Format("select * from [Bid Summary$] where {0} LIKE ?", "#Gah1");
//set value (name is in OleDb parameter ignored, so it could be null)
MyCommand.SelectCommand.Parameters.AddWithValue(null, "%House%");
Note: There is possibility of a sql injection, so be sure about origin of field name (not from user input).

ArraySum only a certain rows in the cfoutput of a query

I am trying to add only certain rows to this output. Currently this output adds all rows, which is used for a Total row at the very end of
<cfoutput query="qrySummary">
#numberFormat(ArraySum(ListToArray(ValueList(qrySummary.secThreeCount))), ",")#
This obviously totals all of SecThreeCount values, but what if I want to exclude the last 2 rows from that list??
Is that possible in coldfusion?
(If this makes a difference)>> So there are 13 rows returning, I want the first 11 rows and exclude the last 2.
I know that I can limit the SQL return of that query to exclude those 2 rows, but I wanted less code to write and keep things neat. And also learn if it is possible:)
Thanks in advance.
Well I think if you don't need those two rows, you should not be returning them in the first place. That would be the best answer. From your statement "but I wanted less code to write" you're optimising in the wrong place: don't optimise for yourself, optimise for the solution.
Leigh's come in underneath me as I've been testing the code for this, but here's a proof of concept using subList():
numbers = queryNew("");
queryAddColumn(numbers, "id", "integer", [1,2,3,4,5,6]);
queryAddColumn(numbers, "maori", "varchar", ["tahi", "rua", "toru", "wha", "rima", "ono"]);
maori = listToArray(valueList(numbers.maori));
subset = maori.subList(2,5);
writeDump([numbers, subset]);
This returns an array with elements ["toru","wha","rima"].
If you are running CF10, one option is using ArraySlice. Grab only the first eleven elements, then apply arraySum.
<cfset allValues = ListToArray(ValueList(qrySummary.secThreeCount))>
<cfset subTotal = arraySum( arraySlice(allValues, 1, 11))>
For earlier versions, there is the undocumented subList(...) approach. It takes advantage of the fact that CF arrays are java.util.List objects under the hood, and uses List.subList(..) to grab a subset of the array.
Another approach. You don't want to add up the values for the last 2 elements, so remove them from your array:
<cfset values = ListToArray(ValueList(qrySummary.secThreeCount))>
<!--- delete the last element --->
<cfset arrayDeleteAt(values, arrayLen(values))>
<!--- delete the last element again --->
<cfset arrayDeleteAt(values, arrayLen(values))>
#numberFormat(ArraySum(values), ",")#
Alternatively, given that you're looping over the query anyway, you could simply add the totals up as you go (with a tiny bit of logic to not bother if you're on the last or penultimate row)

Execute coldfusion code stored in a string dynamically?

I have an email body stored as a string in a database, something like this:
This is an email body containing lots of different variables. Dear #name#, <br/> Please contact #representativeName# for further details.
I pull this field from the database using a stored proc, and then I want to evaluate it on the coldfusion side, so that instead of "#name#", it will insert the value of the name variable.
I've tried using evaluate, but that only seems to work if there's just a variable name. It throws an error because of the other text.
(I can't just use placeholders and a find/replace like this - Resolving variables inside a Coldfusion string, because the whole point of storing this in a database is that the variables used to build the string are dynamic. For example, in one case the name field can be called "name" and in another it could be "firstName", etc.)
I would loop over each #variableName# reference and replace it with the evaluated version.
A regex will be able to find them all and then a loop to go over them all and just evaluate them one by one.
You need to write it to a file and CFINCLUDE it. This will incur a compilation overhead, but that's unavoidable.
Can you not save the code to the file system and just store a reference to where it is in the DB? That way it'll only get recompiled when it changes, rather than every time you come to use it?
<!--- pseudo code --->
<cfquery name="q">
SELECT fileContent // [etc]
</cfquery>
<cfset fileWrite(expandPath("/path/to/file/to/write/code.cfm"), q.fileContent)>
<cfinclude template="/path/to/file/to/write/code.cfm">
<cfset fileDelete(expandPath("/path/to/file/to/write/code.cfm"))>
That's the basic idea: get the code, write the code, include the code, delete the code. Although you'll want to make sure the file that gets created doesn't collide with any other file (use a UUID as the file name, or something, as per someone else's suggestion).
You're also gonna want to load test this. I doubt it'll perform very well. Others have suggested using the virtual file system, but I am not so sure there'll be much of a gain there: it's the compilation process that takes the time, not the actual file ops. But it's worth investigating.
Are you using ColdFusion 9 or Railo? If yes, writing to and including in-memory files may be quick and simple solution. Just generate file name with something like CreateUUID() to avoid collisions.
So basically, after researching and reading the answers, it seems that these are my options:
Have separate fields in the table for each variable and evaluate them individually. e.g. nameVariable, reprNameVariable, and then I can build the body with code like this:
This is an email body containing lots of different variables. Dear #evaluate(nameVariable)#, <br/> Please contact #evaluate(reprNameVariable)# for further details.
Have an "emailBody" field with all the text and applicable field names, and write it to a temporary file and cfinclude it. (As suggested by Adam Cameron, and I think that's what Sergii's getting at)
Have an "emailBody" field and write code to loop over it, find all coldfusion variables, and replace them with their "evaluate"d version. (As suggested by Dale Fraser)
Have small template files, one for each report type, with the email body, and have a field "emailBodyTemplate" that indicates which template to include. (As suggested by Adam Cameron)
Now I just have to decide which to use :) When I do, I'll accept the answer of the person who suggested that method (unless it's one that wasn't suggested, in which case I'll probably accept this, or if someone comes up with another method that makes more sense)
It's been a while since you posted this - but it's EXACTLY what I do. I found your question while looking for something else.
I've simply created my own simple syntax for variables when I write my emails into the database:
Hello ~FirstName~ ~LastName~,
Then, in my sending cfm file, I pull the email text from the database, and save it to a variable:
<cfset EmailBody = mydatabasequery.HTMLBody>
Then I quickly strip my own syntax with my variables (from another query called RecipientList):
<cfset EmailBody = ReplaceNoCase(EmailBody, "~FirstName~", "#RecipientList.First#", "ALL")>
<cfset EmailBody = ReplaceNoCase(EmailBody, "~LastName~", "#RecipientList.Last#", "ALL")>
Then I simply send my email:
<cfmail ....>#EmailBody#</cfmail>
I hope you manage to see this. If you control the authoring of the email messages, which I suspect you do, this should work well.
Russell