Grails : from HQL to DetachedCriteria Query - grails

Let say I have the following domain model :
class Book {
String title
Set authors
static hasMany = {authors: Author}
}
class Author {
String name
}
The HQL query to retrieve a collection of Author given a query on a title Book:
Author.executeQuery("select distinct author from Book as book join book.authors as author where book.name like ?", ["%groovy%"])
But I would to be able to have the same result a with DetachedCriteria or alike (but is it possible ... ?) and without adding a relationship from Author to Book (otherwise it would pretty obvious)
thanks

This can be done a few ways with criteria or detached Criteria, but a bit easier with regular GORM criteria since it implements the createAlias command that detachedCriteria doesn't as of Grails 2.2.2:
Create Alias In Detached Criteria
Here are both ways:
package bookauthor
import grails.gorm.DetachedCriteria
import grails.orm.HibernateCriteriaBuilder
class MyController {
def index() {
HibernateCriteriaBuilder ac2 = Author.createCriteria()
HibernateCriteriaBuilder criteria2 = Author.createCriteria()
HibernateCriteriaBuilder criteria = Book.createCriteria()
def bookResults = criteria {
projections {
property 'aut.id'
}
createAlias('authors', 'aut')
like('title', '%Groovy%')
}
def dc = new DetachedCriteria(Book).build {
authors {}
like('title', '%Groovy%')
}
def myList = dc.list().collect { it.authors.collect { author -> author.id} }.flatten()
def resultsDetached = criteria2 {
'in'('id', myList )
}
def results = ac2 {
'in'('id', bookResults )
}
log.info("RESULTS: " + results)
log.info("Detached RESULTS: " + resultsDetached)
}
}
You'll see in the logs:
bookauthor.MyController - RESULTS: [bookauthor.Author : 1, bookauthor.Author : 3]
bookauthor.MyController - Detached RESULTS: [bookauthor.Author : 1, bookauthor.Author : 3]

Unfortunately, AFAIK, this is not possible with this query. It's possible with the following ugly query, though:
select author from Author author
where author.id in (select author2.id from Book book
join book.authors as author2
where book.name like :bookName)
For such a simple, non-dynamically composed query, I would stick with your HQL query. If you really need to use Criteria, then this is the corresponding code:
Criteria c = session.createCriteria(Author.class, "author");
DetachedCriteria dc = DetachedCriteria.forClass(Book.class, "book");
dc.createAlias("book.authors", "author2");
dc.add(Restrictions.like("book.name", bookName));
dc.setProjection(Projections.property("author.id"));
c.add(Subqueries.propertyIn("author.id", dc);
List<Author> authors = (List<Author>) c.list();

Related

GORM where query on an embedded object

I have domain classes A and B as follows:
class A {
String prop1
String prop2
B prop3
static embedded = ['prop3']
}
class B {
String prop4
String prop5
}
When I want to query like this:
def q = A.where { prop3.prop4 == 'bla' }
def list = q.list()
I get the following exception:
Cannot get property 'javaClass' on null object. Stacktrace follows:
on the "def q = A.where ..." line.
Any clue what's the problem? I've checked this:
http://grails.1312388.n4.nabble.com/GORM-embedded-object-issue-td1379137.html
but how to "just call them directly" is not quite clear to me. Any other way of querying the embedded objects in GORM?
I finally gave up on the where query and went with the DetachedCriteria approach. Gives me the same flexibility as the where queries, but works with embedded domain objects:
def criteria = new DetachedCriteria(A).build {
eq 'prop1', 'bla2'
}
criteria = criteria.build {
eq 'prop3.prop4', 'bla'
}
def list = criteria.list()
What do you get if you do (assuming B is in src/groovy)
def q = A.where { prop3 == new B(prop4: 'bla') }
def list = q.list()
Embedded components are persisted inside the main domain class (owner) itself. It can be accessed directly using any dynamic finder as you do directly on a domain object.
The above can also be represented in dynamic finders as:
A.findAllByProp3(new B(prop4: 'bla'))

How to use "Grouped BY" with Grails criteria among three tables

If I have three domain classes like this:
class Candidate {
static belongsto=[application:Application]
}
class Vote {
String name;
static belongsto=[application:Application]
}
class Application {
Date datedemand;
Candidate candidates;
Vote votes
static hasMany = [candidates:Candidate,votes:Vote]
}
I want to retrieve all candidates grouped by Vote's Name .
I started ​​the following attempt, and I remain blocked :
def criteria = Application.createCriteria()
def results = criteria.list {
votes {
}
candidates{
}
}
In decent grails (2.x+) you could try something similar:
Application.withCriteria {
createAlias("votes", votes)
projections {
groupProperty("votes.name")
}
}
In this scenario I would go for a simple vanilla HQL instead of Criteria, something like this or more (use join the way you need):
String hql = "Select V.name, C.name " +
"from Candidate C, Vote V " +
"where C.application = V.application " +
"group by V.name, C.name"
Query query = session.createQuery(hql)
List results = query.list()

Grails GORM Querying the many side of an association using HQL

I have a one to many relationship between a Course and Categories
class Course {
String code
static hasMany = [categories:CourseCategory]
}
Class CourseCategory {
String name
}
I need to query courses based on a list of categories. I have tried the query
courseInstanceList = Course.findAll("from Course c inner join c.categories cts where cts.id in :categoryIds",[categoryIds:categoryIds])
But this query returns both Courses and CourseCategories - just wondering how to create a query to just return courses?
You can use the createCriteria method:
def c = Course.createCriteria()
println (c.listDistinct {
categories {
'in' 'id', [1L, 2L, 3L]
}
})
Almost three years past ... )
Anyway here's the answer:
courseInstanceList = Course.findAll("SELECT distinct c from Course c inner join c.categories cts where cts.id in :categoryIds",[categoryIds:categoryIds])
to get just categories:
courseInstanceList = Course.findAll("SELECT cts from Course c inner join c.categories cts where cts.id in :categoryIds",[categoryIds:categoryIds])

Using groupProperty and countDistinct in Grails Criteria

I'm using Grails 1.2.4. I would like to know on how can I sort by "countDistinct" (descending) and with groupProperty inside a projections.
Here are my domains:
class Transaction {
static belongsTo = [ customer : Customer, product : Product ]
Date transactionDate = new Date()
static constraints = {
transactionDate(blank:false)
}
}
class Product {
String productCode
static constraints = {
productCode(blank:false)
}
}
In MySQL terms, this is what I want:
select
product_id,
count(product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
In general term, I would like to get a list of products (or just product id) sorted by the number of transactions a product had (descending)
This is my guess:
def c = Transaction.createCriteria() def transactions = c.list {
projections {
groupProperty("product")
countDistinct("product")
}
maxResults(pageBlock)
firstResult(pageIndex) }
def products = transactions.collect { it[0] }
But it doesn't give my expected result. Any lead on this will be highly appreciated. Thanks!
Try this:
def c = Transaction.createCriteria()
def transactions = c.list {
projections {
groupProperty("product")
countDistinct("id")
}
maxResults(pageBlock)
firstResult(pageIndex)
}
Your criteria query is actually equivalent to:
select
product_id,
count(**distinct** product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
Note the distinct. You want to count the number of distinct transactions per product, so counting the transaction id (or any transaction property) makes more sense. Product id just happens to work without the distinct clause.
You can turn on the super useful hibernate SQL logging for debugging this kind of issue. It will show you exactly how your criterias are being transformed into SQL. At runtime:
org.apache.log4j.Logger.getLogger("org.hibernate").setLevel(org.apache.log4j.Level.DEBUG)
or throw this in your Config.groovy:
environments {
development {
log4j = {
debug 'org.hibernate'
}
}
}
EDIT: use countDistinct("id", "transactionCount") as the projection and order("transactionCount") will sort by the count.

Grails GORM Domain class relationship

Grails 1.1.1
Goovy 1.5.7
In a relationship such this:
Author 1 -- n Book n -- 1 Publisher
Defined in Grails:
class Author {
String firstName
String lastName
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}
class Book {
String title
Author author
Publisher publisher
static constraints = {
author(nullable: true)
publisher(nullable: true)
}
}
class Publisher {
String name
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}
I want to load a Book with the values of Publisher and Author.
When i get a Book with the query:
def book2 = Book.findAllByAuthor(author)
I get the response with the autor assosiated but the publisher only have the id and name class in the other query:
def book3 = Book.findAllByPublisher(publisher)
I retrieve me the inverse result,i have the book with the publisher data but the author only have the id and the class name.
Where is the error in the defined model ? o there is an error in the way to do the queries ?
Edit:
I need the way to retrieve the values only with the query like this:
def book2 = Book.findAllByAuthor(author, [fetch:[publisher:'eager']])
In this one I can manage the value of publisher.
Question: If publisher had a hasmany or Domain related, getting the book I'm able to read the attributes?
Thanks.
Thanks.
Lazy fetching is used by default with gorm associations. If you want to enable eager fetching, you can modify the ORM DSL by adding the following mappings block to your Author domain class:
static mapping = {
books lazy:false
}
or you could change the fetch mode in the domain object by adding following code after your books relationship is defined.
static fetchMode = [books:"eager"]
Doing the same to your Publisher domain object should allow you to accomplish what you want. You do want to be careful of the consequence that you may load more data than you intend to.
Shouldn't the get() method return what you are looking for?
Example: def book2 = Book.get(author)
You'd better use Criteria and explicitly define which relations should be loaded eagerly. Just mention relation in the query.
Example:
def c = Teacher.createCriteria()
List<Teacher> results = c.list {
subjects {
attendees {}
}
}

Resources