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

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'
}
}

Related

Grails distinct projection get the result count of distinct items

I am using grails-2.5.6 version. I am using spring-security-core plugin. I have a criteria query on UserRole table. Where I want to find all distinct users by a role. It is working properly.
But the problem is the pagination effect. When I am counting on the list it is counting on UserRole list object. But I need the count on distinct projection items. Here is my attempt below:
def list(Integer max) {
def userInstanceList = UserRole.createCriteria().list(params) {
createAlias('user', 'au')
createAlias('role', 'ar')
projections {
distinct("user")
}
if (params.roleId) {
eq('ar.id', params.getLong("roleId"))
}
}
def totalCount = userInstanceList.totalCount
[userInstanceList: userInstanceList, totalCount: totalCount]
}
Here, totalCount is the number of UserRole list. But I want the distinct projection count.
I would tackle this slightly differently, you want to analyse the users, not the userroles.
So I'd do something like:
List<User> usersWithRole = UserRole.createCriteria().list(params) {
role {
eq('id', params.getLong("roleId"))
}
}*.user
int count = usersWithRole.size()
Unless of course there's hundreds or thousands of users, in which case I wouldn't want to load all of them each time and would revert to SQL.
Is this a custom version of spring security you're using? I've never seen Roles with a 'long' based ID, usually, the key is a String representing the Authority name.
Usually the DBAs see the use of distinct keyword as a code-smell.
In your case I would rather use the User as the main domain object to run the query against and a group by clause:
long id = params.getLong "roleId"
def list = User.createCriteria().list( params ) {
projections{
groupProperty 'userRole.role.id'
}
if( id )
userRole{
role{
eq 'id', id
}
}
}

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)
}

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]
}

Grails 1:m get most relations

I'm relatively new to Grails.
I have the following
class House {
Integer number
Integer maxResidents
static belongsTo = [town: Town]
}
class Town {
String name
static hasMany = [houses: House]
}
I want to get five towns with most Houses. I have seen the possibility to create a criteria but I can't deal with it now. Can someone support?
Thank you!
As you have a bidirectional association you can do this with a query on House:
def result = House.withCriteria {
projections {
groupProperty("town", "town")
rowCount("numHouses")
}
order("numHouses", "desc")
maxResults(5)
}
This would return you a list of results where each result res has the town as res[0] and the number of houses as res[1]. If you'd prefer each result to be a map giving access to res.town and res.numHouses then you should add
resultTransformer(AliasToEntityMapResultTransformer.INSTANCE)
after the maxResults line (along with the appropriate import at the top of your file).

Criteria search

I ran into some problems while trying to count items.
Imagine the following domain classes
class Book {
String name
}
class Author {
String name
static hasMany = [books:Book]
}
How do I get a list of Authors sorted by number of Books?
here's my try:
def c = Author.createCriteris()
c.list {
projections {
count 'books', 'numBooks'
groupProperty 'id'
}
order 'numBooks', 'desc'
}
but somehow I get only unusable results... and I don't know how to join the Author objects to the rsult list.... :-(
Havent tried it, but couldn't you do something like:
class Author {
String name
static hasMany = [books:Book]
static namedQueries = {
sortByMostBooks {
books {
order('size', 'desc')
}
}
}
}
And then get access by the cleaner named query
Author.sortByMostBooks.list();
In addition, you may want to include a belongsTo in you Book domain class:
static belongsTo = Author;
or:
static belongsTo = [authors:Author];
if a book is likely to have multiple authors
got something!
I still don't know how to do it with a criteria, but by switching to HQL, I succeeded.
So if someone comes up with a criteria solution, he will still get the bonus for the correct answer :-)
here is my query:
Author.executeQuery("""
select
a, size(a.books) as numBooks
from
Author a
group by
id
order by
numBooks DESC
""",[max:20])
This query isn't efficient, since it fetches all Authors in a loop, but that's ok for now.

Resources