GORM: Find by collection contents - grails

I Have the following two classes:
class Person{
String name
static hasMany = [
items: Item
]
}
class Item{
String name
}
Multiple Persons can also have the same Item. I'm trying to get a list of all Persons that have a specific item in their collection. I.E. Both Person A and B have Item A in their list so return them both. Unfortunately their is no findAllByCollectionContains() the closest is is findAllByCollection() which requires an exact set.
I've been trying executeQuery to give me a but more control and haven't come up with anything yet.
Example of what I have tried:
Person.executeQuery("select name from Person p where ? in p.items",[item])
Any suggestions?

You have to join the items collection, then you can query on it easily
Person.executeQuery("select name from Person p join p.items as i where i = ?",[item])

Related

How do I check if a given list is a subset of a field value in GORM?

I'm working on some sort of advanced search feature with variable fields. Some of the search fields are lists of some primitive(ish) objects (String, enums, etc.). I want to be able to retrieve records whose values are a subset of some given list.
To illustrate, say I have a Book class (assume the model is appropriate this way):
class Book {
...
List authors = []
...
}
In addition say we have the following book records:
Book(title: 'Great Expectations of Tom Sawyer', authors: ['cdickens', 'mtwain'])
Book(title: 'Huckleberry Potter in Bleak House', authors: ['cdickens', 'mtwain', 'jrowling'])
Book(title: 'A Christmas Carol', authors: ['cdickens'])
Then, I'm given a list of author (names) authorFilter = ['cdickens', 'mtwain'] to search for any collaborative works of cdickens and mtwain. How do I express this using GORM's where construct? Is it even possible to cover this using it?
Basically, what I want to do is:
Book.where {
authorFilter.every { it in authors }
}
This question has come up before. Unfortunately, where nor criteria queries have an every() equivalent. But there's a hack that may work for you. But first, I'll expand on your domain model.
Domain model
class Book {
String title
static hasMany = [authors: Author]
static belongsTo = Author
}
class Author {
String name
static hasMany = [books: Book]
}
HQL query
Using the domain model described above, you can use the following HQL query.
def hql = """
SELECT b FROM Book AS b
INNER JOIN b.authors AS a
WHERE a.name in :authors
GROUP BY b
HAVING COUNT(b) = :count
"""
def books = Book.executeQuery(hql, [authors: authorFilter, count: authorFilter.size()])
How it works.
You can read about how this query works in the other question I mentioned.
I don't think this is any better than #EmmanuelRosa's answer but I have another approach using HQL and the executeQuery method.
Using the same domain model he's given in his answer, I use the MEMBER OF expression to restrict the results.
def authorFilter = [Author.get(1)]
def authorMemberOfRestriction = ""
def namedParameters = [:]
authorFilter.eachWithIndex{ aut, ind ->
authorMemberOfRestriction += ":author${ind} MEMBER OF b.authors AND "
namedParameters.put("author" + ind, aut)
}
namedParameters.put('count', authorFilter.size())
def hql = """
FROM Book b
WHERE
(
${authorMemberOfRestriction}
size(b.authors) = :count
)
"""
def books = Book.executeQuery(hql, namedParameters)
Mine is somewhat different in that the authorFilter is a collection of Author domain class instances; I found it to work much easier for the MEMBER OF expression and truthfully is more of a depiction of how the real data would be modeled.
You can see that I build the multiple MEMBER OF expressions with the eachWithIndex, using the index on both sides of the named parameters. It's not exactly pretty but I don't believe there is a way around this and still use this approach.
I think #EmmanuelRosa's approach is probably the 'cleaner' option but the MEMBER OF approach makes more sense in my head as far as the logic is concerned.
There doesn't seem to be a simpler way of doing this other than doing HQL query. Taking hints from this answer to a very similar question, I figured out a solution to my problem.
To be clear, the Book should already have relation to many String using the hasMany construct:
class Book {
...
static hasMany = [authors: String]
...
}
To fetch results:
def results = Product.executeQuery("select p from Products p join p.tags t where t in :tags", [tags: givenTags])

Grails : how to best construct a hibernate criteria builder to search 'hasMany' relationships with domain instance

I am working on a grails project and would like to leverage hibernate criteria builders to search for instances of a domain object. I would like to find instances where one of the 'hasMany' relationships contains domain object with certain ids. Here is an example of what I mean.
Domain Objects
class Product {
static hasMany = [ productOptions: ProductOption ]
}
class ProductOption{
Option option
static belongsTo = [ product: Product ]
}
class Option{
String name
}
This is a simplified example of my domain structure and doesn't include all relationships.
An Option could be size, color, brand, etc.
Example of what I would like to achieve
Lets say I have 3 products.
Product 1 is red, small and by brandx
Product 2 is blue, small and by brandx
Product 3 is yellow, medium and by brandz
I have a few scenarios that I need to cover.
Scenario 1
Find products that are blue, small and by brandx. So in this case I should only return Product 2.
Scenario 2
Find products that are either red or blue and size small. So both Product 1 and Product 2 should be returned.
Scenario 3
Find products that are either by brandx or brandz. So all products should be returned.
I hope this covers all scenarios.
This is an example of a current attempt.
def c = Product.createCriteria()
def products = c.list{
and {
productOptions {
'option' {
idEq(1)//1 is the id of the blue option
}
}
productOptions {
'option' {
idEq(5)//5 is the id of the small size option
}
}
productOptions {
'option' {
idEq(10)//10 is the id of the brandx brand option
}
}
}
}
The and portion of this example doesn't include all options and fails. How do I best achieve this? Can I use Grails hibernate criteria builder to achieve this? Please let me know if additional information will help.
Thanks in advance for any guidance provided.
What you're looking for is the equivalent of Groovy's Object.every(Closure).
assert [1, 2, 3].every { it < 4 } == true
assert [1, 2, 3].every { it < 3 } == false
The every() method returns a Boolean indicating whether the Closure evaluates to true for every item in the collection.
Unfortunately, none of the query methods (where, criteria, and HQL) provide an equivalent of every(). But... you can cheat using HQL.
Note: Where nor Criteria queries will do because they don't support the equivalent of the HQL HAVING clause.
Scenario #1 - The Hack
def ids = [4, 5, 6] // List of Option ids.
Product.executeQuery '''
select prd from Product as prd
join prd.productOptions as prdopts
join prdopts.option as opt
where opt.id in :ids
group by prd
having count(prd) = :count''', [ids: ids.collect { it.toLong() }, count: ids.size().toLong()]
How it works
The query begins by selecting all of the Products which have any of the Options in the ids list. As long as a Product has at least one of the options it will be returned.
This produces the side-effect of listing a Product for every matching option it has. For instance, if a Product has three of the Options, then the Product is returned three times. The GROUP BY clause makes the query filter out those duplicate listings.
However, those duplicates are key to this hack: if the list of IDs is a unique list, and Products do not have the same Option more than once, then the Product has all of the required Options if the number of duplicates is equal to the number of IDs. And that's what the HAVING clause does by counting the number of Products.
Scenario 2 & 3
Scenarios 2 & 3 can be handled by the same query. I'm going to forgo consistency and chose a Criteria query because it serves this purpose best.
// Example params for scenario 2
def qparams = [
or: [1, 2], // These are color Option IDs
and: 5 // This is a size Option ID
]
// Example params for scenario 3
def qparams = [
or: [10, 11] // These are brand Option IDs
]
Product.withCriteria {
productOptions {
option {
if(qparams.and) eq('id', qparams.and.toLong())
inList('id', qparams.or.collect({ it.toLong() }))
}
}
}
The or parameter is always expected, but the if block only adds the and constraint if the and parameter is specified. Notice that the IDs are all just Option IDs, so you have some flexibility. For instance, you can search for any colors without a size constraint.
About the IDs...
You'll notice that in my examples I converted the IDS from Integers to Longs. If you IDs are coming from the database, then they're already Longs so you can take that code out.

Grails createCrieteria on super class with conditions on subclasses

I have been trying for this. But don't know it is possible or not. I need to list records by writing a crieria query on super class with conditions on subclass.
Say, Cats, Dogs extends Animal class. I need to list all animals but dogs with black dots and cats with white color. Here the key problem is, dog's properties are not in cat domain class.
But i have to write createCriteria on Animal class so that i can paginate with all animals or am i missing something?.
given that you use tablePerHierarchy = true you should be able to write a native SQL query to select the records from the table by where-clauses pointing to partially null-ed columns:
Anumal.withCriteria{
or{
sqlRestriction "dots is not null and dots != 'black'" // dogs
sqlRestriction "color = 'white'" // cats
}
}

Grails returning one element from each object

I am trying to just return a single string from each object.
Given the following:
class Book {
String title
Date releaseDate
String author
Boolean paperback
}
for every instance of Book I want to get an array of authors then make them unique.
I thought you could do something like:
def authors = Book.findAllByAuthor()
This just gives me an array off book objects.
I know i can do a
a =[]
authors.each{a.add(it.author)}
a.unique()
I am almost certain there is a way just to grab all authors in one line.
any ideas?
This gives you distinct authors of any book:
Book.executeQuery("select distinct author from Book")
You can use projections to get a distinct list of authors across all books. Take a look at the createCriteria documentation for more examples.
def c = Book.createCriteria()
def authors = c.list() {
projections {
distinct('author')
}
}

Grails projections group by and count

Following are my two classes
class Users {
String emailAddress
String password
// String filename
String firstName
String lastName
Date dateCreated
Date lastUpdated
}
and
class SharedDocuments {
Users author
Users receiver
Documents file
static constraints = {
}
}
I want to run a query similar to this one, essentially i want to get list of all the users along with count of documents they have authored
SELECT author_id, COUNT(SharedDocuments.id )FROM SharedDocuments
INNER JOIN users ON author_id = users.id
GROUP BY author_id
This is what I have so far
def sharedDocumentsInstanceList = SharedDocuments.createCriteria().list(params){
createAlias("author","a")
eq("receiver.id",session.uid)
projections{
groupProperty "author"
count "id",'mycount'
}
order('mycount','desc')
maxResults(params.max)
}
I have this 90% working, if i get rid of count or countDistinct I get list of distinct authors but what i want is authors along with the counts of documents. So when i add the count or countDistinct clause to this criteria I just get array of long!!
like [2,3,4] what I want is [[author1,2],[author2,3] ...]
How can i achive this I have already seen Grails: Projection on many tables?,
Grails criteria projections - get rows count,
Grails groupProperty and order. How it works?,
but none of the answers seem to be solving my issue!
Projections with pagination has a bug where it only return the last field in the projections block. The current grails 2.1.5 has this bug fixed. Here is the reported bug http://jira.grails.org/browse/GRAILS-9644
Couldn't you just use countBy* - like
SharedDocuments.countByAuthorAndReceiver(user, receiver)
If you want the result as as you described in your query then you need the id of the author or any other specific property like email.
def sharedDocumentsInstanceList = SharedDocuments.createCriteria().list(params){
eq("receiver.id",session.uid)
projections{
groupProperty "author"
count "id",'mycount'
}
order('mycount','desc')
maxResults(params.max)
}

Resources