some how a Criteria: associations + not + ilike does not give a good result.
I still get cases with actions that have the status I don't want in the result.
Any clue or suggestion for other approaches?
I have this in controller:
def pgp = [:]
pgp.max = params.max?.toInteger() ?: 20;
pgp.offset = params.offset?.toInteger() ?: 0
pgp.max = 20;
def result = Case.createCriteria().list(pgp) {
actions {
not {
and {
ilike("status","%CLOSED")
ilike("status","%Installed in PRD")
}
}
}
}
This is the relevant Domain snipped:
class Case {
String caseCode
String caseName
String caseType
static hasMany = [ actions : Action ]
I'm on Grails 2.4.4
Your Boolean logic is faulty - the and should be an or. Your current tests will be true for every possible value of status, as any value that passes ilike("status","%CLOSED") will fail ilike("status","%Installed in PRD") and vice versa.
Related
I'm trying to use the createCriteria in a grails application to return some rows from a DB. I'm not getting any results.
def query = {
ilike('fullName','%${params.term}%')
projections {
property('id')
property('fullName')
}
}
def plist = Patient.createCriteria().list(query)
def patientSelectList = []
plist.each {
def pMap = [:]
pMap.put("id", it[0])
pMap.put("label", it[1])
pMap.put("value", it[1])
patientSelectList.add(pMap)
}
the fields i'm looking for exist as the following snippet returns results but is very slow.
def patientList = Patient.getAll()
def patientSelectList = []
patientList.each { patient ->
if (patient.fullName.toLowerCase().contains(params.term.toLowerCase()) ) {
def pMap = [:]
pMap.put("id", patient.id)
pMap.put("label", patient.fullName)
patientSelectList.add(pMap)
}
}
return patientSelectList
thanks for anyhelp
I had my Db fields encryted with jasypt. Removing the encryption on the field I needed to query fixed the issue!
You're using a String rather than a GString in the ilike() parameter, so params.term is not being evaluated. To use a GString, use double-quotes instead.
ilike('fullName',"%${params.term}%")
Also make sure % is an appropriate wildcard character for your database (it probably is).
I would like to have a util for building queries, so that I can add specificity to a common query rather than hard coding similar queries over and over again.
For instance:
DetachedCriteria query = DeviceConfiguration.where { ... }
while(query.list(max: 2).size() > 1) QueryUtil.addConstraint(query, newConstraint)
But I'm having trouble with queries that involve many-to-many relationships.
If my domain classes are:
class StringDescriptor {
String name
String stringValue
static hasMany = [ deviceConfigurations: DeviceConfiguration ]
static belongsTo = DeviceConfiguration
}
class DeviceConfiguration {
Integer setting1
Integer setting2
static hasMany = [ stringDescriptors: StringDescriptor ]
}
And my device configurations look like this:
DeviceConfiguration hondaAccord = new DeviceConfiguration(setting1: 1, setting2: 1)
DeviceConfiguration hondaCivic = new DeviceConfiguration(setting1: 2, setting2: 2)
DeviceConfiguration accord = new DeviceConfiguration(setting1: 3, setting2: 3)
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor civicDescriptor = new StringDescriptor(name: "model", stringValue: "civic")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
hondaAccord.addToStringDescriptors(hondaDescriptor)
hondaAccord.addToStringDescriptors(accordDescriptor)
hondaCivic.addToStringDescriptors(hondaDescriptor)
hondaCivic.addToStringDescriptors(civicDescriptor)
accord.addToStringDescriptors(accordDescriptor)
hondaAccord.save(failOnError: true)
hondaCivic.save(failOnError: true)
accord.save(failOnError: true, flush: true)
I would like to be able to do this:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { stringDescriptors {name =~ "model" & stringValue =~ "civic"} }
if(query2.list(max: 2)?.size() > 1)
//...
But that doesn't work - query2 gives the same results as the first query. And yet when I do THIS, it works perfectly:
def query = DeviceCollector.where{ stringDescriptors {name =~ "make" & stringValue =~ "honda"} }
if(query.list(max: 2)?.size() > 1)
def query2 = query.where { eq('setting1', 1) }
if(query.list(max: 2)?.size() > 1)
def query3 = query.build { eq('setting2', 1) }
Please advise :(
EDIT thanks to injecteer
Now my domain includes this:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { String name, String value ->
stringDescriptors {
ilike 'name', name
ilike 'stringValue', value
}
}
}
}
And my attempt to string the queries together looks like this:
//Lists hondaAccord and hondaCivic
DeviceConfiguration.byStringDescriptor("make", "honda").list()
//Lists hondaAccord and accord
DeviceConfiguration.byStringDescriptor("model", "accord").list()
// LISTS NOTHING... BUT WHYYYYY?
DeviceConfiguration.byStringDescriptor("make", "honda").byStringDescriptor("model", "accord").list()
I am confused. Yet again.
EDIT thanks to injecteer's updated answer
Yay, here is the named query that worked for me:
class DeviceConfiguration {
//...
static namedQueries = {
byStringDescriptor { List<StringDescriptor> descriptors ->
sizeEq('stringDescriptors', descriptors.size())
stringDescriptors {
or {
for(descriptor in descriptors) {
and {
ilike 'name', descriptor.name
ilike 'stringValue', descriptor.stringValue
}
}
}
}
}
}
}
The results (YAYYY) :) ...
StringDescriptor hondaDescriptor = new StringDescriptor(name: "make", stringValue: "honda")
StringDescriptor accordDescriptor = new StringDescriptor(name: "model", stringValue: "accord")
//returns nothing - **check**
def hondaQuery = DeviceConfiguration.byStringDescriptor([hondaDescriptor]).list()
//returns accord configuration - **check**
def accordQuery = DeviceConfiguration.byStringDescriptor([accordDescriptor]).list()
//returns just the hondaAccord configuration - **YESSSSSS**
def hondaAccordQuery = DeviceConfiguration.byStringDescriptorUsingOr([hondaDescriptor, accordDescriptor]).listDistinct()
injecteer is my favorite person ever.
Use criteria query or named queries. they both allow for better chaining
class DeviceConfiguration {
static namedQueries = {
byDescriptors { List vals ->
stringDescriptors {
or{
for( def tuple in vals ){
and{
ilike 'name', "%${tuple[ 0 ]}%"
ilike 'stringValue', "%${tuple[ 1 ]}%"
}
}
}
}
}
}
}
so you can call:
DeviceConfiguration.byDescriptors( [ [ 'make', 'honda' ], [ 'model', 'accord' ] ] ).findAllBySetting1( 10 )
you should know, what conjunction is appropriate and or or
UPDATE 2
with so many of ands you won't find anything...
if you fire up a query like blah( honda, accord ).list() it would try find stringDescriptors with name='honda' AND name='accord' which is not possble, so it returns no results!
That's why I tend to think, that your domain model does NOT allow such queries at all - even at SQL-level.
Your attributes shall be clearly distinguishable, so that you can find by honda (type 'make') and accord (type 'model') it shouldn't look for "honda" in "model".
Can a single DeviceConfiguration instance contain several StringDescriptors of the same type?
I've got two domains, one stored a link to another, like so (I've cut out all the other fields to eliminate noise): -
class TestScenario {
static constraints = {
testscenariodesc(blank:false, maxSize:100, unique: true)
}
static mapping = {
table "test_scenario"
version false
columns{
id column:"test_scenario_id"
}
}
String testscenariodesc
String toString(){
"${testscenariodesc}"
}
}
This is used in: -
class TestExecQueue {
static constraints = {
testscenarioid()
myPriority(inList:[0,1,2,3,4], blank:false)
}
static mapping = {
table "test_exec_queue"
version false
columns{
id column:"test_exec_queue_id"
testscenarioid column:"test_scenario_id"
myPriority column:"Priority"
}
}
TestScenario testscenarioid
Integer myPriority
}
I've got a list in my testexecqueue like so: -
def list(Integer max) {
params.max = 10000
[testExecQueueInstanceList: TestExecQueue.list(params), testExecQueueInstanceTotal: TestExecQueue.count()]
}
But what I want to do is return the results sorted by the testscenariodesc value (not the numeric value of testscenarioID. Is this possible?
I tried this: -
[testExecQueueInstanceList: TestExecQueue.list(params, sort: "testscenarioid", order: "desc"), testExecQueueInstanceTotal: TestExecQueue.count()]
But when I then go to the page I get the following error:
No signature of method: testautomation.TestExecQueue.list() is applicable for argument types: (java.util.LinkedHashMap, org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap) values: [[sort:testscenarioid, order:desc], [action:list, ...]] Possible solutions: list(), last(), list(java.util.Map), first(), last(java.lang.String), last(java.util.Map)
What am I doing wrong here? Thanks for any suggestions! :)
The list() method accepts sort and order parameters. Take a look at the documentation for details. For example
def results = myDomain.list([max: 10, sort: 'myColumn', order: 'asc'])
Alternatively you can specify a default sort order on your domain. For example:
class MyClass {
String myColumn
static mapping = {
sort 'myColumn'
}
}
Is it possible to construct such query in run-time in grails/groovy ?
Let's say I have:
def query = Person.where {
age in 18..65
}
and in run-time I wanna add weight to it as :
def query = Person.where {
age in 18..65
weight in 100..200
}
possible ?
I would use Criteria Queries instead. They allow you to dynamically construct queries like you want very easily. For example, you could create a criteria like this:
def result = Person.createCriteria {
'in'("age", [18..65])
if (params.includeWeight) {
'in'("weight", [100..200])
}
}.list()
Person.where is a method that takes a Closure as argument. A feature that closures have is composition. Here's an example from Groovy Goodness:
def convert = { new Expando(language: it) }
def upper = { it.toUpperCase() }
// Composition.
def upperConvert = convert << upper
So you can do something like:
def defaultWhere = {
age in 18..65
}
if(someRuntimeTest) {
defaultWhere << {
weight in 100..200
}
}
Person.where(defaultWhere)
I wanted to implement a default sort order in my domain class and immediately found it didn't work with the getAll method. No biggie, I just used list instead. The thing is that the default sort order in a domain class does not allow you specify multiple sort fields (as seen here).
My goal is to sort all Foo objects first by the name of their Bar object, then by their own name.
class Foo {
String name
String Bar
}
class Bar {
String name
}
How can I implement this in the domain class so I don't have to specify a long/nasty comparator every time I call .list()?
One of my attempts:
static Comparator getComparator() {
def c = { a, b ->
def result = a.bar.name.compareTo( b.bar.name );
if ( result == 0 ) {
result = a.name.compareTo( b.name );
}
}
return c as Comparator
}
Then I could just call Foo.list(Foo.getComparator())... if I could get it to work.
Update:
I think I am really close here, just having trouble with implementing two comparisons in the same sort closure.
Foo.list().sort{ a, b ->
def result = a.bar.name <=> b.bar.name;
// Things mess up when I put this if statement in.
if( result == 0 ) {
a.name <=> b.name
}
}
Disco!
class Foo { // My domain class
// ...
static Comparator getComparator() {
def c =[
compare: { a, b ->
def result = a.bar.name <=> b.bar.name;
if( result == 0 ) {
result = a.name <=> b.name
}
return result
}
] as Comparator
}
// ...
}
And implemented like this in my controller:
Foo.list().sort( Foo.getComparator() )
PS:
The above works, but Jeff Storey posted some code in his answer after I discoed, and his code works and is much nicer than mine so use it :)
In your case, would it make sense to have Foo implement Comparable and the implementation could do the comparison as you described? Then when you sort the objects in a list, because they are Comparable, they will sort properly.
If it does not make sense for you to implement Comparable though, you will need to specify a comparator to sort by.
Here's some sample code based on your comments:
edit:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
int lastNameCompare = lastName <=> other.lastName
return lastNameCompare != 0 ? lastNameCompare : firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This prints:
unsorted = [Smith,John, Jones,Bill, Smith,Adam]
sorted = [Jones,Bill, Smith,Adam, Smith,John]
To further simplify the above post (I would have commented on it but I don't have the rep yet), you can chain the groovy compare operators using the elvis operator:
class Person implements Comparable<Person> {
String firstName
String lastName
int compareTo(Person other) {
return lastName <=> other.lastName ?: firstName <=> other.firstName
}
String toString() {
"${lastName},${firstName}"
}
}
def people = [new Person(firstName:"John",lastName:"Smith"), new Person(firstName:"Bill",lastName:"Jones"), new Person(firstName:"Adam",lastName:"Smith")]
println "unsorted = ${people}"
println "sorted = ${people.sort()}"
This will give you the same result because 0 is considered false in groovy's eyes, which will make it look at the next conditional in the chain.