Exists clause in criteria - grails

Consider this query:
select user_id
from user_role a
where a.role_id = -1
and not exists (select 1
from user_role b
where b.role_id != a.role_id
and b.user_id = a.user_id);
I'm trying to recreate it using gorm criterias. Is it possible? How can I add the exists clause?
UPDATE
I've been trying to resolve this way:
UserRole.createCriteria().list{
eq('role', roleObj)
createAlias('user', 'u')
not{
inList('u.id', {
not {
eq('role', roleObj)
}
projections {
property 'user.id'
}
})
}
}
Still not working. I'm gettins this error when executing it:
DUMMY$_closure1_closure2_closure3_closure4 cannot be cast to java.lang.Long
I don't understand the error message. The inner criteria returns the list of ids and if I replace the inner criteria with a list of longs it works. Any clue?
Thanks in advance

Not tested : Try Sql restriction
UserRole.createCriteria().list {
and{
eq('roleId',-1)
sqlRestriction(" not exists(select 1
from UserRole b
where ............
)")
}
}
-- Hoping you have named your domain as UserRole and column name named as roleId.

Related

GORM notIn subquery with alias

I am trying to implement the following SQL query in GORM:
select au.* from App_user au
inner join SEC_USER su on su.id=au.SEC_USER_ID
where
not su.id in (
select susr.SEC_USER_ID from SEC_USER_SEC_ROLE susr
inner join SEC_ROLE sr on susr.SEC_ROLE_ID=sr.id
where sr.authority like 'ROLE_INTERNAL%'
and susr.SEC_USER_ID=su.id
)
(get all the users who don't have a role matching the ROLE_INTERNAL.* pattern)
I have a working query, using GORM:
AppUser.createCriteria().list(args, {
secUser {
// def su = SecUser
notIn "id", SecUserSecRole.where {
secRole {
authority ==~ 'ROLE_INTERNAL%'
}
/*
secUser {
id == su.id
}
*/
}.property("secUser.id")
}
})
But this query is inefficient, due to the fact that I don't know how to add the SQL where clause and susr.SEC_USER_ID=su.id in the criteria DSL.
I have seen here and there mentions of using the commented code, to create an alias of the SecUser (su) at the root level, and then using it in the subquery, but I ger the following exception when trying to uncomment my additional statements:
No such property: id for class: SecUser
I feel that i am really close, but I can't figure it out by looking at the documentation. It is rather sparse in this aspect.
As the documentation shows under "7.4.8. More Advanced Subqueries in GORM",
DetachedCriteria<AppUser> query = AppUser.where {
secUser {
def u = SecUser
notIn "id", SecUserSecRole.where {
def u2 = secUser
secRole.authority ==~ 'ROLE_INTERNAL%' && u2.id == u.id
}.property("secUser.id")
}
}
query.list(args)

Grails/Gorm - namedQuery hasMany relationship

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

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

Grails - Use logical OR in CreateCriteria

Starting with the following Domains:
class Person {
String login
String name
}
class MyDomain {
Person creator
static hasMany = [userlist:Person]
}
I wrote a critera to retreive all MyDomainIntances where I'm as creator OR where I'm in the userlist:
def login = "myself"
def result = MyDomain.createCriteria().list () {
projections { distinct ( "id" )
property("name")
property("id")
}
or {
userlist{eq("login", login)}
creator {eq("login",login)}
}
order("name","desc")
}
The problem is with this code I get only instances where I'm in the userlist.
Though creator {eq("login",login)} works well: if I use it itself only, I get a list where I'm as creator
generated query:
Hibernate:
select distinct this_.id as y0_,this_.name as y1_,this_.id as y2_
from mydomain this_ inner join person creator_al2_ on this_.creator_id=creator_al2_.id
inner join mydomain_person userlist5_ on this_.id=userlist5_.mydomain_userlist_id
inner join person userlist1_ on userlist5_.person_id=userlist1_.id
where ((userlist1_.login=?) or (creator_al2_.login=?))
order by this_.name desc
I think the issue is in the OR, as you state that you only get results where 'myself' is in the user list. This blog may help, which also cites this helpful article on createAlias.
def result = MyDomain.createCriteria().list () {
createAlias('creator', 'c')
projections { distinct ( "id" )
property("name")
property("id")
}
or {
userlist{
eq("login", login)
}
eq("c.login", login)
}
order("name","desc")
}

How can I search for domain class instances where a reference to another one is empty or matches a given value?

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')
}
}
}

Resources