having the domain classes:
class A {
Date dateCreated
static hasMany = [b:B]
...
}
class B {
String name
String value
...
}
What createCriteria or HQL query can I use to return a list with:
A's creation dateB's value for A with the name entry set to 'X'
Note: Although there's no explicit constraint, there's only one "value" for each 'X' and a combination.
Thanks
The HQL would be
def results = A.executeQuery(
'select a.id, a.dateCreated, b from A a inner join a.b b ' +
'where b.name=:name',
[name: 'X'])
This will give you a List of 3-element Object[] arrays containing A.id, A.dateCreated, and the list of B instances. I added the id to the query so you can group by it client-side:
def grouped = results.groupBy { it[0] }
This will be a Map where the keys are the A ids and the values are the Lists from the original results.
Ideally you'd do the grouping at the database, but it would complicate the query, and assuming you don't have a large number of results it should be fast.
Related
Lets Say a domain class A has many Class B objects. I need to do a criteria query which returns
A.id
A.name
B.count(no of B elements associated with A)
B.last Updated(date of most recent update of B elements associated with A considering i have last_updated date for all B elements)
Also the query should be flexible enough to add conditions/restrictions to both A and B domain objects.
Currently I have gotten as far as this:
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
property('publicId')
}
}
But the problem is that it only returns one object and the count of child objects is for all the elements of B instead of just those associated with A
Recently I was in a similar scenario I needed a query in which one of your rows will store the count of many in a one-to-many relationship
But unlike your scenario I used native sql queries to resolve the query.
The solution was to use derived tables (I do not know how to implement them using criteria query).
In case you find it useful I share a code with the implementation taken from a grails service:
List<Map> resumeInMonth(final String monthName) {
final session = sessionFactory.currentSession
final String query = """
SELECT
t.id AS id,
e.full_name AS fullName,
t.subject AS issue,
CASE t.status
WHEN 'open' THEN 'open'
WHEN 'pending' THEN 'In progress'
WHEN 'closed' THEN 'closed'
END AS status,
CASE t.scheduled
WHEN TRUE THEN 'scheduled'
WHEN FALSE THEN 'non-scheduled'
END AS scheduled,
ifnull(d.name, '') AS device,
DATE(t.date_created) AS dateCreated,
DATE(t.last_updated) AS lastUpdated,
IFNULL(total_tasks, 0) AS tasks
FROM
tickets t
INNER JOIN
employees e ON t.employee_id = e.id
LEFT JOIN
devices d ON d.id = t.device_id
LEFT JOIN
(SELECT
ticket_id, COUNT(1) AS total_tasks
FROM
tasks
GROUP BY ticket_id) ta ON t.id = ta.ticket_id
WHERE
MONTHNAME(t.date_created) = :monthName
ORDER BY dateCreated DESC"""
final sqlQuery = session.createSQLQuery(query)
final results = sqlQuery.with {
resultTransformer = AliasToEntityMapResultTransformer.INSTANCE
setString('monthName', monthName)
list()
}
results
}
The part of interest is to declare a row within the main select and then in the clause from declare the derived query that stores the result in a row with the same name declared in the main select
SELECT ...
total_tasks --Add the count column to your select
FROM ticket t
JOIN (SELECT ticked_id, COUNT(1) as total_tasks
FROM tasks
GROUP BY ticked_id) ta ON t.id = ta.ticked_id
...rest of query
This last example I share from the answer made by the user Aaron Dietz to the question that I also formulate
I hope it is useful for you
Turns out I wasn't very far from the solution and i just needed to do grouping based on the right property which is the foreign key column in the child table which is b.a in this case so the following works now
A.createCriteria().list {
createAlias('b','b')
projections{
property('id')
property('gender')
property('dateOfBirth')
count('b.id')
groupProperty('b.a')
property('publicId')
}
}
In the criteria you need to group by the property which are not aggregate.
Try following:
A.createCriteria().list {
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
or If you want to have a list of map object return you can try add resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
A.createCriteria().list {
resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
createAlias('b','b')
projections{
groupProperty('id','id')
groupProperty('gender','gender')
groupProperty('dateOfBirth','dateOfBirth')
count('b.id','total')
groupProperty('publicId','publicId')
}
}
Hope it can help
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'
}
}
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])
I have two classes in Grails application ,Employee has hasMany(one To Many relations) for another class called Vote
class Employee {
String employeeName
static hasMany = [votes:Vote]
}
and vote class has voteRank integer property, and all employees vote on scale of 1-10 and every employee contains list of these votes given by other employees
class Vote {
Integer voteRank
static belongsTo =[employee:Employee]
}
How do I get the avg of all votes given per employee. This can be solved in SQL by using group by clause. But i am looking for grails domain mapping and gorm oriented answer for this problem.
This HQL query will work:
def averages = Employee.executeQuery(
'select e, avg(vote.voteRank) ' +
'from Employee e join e.votes vote ' +
'group by e')
This will return a List of Object[] arrays where the first element is the Employee and the second is the average. If you don't want to return the whole Employee instances you can select one or more properties:
def averages = Employee.executeQuery(
'select e.employeeName, avg(vote.voteRank) ' +
'from Employee e join e.votes vote ' +
'group by e')
You can accomplish this with a simple method on the Employee domain class.
class Employee {
String employeeName
static hasMany = [votes: Vote]
def sumVotes() {
votes.voteRank.sum() / votes.size()
}
}
I need to get a sum of all items sold per order per store. I am running a sum() on expression using executeQuery(). It works fine as shown below but I wanted to know if there is a better, groovier way to do it.
StoreService {
static transactional = false
def getTotalOrders(def store) {
return Store.executeQuery("select sum(a.soldQuantity * a.soldPrice) as total
from OrderItem a inner join a.order b inner join b.store c
where c= :store", [store: store]).get(0)
}
}
Store {
transient storeService
def getTotalSales() {
storeService.getTotalSales()
}
static hasMany = [items: Item]
// no hasMany to Order
}
Item {
static belongsTo = [store: Store]
// no hasMany to OrderItem
}
Order {
static hasMany = [orderItems: OrderItem]
static belongsTo = [store: Store]
}
OrderItem {
BigDecimal soldPrice
Integer soldQuantity
static belongsTo = [order: Order, item: Item]
}
I think withCriteria() would be easier to read but I couldn't figure out how to do it with expressions within sum() wouldn't take for obvious reasons.
projections {
sum("soldPrice * soldQuantity")
}
Thanks
There are two options you can go with.
Option 1
You can add a formula mapping to your domain class then query it directly.
OrderItem {
BigDecimal soldPrice
Integer soldQuantity
BigDecimal totalPrice
static mapping = {
totalPrice formula: "sold_price * sold_quantity"
}
static belongsTo = [order: Order, item: Item]
}
Now your criteria query can just contain
projections {
sum("totalPrice")
}
Not only that but you can query it with dynamic finders OrderItem.findAllByTotalPriceGreaterThan(20.00) as well as simple access println "The final price is ${orderInstance.totalPrice}. We find this really nifty however there are times when you would want to get totalPrice before the OrderItem has been persisted so we usually write a simple(Not DRY) getter
BigDecimal getTotalPrice() {
totalPrice ?: (soldPrice && soldQuantity) ? soldPrice * soldQuantity : null
}
But you only need this sort of thing if you require totalPrice before it has been persisted.
Option 2
Before formula mappings we used to drop down to the Hibernate Criteria API and use a sqlProjection Projection as part of our criteria query.
projections {
addProjectionToList(Projections.sqlProjection(
"sum(sold_price * sold_quantity) as totalPrice",
["totalPrice"] as String[],
[Hibernate.LONG] as Type[],
), "sumProjection")
}
Note
I think it is important to note that in both the formula and the sql projection, use the column names in the database and your database specific sum syntax.
As of Grails 2.2, SQL projections are supported without having to drop down to the Hibernate Criteria API. Note that a formula mapping may still be more desirable, but with this you can directly implement the sum('soldPrice * soldQuantity') style projection as per your question.
http://grails.org/doc/latest/guide/single.html#criteria
I'd try to add a transient derived property total to OrderItem and use sum() on it.
Try SQL Projection
projections {
sqlProjection 'sum("soldPrice * soldQuantity") as total', 'total', StandardBasicTypes.DOUBLE
}
For farther details
http://docs.grails.org/2.5.6/guide/GORM.html#criteria