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).
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 <> ?
)
Just for background, let us have these three domain classes:
class Group {
Long id
Person person
}
class Person {
Long id
Country country
String name
}
class Country {
Long id
}
So, with these classes in mind, I am given a Group object's id as well as a Country object's id. I would like to get the list of Person objects based on these two.
It seems relatively simple, but I am new to criteria queries and so I am struggling to figure out what I am doing wrong. This is what I have so far:
def c = Group.createCriteria()
def names = c.list (order: "asc") {
createAlias('person', 'p')
createAlias('p.country', 'c')
and {
eq ('c.id', Long.valueOf(countryId))
eq ('id', groupId)
}
projections {
property('p.name')
}
}
Of course, this is wrong as it is throwing errors. Can someone please let me know what I am doing wrong?
Thanks for your help!
static def searchPersons(Map params) {
return Group.createCriteria().list(params, {
if(params.groupId) {
eq('id', params.groupId.toLong())
}
person {
order('name')
if(params.countryId) {
country {
eq('id', params.countryId.toLong())
}
}
}
projections {
property('person')
}
})
}
Still, it might be better to add the necessary associations (hasMany, etc.) on your domains.
The first thing you can do is improve the associations between your domain classes. This will help make criteria queries simpler (and deter monkey-patching later).
In your example, the association between Person an Group is a one-to-many; one person can have many groups. That may be your intention, but it also means that a group can have only one person. Basically, there's no way to group people together.
I'm going to assume that you want a many-to-one relationship so that many Person (people) can be in the same Group. With this in mind, the domain classes (with the explicit IDs left in) would look like this:
class Group {
Long id
}
class Person {
Long id
Country country
String name
Group group
}
class Country {
Long id
}
As for the query, since your expected result is instances of Person, the best place to start is with Person rather than Group.
List of Person instances
Here's how to get a list of Person instances.
Where query
def people = Person.where {
country.id == countryId
group.id == groupId
}.list()
Criteria query
def people = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
}
List of Person names
Notice that there's a discrepancy between your question and example. You asked for a list of Person instances, yet your example demonstrates attempting to get a list of Person names .
Here's how to get a list of names of the Person instances.
Where query
def names = Person.where {
country.id == countryId
group.id == groupId
}.projections {
property 'name'
}.list()
Criteria query
def names = Person.withCriteria {
country {
eq 'id', countryId as Long
}
group {
eq 'id', groupId
}
projections {
property 'name'
}
}
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')
}
}
}
I have domain like
class Book {
Author author
String title
}
and another
class ListingTracking {
Book book
String oldVal
String newVal
Date modDate
static constraints = {
}
}
and now I need all those listingTacking which have a list of book ids but my criteria is on Book domain and I can not change association.
I tried
def books= Book.createCriteria().list {
'in'("id", [7,2,3,6].collect {it as Long} )
createAlias("listingTracking","lt")
projections{
property("title")
}
'in'("lt.book.id",[7,2,3,6].collect {it as Long})
}
I know that I can't create createAlias of listingTracking in Book domain but I need something like that, Is this possible via criteria?.
def listingTracking = ListingTracking.findAllByBookInList( Book.getAll([7,2,3,6]) )
Instead of Book.getAll you can use any finder you prefer on Books
I've read through a lot of posts and docs and must be missing something.
In my application (model below) I am having a data issue that seems to be out of my control where I have a categoryId in the join table JOBORDERCATEGORIES that has no corresponding row in the CATEGORY table. I am accessing the category data through getJobCategories() in the JobOrder. This is producing the following error when my Category table is missing a referenced row:
2012-03-07 08:02:10,223 [quartzScheduler_Worker-1] ERROR listeners.SessionBinderJobListener - Cannot flush Hibernate Sesssion, error will be ignored
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.matrixres.domain.Category#416191]
and my code is halting.
I have tried using ignoreNotFound but it is not helping me to get past the error above.
If I missed a post on the solution to this problem please link me to it otherwise thoughts are welcome on how to move forward. Perhaps there is a more direct route I will have to hammer in to achieve my goal of getting a good category list, but I am not familiar enough with the framework to know what is next. As a note, I cannot write to any of these tables.
thanks, rich
A simplified Version of my Model:
Job Order Object:
class JobOrder {
def getJobCategories() {
def cats = []
try {
def jocategories = this.categories
if(!jocategories.isEmpty() && jocategories!=null){
println "we got categories for ${this.id}"
jocategories.each { cat ->
if(cat?.parentCategoryID == 0){
if(cat.occupation != null){
cats << cat.occupation
} else {
cats << cat.name
}
}
}
}
} catch(e) {
cats << "Other Area(s)"
}
cats
}
static mapping = {
table 'dbo.JOBORDER'
version false
id generator: 'identity', column: 'JOBORDERID'
/*
* several other mapped columns deleted here
*/
categories joinTable:[name:'jobOrderCategories', column: 'categoryId', key:'jobOrderID']
}
/*
* several properties deleted here
*/
static hasMany = [categories: Category] //several other hasMany associations exist
}
Category Object:
class Category {
static mapping = {
table 'CATEGORY'
version false
id generator: 'identity', column: 'categoryID'
occupation column: 'OCCUPATION'
name column: 'NAME'
parentCategoryID column: 'PARENTCATEGORYID'
/*
* several other mapped columns deleted here
*/
jobOrders joinTable:[name:'jobOrderCategories', column: 'jobOrderID', key:'categoryId']
}
String name
String occupation
int parentCategoryID
/*
* several properties deleted here
*/
static belongsTo = [JobOrder]
static hasMany = [jobOrders:JobOrder]
}
Join Table:
class JobOrderCategories {
static mapping = {
table 'JOBORDERCATEGORIES'
version false
isDeleted column: 'ISDELETED'
jobOrderID column: 'JOBORDERID'
categoryId column: 'CATEGORYID'
}
Boolean isDeleted
Integer jobOrderID
Integer categoryId
}
These kinds of situations aren't the most fun, but I have had to deal with this kind of Roll-Your-Own ORM problems before ;) Basically what you're going to want to do here is store the object properties not typed as Object references, but as ints, and while you'll lose some of the dynamic finder things GORM makes so nifty, you'll have a fairly straight-forward means of accessing the data that doesn't involve tangling yourself up with Hibernate's innards.
Basically, this will involve ditching your hasMany and belongsTo properties on JobOrder and Category. Instead, you'll want to do things like
def myJobOrder = JobOrder.get(yourId);
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(myJobOrder.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
You can put variations of those traversals in helper methods on your classes, like for example, your getJobCategories method could become
class JobOrder {
//...
def getJobCategories() {
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(this.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
}
}
And so on. This is definitely not the prettiest thing in the world to deal with, and you lose your ability to traverse through things easily with GORM (ex a
jobOrder.withCriteria {
categories {
eq('name', blah)
}
}
becomes a executeQuery type of situation.)
But overall, its not too bad to deal with :)
Hope that helps!