How to get access to properties of related table via WHERE clause? - grails

For example I have follow domains:
class Company {
String name
}
class Employee {
String firstName
String secondName
Company company
}
And I try to get all employees where company name is 'M$oft'
Employee.where {
eq 'company.name', 'M$oft'
}.list()
Got:
could not resolve property: company.name of: myapp.Employee
Also I try this:
Employee.where {
createAlias('company', 'c')
eq 'c.name', 'M$oft'
}.list()
Got:
could not resolve property: c of: myapp.Employee
I know about:
Employee.where {
company.name == 'M$oft'
}
but it's not appropriate for me.
I just want to know: is it possible do it via where clause with usage of string names of fields?

Use next syntax:
Employee.where {
company{
eq 'name', 'M$oft'
}
}.list()
And don't forget to use ' ' with strings that have $. Maybe some unexpected result.

Related

Grails: search using createCriteria

I need to do simple search (Two my example-simple domains and controller action are below). I want to return list of users with firstName, lastName or Car.carName like searchPattern
class User {
String firstName
String lastName
static hasMany = [car : Car]
}
class Car {
User user
String carName
}
def list(String search){
...
def searchPattern = "%" + search + "%"
def domains = User.createCriteria().list(max: max, offset: offset) {
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
car {
like("carName", searchPattern)
}
}
}
It returns incorrect results - doesn't see user, which hasn't got car. Can you help me to change it for correct working? Thanks a lot
try this one:
car{
or{
isNull 'carName'
like 'carName', searchPattern
}
}
First you need to set up the domain class associations correctly. It seems you're going for a has-many association between User and Car. There are two variations: uni-directional and bi-directional. However, your implementation uses neither. Going with the assumption that you want a bi-directional association, you'll need to modify your Car class like this:
class Car {
static belongsTo = [user: User]
String carName
}
And for clarity, since a User has many Cars, it'd be worth pluralizing the collection name:
class User {
String firstName
String lastName
static hasMany = [cars : Car]
}
For more on associations, you can read my article on the subject.
Next, since you want Users even if they do not have Cars, you should know about a subtle default built into GORM: the SQL database tables are automatically INNER JOINed. It is this INNER JOIN that's causing Users without Cars to be ignored. To address this, you'll need to change the join to an OUTER JOIN. You can do something like this:
import static org.hibernate.sql.JoinType.*
def domains = User.createCriteria().list(max: max, offset: offset) {
createAlias('cars', 'c', LEFT_OUTER_JOIN)
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
like("c.carName", searchPattern)
isNull("c.carName")
}
}
If I recall, aliases are used differently, hence the c.carName. You can read a bit more about using a LEFT OUTER JOIN here.
Thanks a lot to all for your help and for usefull links. This decided my problem:
import org.hibernate.criterion.CriteriaSpecification
.....
def domains = User.createCriteria().list(max: max, offset: offset) {
createAlias('cars', 'c', CriteriaSpecification.LEFT_JOIN)
or {
like("firstName", searchPattern)
like("lastName", searchPattern)
like("c.carName", searchPattern)
}

How can I write this relatively simple criteria query involving three domains?

Just for background, let us have these three domain classes:
class Group {
Long id
Person person
}
class Person {
Long id
Country country
String name
}
class Country {
Long id
}
So, with these classes in mind, I am given a Group object's id as well as a Country object's id. I would like to get the list of Person objects based on these two.
It seems relatively simple, but I am new to criteria queries and so I am struggling to figure out what I am doing wrong. This is what I have so far:
def c = Group.createCriteria()
def names = c.list (order: "asc") {
createAlias('person', 'p')
createAlias('p.country', 'c')
and {
eq ('c.id', Long.valueOf(countryId))
eq ('id', groupId)
}
projections {
property('p.name')
}
}
Of course, this is wrong as it is throwing errors. Can someone please let me know what I am doing wrong?
Thanks for your help!
static def searchPersons(Map params) {
return Group.createCriteria().list(params, {
if(params.groupId) {
eq('id', params.groupId.toLong())
}
person {
order('name')
if(params.countryId) {
country {
eq('id', params.countryId.toLong())
}
}
}
projections {
property('person')
}
})
}
Still, it might be better to add the necessary associations (hasMany, etc.) on your domains.
The first thing you can do is improve the associations between your domain classes. This will help make criteria queries simpler (and deter monkey-patching later).
In your example, the association between Person an Group is a one-to-many; one person can have many groups. That may be your intention, but it also means that a group can have only one person. Basically, there's no way to group people together.
I'm going to assume that you want a many-to-one relationship so that many Person (people) can be in the same Group. With this in mind, the domain classes (with the explicit IDs left in) would look like this:
class Group {
Long id
}
class Person {
Long id
Country country
String name
Group group
}
class Country {
Long id
}
As for the query, since your expected result is instances of Person, the best place to start is with Person rather than Group.
List of Person instances
Here's how to get a list of Person instances.
Where query
def people = Person.where {
country.id == countryId
group.id == groupId
}.list()
Criteria query
def people = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
}
List of Person names
Notice that there's a discrepancy between your question and example. You asked for a list of Person instances, yet your example demonstrates attempting to get a list of Person names .
Here's how to get a list of names of the Person instances.
Where query
def names = Person.where {
country.id == countryId
group.id == groupId
}.projections {
property 'name'
}.list()
Criteria query
def names = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
projections {
property 'name'
}
}

Sort GORM query result by a column of a foreign key

I have a foreign key relationship that is fully mapped in Grails, let say:
class Hero {
Long id
String name
Long experience
Pet pet
.....
}
class Pet {
Long id
String name
Long ownerId
.....
}
Lets say I want to query all Hero who named their Pet something like "Hiccup". So I'll perform a query to the Hero class:
def matching = Hero.findAll {
ilike('pet.name', '%Hiccup%')
}
And it worked! The problem is how can I sort the returned List by the Pet's Name column? I've tried:
def matching = Hero.findAll {
ilike('pet.name', '%Hiccup%')
orderBy('pet.name', 'asc')
}
But it returns an error:
org.hibernate.QueryException:
could not resolve property: pet.name of: Hero
Thank you.
try this:
def matching = Hero.withCriteria {
pet {
ilike('name', '%Hiccup%')
orderBy('name', 'asc')
}
}
or the other way around:
def matching = Pet.withCriteria {
projections {
property('hero')
}
ilike('name', '%Hiccup%')
orderBy('name', 'asc')
}
But this only works when you declare the pet <--> hero relationship with grails command. I dont know if this is a one-to-one / one-to-many
It should be like this (one-to-one):
class Hero {
Long id
String name
Long experience
static hasOne = [pet:Pet]
.....
}
class Pet {
Long id
String name
Long ownerId
.....
static belongsTo = [hero:Hero]
}

findAll order by "many to one" column raises exception

I have an entity, Student, defined in Student.groovy as:
#EqualsAndHashCode(includes = ['id'])
class Student {
Long id
String name
String type
University university
static mapping = {
university column : 'UNIVERSITY_ID'
}
}
and a University entity, defined in University.groovy as:
class University {
Long id
String name
static mapping = {
id column : 'id', generator : 'assigned'
}
}
I've been trying to switch from calling
Student.list(sort: ..., order: ...)
to calling:
Student.findAll("from Student s where type = :type ", [type : 'T'], [ sort : 'name' ])
This fails to order correctly by the name field. The previous version, using list worked fine.
I've also tried calling something like
Student.findAll(sort : 'name') { type == "T" }
which worked fine like this, but when trying to sort by the university.name
Student.findAll(sort : 'university.name') { type == 'T" }
it raised an error regarding the university.name field not being found.
Anybody have any idea on how to do this properly?
Thank you.
Use executeQuery instead of findAll - they should function the same, but I've found that executeQuery is for some reason a more direct caller of the HQL, and findAll fails or returns unexpected results in some cases.
So that first query would be
Student.executeQuery(
'select s from Student s where s.type = :type order by s.name',
[type : 'T'])
and ordering by university name would be
Student.executeQuery(
'select s from Student s where s.type = :type order by s.university.name',
[type : 'T'])
I like HQL and tend to use it a lot, but it couples you to Hibernate and relational databases - if you want to switch to a NoSQL database these queries will fail. Criteria queries, "where" queries and finders all use criteria queries internally, and those are converted to native query API calls by the GORM implementation.
The equivalent criteria queries would be
Student.withCriteria {
eq 'type', 'T'
order 'name', 'asc'
}
and
Student.withCriteria {
eq 'type', 'T'
university {
order 'name', 'desc'
}
}
Some unrelated notes:
You shouldn't use id in equals or hashCode calculations; if you have a persistent Student and a new non-persistent instance with the same name, type, and University, they should be considered equal, but since the non-persistent instance's id will be null they'll be considered different.
You don't need to specify the id property - Grails adds it and the version field to the bytecode via an AST transformation during compilation.
There's no need to map the column name of the university property to 'UNIVERSITY_ID' - that's what it would be anyway.
You can omit the redundant column setting in the id mapping.
Here's the Student class with cruft removed:
#EqualsAndHashCode(includes = ['name', 'type', 'university'])
class Student {
String name
String type
University university
}
and University:
class University {
String name
static mapping = {
id generator: 'assigned'
}
}

Grails / GORM criteria query with hasmany String

I have a domain object (Cat) like this:
class Cat {
String name
static hasMany = [
nicknames: String
]
}
(A cat has a name, and also has many nicknames (which are Strings))
And I am trying to query all the cats with certain nicknames.
I've tried this:
PagedResultList getCatsByNickname(String nickname, Map params) {
PagedResultList results = Cat.createCriteria().list(params) {
'ilike'('nicknames','%'+nickname+'%')
}
return results
}
But it never returns any results. (If I change the query to just use the simple name attribute, it works finding all cats with that name, but I want to query against the nicknames.)
I also tried this:
PagedResultList getCatsByNickname(String nickname, Map params) {
PagedResultList results = Cat.createCriteria().list(params) {
'nicknames' {
'ilike'('nicknames','%'+nickname+'%')
}
}
return results
}
But I get the error: org.hibernate.MappingException: collection was not an association: example.Cat.nicknames
So the question is, how do I query against a hasMany of type String?
After a lot of trying and researching, I found this will work with Grails 2.4.0, I don't know about older versions.
Cat.withCriteria {
createAlias('nicknames', 'n')
ilike 'n.elements', '%kitty%'
}
The trick is to use 'n.elements'
You can use HQL for querying in such a scenario. For example,
Cat.findAll("from Cat c where :nicknames in elements(c.nicknames)", [nicknames:'kitty'])
You can also use HQL (tested with Grails 2.5.0):
Cat.findAll("from Cat c inner join c.nicknames as n where upper(n) like '%'||?||'%'", [nickname.toUpperCase()])

Resources