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)
}
Related
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])
I have a many-to-many relationship:
class Project {
Set<PrincipalInvestigator> pis
:
static hasMany = [ pis:PrincipalInvestigator ]
}
class PrincipalInvestigator {
String name
:
}
I want a query that returns a unique and sorted list of PIs that belong to a pre-defined list of projects.
A naive approach is to iterate thru the projects, and iterate thru their list of PIs, while removing the dupes. The code to do this is trivial, but it is slow.
So far, the best working solution I could come up with is:
def pi_ids = Project.createCriteria().list{ // find unique list of PI IDs
// project filters here, not relevant to the question
createAlias('pis', 'aka_pis', JoinType.LEFT_OUTER_JOIN)
isNotNull('aka_pis.id')
projections {
distinct('aka_pis.id')
}
}
def pi_list = PrincipalInvestigator.createCriteria().list{ // get PIs from list of IDs
inList('id', pi_ids)
order('name', 'asc')
}
My solution is one order of magnitude faster, but it's still 2 distinct queries. Is there a way to get the same result in a single query?
Using executeQuery makes queries as this alot easier. Something along the following should work:
PrincipalInvestigator.executeQuery("select distinct p.pis from Project p where p.id in :projectIds",
[projectIds: [1,2,3]])
The solution to my problem is this HQL:
PrincipalInvestigator.executeQuery(
"select distinct pi from Project p inner join p.pis as pi where p.id in :projectIds order by pi.name",
[projectIds:[1,2,3]])
This solution allows for sorting of the distinct results and the inner join trims all the null instances. Thanks to cfrick for putting me on the right track.
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')
}
}
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])
I am not sure if I am going about this the best way, but I will try to explain what I am trying to do.
I have the following domain classes
class User {
static hasMany = [goals: Goal]
}
So each User has a list of Goal objects. I want to be able to take an instance of User and return 5 Users with the highest number of matching Goal objects (with the instance) in their goals list.
Can someone kindly explain how I might go about doing this?
The easiest and most efficient way to achieve this is using plain SQL. Assuming you have these tables
users [id]
goals [id, description]
user_goals [user_id, goal_id]
You can have the following query to do what you need:
set #userId=123;
select user_id, count(*) as matched from user_goals
where user_id!=#userId
and goal_id in (select ug.goal_id from user_goals ug where ug.user_id=#userId)
group by user_id order by matched desc limit 5;
This takes a user id and returns a list of other users with matching goals, sorted by the number of matches. Wrap it up in a GoalService and you're done!
class GoalService {
def findUsersWithSimilarGoals(user) {
// ...
}
}
It may also be possible to do this with criteria or HQL, but with queries like this it's usually easier to use SQL.
If you're looking for a simple match, perhaps the easiest way would be to do a findAll for each Goal and then count the number of results that each other User appears in:
Map user2Count = [:]
for (goal in myUser.goals){
for (u in User.findAllByGoal(goal)){
def count = user2Count.containsKey(u) ? user2Count.get(u) : 0
count++
user2Count.put(u, count)
}
}
// get the top 5 users
def topUsers = user2Count.entrySet().sort({ it.value }).reverse()[0..5]
This may be too slow, depending on your needs, but it is simple. If many users share the same goals then you could cache the results of findAllByGoal.