Grails/Gorm - namedQuery hasMany relationship - grails

I have the following domain classes
class EventA {
static belongsTo = [offer: Offer]
}
class EventB extends EventA {}
class EventC extends EventA {}
class Offer {
static hasMany [events: EventA]
}
I need to retrieve offers that are not associated with an EventC.
In SQL this can easily be performed as:
SELECT *
FROM OFFER O
LEFT JOIN EVENTC C ON O.event_id = C.id
WHERE C.ID IS NULL
Searching through the grails documentation I found instanceOf. Stating that once you have the result set you can perform a check of the instance type.
def offers = Offer.list()
for (Offer o in offers) {
for(Event e : o.events) {
if (e.instanceOf(EventC)) {
// no bueno
}
}
}
The above just feels wrong. I would prefer to have the database do such filtering for me. Is there a way to perform such a filter with searchCriteria?

You can accomplish this by querying the Event classes directly. That way you can specifically query the flavor of Event you care about. Then query the Offer table with the list of Id's
Offer.findAllByIdInList(EventC.list().offerId)

This actually ended up being easier then I expected. On my search criteria, I can build an expression to not include any Offer that has an event EventC.
Example:
Offer.with {
events {
ne('class', EventC)
}
}
Since I questioned this approach I enabled hibernate logging. Ironically, it generated SQL that was pretty similar to what I was after.
SELECT *
FROM OFFER O
LEFT JOIN EVENTB B ON O.ID == B.EVENT_ID
LEFT JOIN EVENTC C ON O.ID == C.EVENT_ID
WHERE
(
CASE
WHEN B.ID IS NOT NULL THEN 1
WHEN C.ID IS NOT NULL THEN 2
END <> ?
)

Related

Get count of child objects in grails Criteria query

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

GORM or HSQL for left join exclusive

I have Course domain,
Course has one teacher or null
I want to find all courses which either has no teacher or teacher.id != :loginId
How can I write query using GORM dynamic find* methods
Or write it using HSQL
- My teacher property is User domain
Appreciate your help
hasOne Class Structure
class Course {
User teacher
static hasOne = [
teacher: User
]
}
class User {
// implicit id field
}
Using HQL
def getCourses(def loginId) {
return Course.executeQuery("""
SELECT
c
FROM
Course c
LEFT OUTER JOIN c.teacher as t
WHERE
(t.id = NULL OR t.id != :logIn)
""", [loginId: loginId])
}
Using CriteriaBuilder
import org.hibernate.criterion.CriteriaSpecification
def getCourses(def loginId) {
return Course.createCriteria().list{
createAlias(
"teacher",
"t",
CriteriaSpecification.LEFT_JOIN
)
or {
ne("t.id", loginId)
isNull('t.id')
}
}
}
I'm going off of past experiences so I haven't tested your exact scenario, but I believe both options should work. I'm under the impression that a grails dynamic finder would not work in this case because of the nested condition you need (course.teacher.id != loginId).

Grails search owned objects by properties

I am trying to find the proper syntax for a query that I know has got to be very common but couldn't find a code example for.
class ObjA {
...
static hasMany = [b:ObjB]
}
if a is an instance of ObjA, I want to perform a query like:
a.b.findAllBsSuchThat(b.someproperty = somevalue)
In order to avoid (N+1) queries for lazy associations per a, you can use a criteria as:
ObjA.withCriteria {
b {
eq 'someProperty', someValue
}
}
or where queries:
ObjA.where { b.someProperty == somevalue }.list()
If you use something like a.b.findAllBsSuchThat(b.someproperty = somevalue) then you would be getting all b's for a and then filtering on the result. This will affect the performance and will unnecessary.

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

Grails projection on arithmetic expression with executeQuery()?

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

Resources