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])
Related
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 <> ?
)
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 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).
To paraphrase a Grails example, I'm trying to fetch a list of books with no author. The author could be anonymous, or simply not set (null). So far I can search by value, I can search by null, but I can't seem to do both at once. Using the Book and Author example, let's say I have the following books...
"The Adventures of Tom Sawyer" by Mark Twain
"O: A Presidential Novel" by Anonymous
"Beowulf"
To find books by "Anonymous" I could do this...
Book.withCriteria {
author {
eq('name', 'Anonymous')
}
}
Returns "O: A Presidential Novel"
All is well. Now to find books with no author I can do this...
Book.withCriteria {
isNull('author')
}
Returns "Beowulf"
That's fine too. So to fetch both books I should 'or' them together...
Book.withCriteria {
or {
isNull('author')
author {
eq('name', 'Anonymous')
}
}
}
Returns "O: A Presidential Novel"
Why doesn't this return both books? I'm using Grails 2.3.7 with Hibernate 3.6.10.16
Update:
I've found a query that works though I'm confused how it's different...
Book.withCriteria {
or {
isNull('author')
// author {
// eq('name', 'Anonymous')
// }
sqlRestriction('{alias}.author_id = (select author_id from authors where name = ?)', 'Anonymous')
}
}
As mentioned in another answer, association queries like that map to an inner join at the SQL level. You can instead do a left outer join using createAlias:
def list = c.list {
createAlias('author', 'a', CriteriaSpecification.LEFT_JOIN)
or {
isNull('author')
eq('a.name', 'Anonymous')
}
}
Your 3rd query results in this SQL statement (Grails 2.4.4, PostgreSQL):
select ...
from book b inner join author a on b.author_id = a.id
where (b.author_id is null or (a.name=$1))
So Grails emits an inner join which eliminates all books without an author. I don't know if it is possible that Grails emits an outer join here.
As for your 4th query using sqlRestriction, this results in a sub select:
select ...
from book b
where (b.author_id is null or b.author_id =
(select author_id from author where name = $1))
So this works, but generally speaking sub selects might be slower than inner/outer joins.
Did you try to do ? It seems that you get just the first result.
def c = Book.createCriteria()
def list = c.list{
or {
isNull('author')
author {
eq('name', 'Anonymous')
}
}
}
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.