Coldfusion: Can't reference var from query result set - coldfusion

Using cfscript, trying to set the ID of the newly inserted question so that I can use it in my answer insert to build the relationship. I've done this a million times outside of cfscript. setName seems to be the proper method to call to create the query name.
I'm receiving the error that "theQuestionID" does not exist in qryQuestion
i = 1;
while ( structKeyExists( form, "question" & i ) )
{
q = new Query();
q.setDatasource("kSurvey");
q.setName("qryQuestion");
q.setSQL("
set nocount on
insert into question (question)
values('#form["question#i#"]#')
select ##IDENTITY AS theQuestionID
set NOCOUNT off
");
q.execute();
writeOutput("Question"&i&"<br>");
j = 1;
while ( structKeyExists( form, "question" & i & "_answer" & j) ) {
q = new Query();
q.setDatasource("kSurvey");
q.setSQL("
insert into answer (answer,questionid)
values('#form["question#i#_answer#j#"]#',#qryQuestion.theQuestionID#)
");
q.execute();
writeOutput("Answer"&j&"<br>");
j++;
}
i++;
}

Theres a better way to accomplish this without having to select ##identity (which in itself isn't the best way to get it from sql server, using scope_identity is the best practice way to do this in sql server. http://msdn.microsoft.com/en-us/library/ms190315.aspx
Fortunately ColdFusion makes this even easier:
<cfscript>
insertQuery = new query();
insertQuery.setDatasource("datasourcename");
insertQuery.setSql("insert into contact(firstname, lastname)
values('ryan','anklam')");
result = insertQuery.Execute();
theKey = result.getPrefix().generatedkey;
</cfscript>

Related

cfscript and queryExecute() using like instead of equals

I'm curious if this is the correct method to use the like operator when using queryExecute() in a cfscript function.
if( len(arguments?.lastName) ){
local.sqlWhere & = " AND t_lastname LIKE :lName";
local.sqlParams.lName = { value : arguments.lastName & '%', cfsqltype:'cf_sql_varchar'};
};
Is it just appended like a string with & '%'?
I've just go through your issue. In coldfusion & symbol always concatenation the two string. So we could not able to use like that. Here I've wrote some sample code for you please check that. I hope it will more help full to wrote a script based query.
local.MyQry = "SELECT * FROM Users WHERE 1=1 ";
I've used same condition from you. Not sure about your conditions
if( len(arguments?.lastName) ){
local.MyQry &= " AND Email like :email"
}
Here concatenate the query with previous one if the condition is true. And mentioned :(colon as we are going to use as queryparam)
local.qry = new Query( datasource = 'your DB name' , sql = Local.MyQry);
if( len(arguments?.lastName) ){
local.qry.addParam( name="email", value="%#Arguments.email#%", cfsqltype="cf_sql_varchar");
}
return local.qry.execute();
You can give the % symbol here based on your scenario . Ex %#Arguments.email#. or %#Arguments.email#%
I hope this will help you more. Thanks

How to get the results of a stored procedure when using cfscript new StoredProc()

First time trying to use a stored procedure via cfscript and I can't figure out how to get the results. With a regular query I do something like this to get my result set:
queryResult = queryResult.execute().getResult();
With the stored procedure my code is:
queryResult = new storedProc( procedure = 'stpQueryMyResultSet', datasource = 'mydsn' );
queryResult = queryResult.execute();
writeDump(queryResult);
That returns 3 structs - prefix, procResultSets and procOutVariables, but I can't seem to figure out how to get my query results.
Thanks to #Ageax for pointing me to that page. Here's how I got it working (I also added in a param for max rows to return):
queryResult = new storedProc( procedure = 'stpQueryMyResultSet', datasource = 'mydsn' );
queryResult.addParam( type = 'in', cfsqltype = 'cf_sql_integer', value = '10');
queryResult.addProcResult( name = 'result' );
qResult = queryResult.execute().getProcResultSets().result;
writeDump(qResult);

Using cfloop with queryfilter function

I am new to ColdFusion and trying to use cfloop for the below code:
<cfscript>
var origRate = 0;
var toRate = 0;
rates = myQuery.filter(function (obj) {
return (obj.code == arguments.origCode || obj.code ==
arguments.toCode)
})
</cfscript>
I modified below, the original code and inserted the above new code to avoid the inline sql queries:
<cfquery name="rates" dbtype="query">
select code, rate
from myQuery
where code = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.origCode#" />
or code = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.toCode#" />
</cfquery>
I tried using cfloop without changing to the previous code as below but it is not working:
<cfloop query="rates">
<cfscript>
if (code == arguments.origCode) origRate = rate;
if (code == arguments.toCode) toRate = rate;
</cfscript>
</cfloop>
Once the second block of code was inserted by commenting out the first block of code above, it did not load the page. If anyone has an idea, I really appreciate it. Thank you in advance!
There were some missing details about the application and data, so I made a couple of assumptions. It appears that you have a query object that you want to filter and pull rates from for an origCode and a toCode. Without knowing more about your data structure and what you plan to do with it, I can only make some general suggestions. I still maintain that it would be much better to filter in the query, but I understand the limitation. Since you have to filter inside your application, both the bulk of the base data you initially return and the processing to filter those records will negatively impact the performance.
First thing I did was to set up a fake query object. This is where my first assumption comes into play. I assumed that your code won't be any duplicated in your table, and that the code will have a rate associated with it.
myQuery = queryNew(
"code, rate",
"integer, integer",
[
{ "code" : 1 , "rate" : 10 } ,
{ "code" : 2 , "rate" : 15 } ,
{ "code" : 3 , "rate" : 20 } ,
{ "code" : 4 , "rate" : 25 } ,
{ "code" : 5 , "rate" : 30 }
]
);
I would not recommend a Query of Query here, because it's a lot of overhead for something that can be accomplished fairly easily.
I created a function that you can pass in your origCode and the toCode, and it will return you a structure of the origRate and the toRate. I included some comments in the code, so you will be able to see what I was doing. The bulk of the function is using the filter() closure to filter the query records down. If you are able to filter through SQL, you'll be able to eliminate this block.
function returnNewRates( required Numeric origCode, required Numeric toCode ) {
local.ratesStruct = { "origRate":-1, "toRate":-1 } ;
// This will be our query. If we _have_ to use an existing query, pass it in and duplicate() it. (Pass by Reference!)
local.qry = duplicate( myQuery ) ;
/////////////
// Closure to filter the query. This should be done in SQL.
// https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-m-r/queryfilter.html
local.filteredQuery = qry
.filter( function (obj) {
return ( obj.code == origCode || obj.code == toCode ) ;
} ) ;
// Now assign new rates. NOTE: The query shouldn't return more than 2 rows. We can validate if needed.
for ( var r IN filteredQuery ) {
if( r.code == arguments.origCode ) { ratesStruct.origRate = r.rate ; }
if( r.code == arguments.toCode ) { ratesStruct.toRate = r.rate ; }
}
return ratesStruct ;
}
To assign the origRate and toRate, we first create a ratesStruct return value to hold the structure of the rates. After we filter our query, we just loop through those filtered results and check to see if the code in the row matches with our input variables. Another one of my assumptions was that the database would return no more than two records (one origCode and one toCode, or neither). If it is possible to return more than one row for a code, then the output codes will be overwritten by the last related row in the query. If there are other rows appropriate for sorting, then they can be used and only select the top row for the needed rate. I also defaulted the returned rates to a -1 to signify that no rate was found for the code. That can be changed if needed.
After that, I just ran a few tests to make sure we didn't get any wonkiness. Code is at https://trycf.com/gist/c3b87ca7c508562fd36f3ba6c73829c7/acf2016?theme=monokai.
And again, I think this can all probably be done within the database itself. Probably by giving you access to a stored procedure that you can pass origCode and toCode to.
If you are receiving an error about an invalid construct it is because the version of CF does not support the == operator. For Adobe ColdFusion, until recently the only supported equals operators have been eq, is or various comparison functions depending on the variables and intentions involved.
<cfloop query="rates">
<cfscript>
if (code eq arguments.origCode) origRate = rate;
if (code eq arguments.toCode) toRate = rate;
</cfscript>
</cfloop>

getting result metadata from coldfusion newQuery() in cfscript

Documentation on CFscript is a bit sparse in the docs, and searching for a cfscript specific answer gets lost in CF tag answers. So here's my question:
How do I get the result metadata from a query that was performed using script? Using tags I can add result="myNamedResultVar" to my cfquery. I can then refer to the query name for data, or myNamedResultVar for some metadata. However, now I'm trying to write everything in script, so my component is script based, top-to-bottom. What I'm ultimately after is the last inserted Id from a MySQL insert. That ID exists in the result metadata.
myNamedResultVar.getPrefix().generatedkey
Here's my query code:
public any function insertUser( required string name, required string email, required string pass ) {
// insert user
var sql = '';
var tmp = '';
var q = new query();
q.setDatasource( application.dsn );
q.addParam(
name='name'
,value='#trim( arguments.name )#'
,cfsqltype='CF_SQL_VARCHAR'
);
q.addParam(
name='email'
,value='#trim( arguments.email )#'
,cfsqltype='CF_SQL_VARCHAR'
);
q.addParam(
name='pass'
,value='#hashMyString( arguments.pass )#'
,cfsqltype='CF_SQL_VARCHAR'
);
sql = 'INSERT INTO
users
(
name
,email
,pass
,joined
,lastaccess
)
VALUES
(
:name
,:email
,:pass
,CURRENT_TIMESTAMP
,CURRENT_TIMESTAMP
);
';
tmp = q.execute( sql=sql );
q.clearParams();
}
How do I specify the result data? I've tried something like this:
...
tmp = q.execute( sql=sql );
var r = tmp.getResult();
r = r.getPrefix().generatedkey;
q.clearParams();
return r;
However, on an insert the getResult() returns a NULL as best I can tell. So the r.getPrefix().generatedkey does NOT work after an insert. I get r is undefined
You are getting the result property of the query first and then from that you are trying to get the prefix property in result. But this is not the case. You can directly get the prefix property and then the generated key like this:
tmp.getPrefix().generatedkey;
For reference you can check this blog entry: Getting the Generated Key From A Query in ColdFusion (Including Script Based Queries)
after some futzing... THIS seems to work
... tmp = q.execute( sql=sql );
var r = tmp.getPrefix( q ).generatedkey;
q.clearParams();
return r;

How can I get EclipseLink to output valid Informix SQL for an UPDATE WHERE clause?

We have a named query like this:
UPDATE Foo f SET f.x = 0 WHERE f.x = :invoiceId
Foo in this case is an entity with a superclass, using the table-per-class inheritance strategy.
The SQL that EclipseLink generates is:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
(The ? slots are correctly filled in.)
On Informix 11.70, we get an error that the subquery cannot access the table being changed.
Here is the documentation that I was able to find on subquery restrictions on Informix: http://publib.boulder.ibm.com/infocenter/idshelp/v115/index.jsp?topic=%2Fcom.ibm.sqls.doc%2Fids_sqs_2005.htm
Other databases also feature restrictions on subqueries like this, so although this is manifesting as an Informix issue, I'm sure that if we ran this against, say, MySQL, we would get a similar error.
How can I get EclipseLink to honor these restrictions? Is there a better query I should be using?
Instead of:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
do this:
UPDATE
foo_subclass SET x = ?
WHERE
foo_subclass.x = ? AND
EXISTS(SELECT t0.id
FROM foo_superclass t0
WHERE ((foo_subclass.id = t0.id) AND (t0.DTYPE = ?))
Note that on 11.50 you cannot use alias for foo_subclass. It's allowed in 11.70.
I'm assuming "id" are primary keys (or at least unique identifiers).
Found the answer. Looks like EclipseLink had to handle this case for MySQL, which also has similar issues.
The answer is that in your InformixPlatform subclass, you need to override the following methods to solve this problem:
supportsLocalTemporaryTables(): this needs to return true
shouldAlwaysUseTempStorageForModifyAll(): this needs to return true
dontBindUpdateAllQueryUsingTempTables needs to return true
getCreateTempTableSqlPrefix(): this needs to return CREATE TEMP TABLE
getCreateTempTableSqlSuffix(): this needs to return WITH NO LOG
isInformixOuterJoin(): needs to return false
getTempTableForTable(DatabaseTable): this needs to do this:
return new DatabaseTable("TL_" + table.getName(), "" /* no table qualifier */, table.shouldUseDelimiters(), this.getStartDelimiter(), this.getEndDelimiter());
In addition, you need to override the following methods as well for proper InformixPlatform behavior:
appendBoolean(Boolean, Writer): the stock Informix platform does not write out boolean literals properly. Yours needs to do this:
if (Boolean.TRUE.equals(booleanValue)) {
writer.write("'t'");
} else {
writer.write("'f'");
}
You need to override writeUpdateOriginalFromTempTableSql so that it contains the same code as the H2Platform's override does:
#Override
public void writeUpdateOriginalFromTempTableSql(final Writer writer, final DatabaseTable table, final Collection pkFields, final Collection assignedFields) throws IOException {
writer.write("UPDATE ");
final String tableName = table.getQualifiedNameDelimited(this);
writer.write(tableName);
writer.write(" SET ");
final int size = assignedFields.size();
if (size > 1) {
writer.write("(");
}
writeFieldsList(writer, assignedFields, this);
if (size > 1) {
writer.write(")");
}
writer.write(" = (SELECT ");
writeFieldsList(writer, assignedFields, this);
writer.write(" FROM ");
final String tempTableName = this.getTempTableForTable(table).getQualifiedNameDelimited(this);
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(") WHERE EXISTS(SELECT ");
writer.write(((DatabaseField)pkFields.iterator().next()).getNameDelimited(this));
writer.write(" FROM ");
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(")");
}
Lastly, your constructor needs to call this.setShouldBindLiterals(false).
With these changes, it seems that Informix is happy.