Grails - Use logical OR in CreateCriteria - grails

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

Related

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

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

Exists clause in criteria

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.

How to ensure data integrity when using Table Per Subclass?

I am using the table per subclass strategy in Grails by setting the tablePerHierarchy property of the static mapping field in my superclass to false. This way, Grails creates one table for my superclass and one additional table for each of my subclasses.
However, while the superclass and subclass records share the same ID (primary key), there are no foreign key constraints to keep them consistent, i.e. it is possible to delete the superclass record, leaving the subclass record in an invalid state. I want to know if there is a setting/property to make GORM address this in some way, e.g. through constraints. Or is my only option to add foreign keys manually?
For example, given the following domain class as superclass:
class Product {
String productCode
static mapping = {
tablePerHierarchy false
}
}
And the following domain class as subclass:
class Book extends Product {
String isbn
}
This results in the creation of two tables, the Product table and the Book table. When creating a Book – through scaffolded pages, for instance – a record is inserted into each table, their only link being the fact that the ID value is the same for each. Specifically, the data might look like this:
PRODUCT
Id Version ProductCode
1 1 BLAH-02X1
BOOK
Id ISBN
1 123-4-56-7891011-1
Because there is no formal relationship defined at the database level for these tables, it is possible to delete one of the records and leave the other, which results in invalid data. Obviously I can use SQL to manually create a foreign key constraint on the two ID fields, but I was hoping to let Grails handle that. Is this possible?
Using Grails 2.2.1
Solved!
The following solution fixed this issue for me. Add the class below to src/java (this class cannot be written in Groovy)
package org.example;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
import org.hibernate.MappingException;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import java.util.Iterator;
public class TablePerSubclassConfiguration extends GrailsAnnotationConfiguration {
private static final long serialVersionUID = 1;
private boolean alreadyProcessed = false;
#Override
protected void secondPassCompile() throws MappingException {
super.secondPassCompile();
if (alreadyProcessed) {
return;
}
for (PersistentClass persistentClass : classes.values()) {
if (persistentClass instanceof RootClass) {
RootClass rootClass = (RootClass) persistentClass;
if (rootClass.hasSubclasses()) {
Iterator subclasses = rootClass.getSubclassIterator();
while (subclasses.hasNext()) {
Object subclass = subclasses.next();
// This test ensures that foreign keys will only be created for subclasses that are
// mapped using "table per subclass"
if (subclass instanceof JoinedSubclass) {
JoinedSubclass joinedSubclass = (JoinedSubclass) subclass;
joinedSubclass.createForeignKey();
}
}
}
}
}
alreadyProcessed = true;
}
}
Then in DataSource.groovy set this as the configuration class
dataSource {
configClass = 'org.example.TablePerSubclassConfiguration'
pooled = true
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
}
Update
I've submitted a pull request to Grails for this issue. The fix was included in Grails 2.3.8 or 2.3.9 (can't remember which).
Hibernate ensures the data integrity in case of table per subclass. In case of table per subclass, subclass maintains a primary key association with superclass. Have a look at Hibernate Table per subclass. To validate the fact here is your test case:
class Product {
String productCode
static mapping = {
tablePerHierarchy false
}
}
class Book extends Product{
String isbn
}
//Test Case
def testTablePerSubclass{
def product = new Product(productCode: 'XYZ456')
product.save(flush: true, failOnError: true)
def book = new Book(isbn: '123456123', productCode: 'ABC123')
book.save(flush: true, failOnError: true)
assert Book.list().size() == 1 //One Book
assert Book.list()*.id == [2] //Book id
assert Product.list().size() == 2 //One Product, one Book (2 Products)
assert Product.list()*.id == [1, 2] //Product id, Book Id
//Grab the product (book) to delete
def productToDelete = Product.get(book.id)
productToDelete.delete(flush: true)
assert Book.list().isEmpty() //Book deleted from Book table as well
assert Product.list().size() == 1 //One Product remaining in Product table
assert Product.list()*.id == [1] //Remaining Product Id
}
Keep logSql true in DataSource.groovy to see corresponding sqls getting executed.
Log Sql Output:-
Hibernate: insert into product (id, version, product_code) values (null, ?, ?)
Hibernate: insert into product (id, version, product_code) values (null, ?, ?)
Hibernate: insert into book (isbn, id) values (?, ?)
Hibernate: select this_.id as id0_0_, this_1_.version as version0_0_, this_1_.product_code as product3_0_0_, this_.isbn as isbn1_0_ from book this_ inner join product this_1_ on this_.id=this_1_.id
[com.example.Book : 2]
Hibernate: select this_.id as id0_0_, this_.version as version0_0_, this_.product_code as product3_0_0_, this_1_.isbn as isbn1_0_, case when this_1_.id is not null then 1 when this_.id is not null then 0 end as clazz_0_ from product this_ left outer join book this_1_ on this_.id=this_1_.id
[com.example.Product : 1, com.example.Book : 2]
Hibernate: delete from book where id=?
Hibernate: delete from product where id=? and version=?
Hibernate: select this_.id as id0_0_, this_1_.version as version0_0_, this_1_.product_code as product3_0_0_, this_.isbn as isbn1_0_ from book this_ inner join product this_1_ on this_.id=this_1_.id
[]
Hibernate: select this_.id as id0_0_, this_.version as version0_0_, this_.product_code as product3_0_0_, this_1_.isbn as isbn1_0_, case when this_1_.id is not null then 1 when this_.id is not null then 0 end as clazz_0_ from product this_ left outer join book this_1_ on this_.id=this_1_.id
[com.example.Product : 1]
Using Grails 2.2.2

Using groupProperty and countDistinct in Grails Criteria

I'm using Grails 1.2.4. I would like to know on how can I sort by "countDistinct" (descending) and with groupProperty inside a projections.
Here are my domains:
class Transaction {
static belongsTo = [ customer : Customer, product : Product ]
Date transactionDate = new Date()
static constraints = {
transactionDate(blank:false)
}
}
class Product {
String productCode
static constraints = {
productCode(blank:false)
}
}
In MySQL terms, this is what I want:
select
product_id,
count(product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
In general term, I would like to get a list of products (or just product id) sorted by the number of transactions a product had (descending)
This is my guess:
def c = Transaction.createCriteria() def transactions = c.list {
projections {
groupProperty("product")
countDistinct("product")
}
maxResults(pageBlock)
firstResult(pageIndex) }
def products = transactions.collect { it[0] }
But it doesn't give my expected result. Any lead on this will be highly appreciated. Thanks!
Try this:
def c = Transaction.createCriteria()
def transactions = c.list {
projections {
groupProperty("product")
countDistinct("id")
}
maxResults(pageBlock)
firstResult(pageIndex)
}
Your criteria query is actually equivalent to:
select
product_id,
count(**distinct** product_id)
from
transaction
group by
product_id
order by
count(product_id) desc
Note the distinct. You want to count the number of distinct transactions per product, so counting the transaction id (or any transaction property) makes more sense. Product id just happens to work without the distinct clause.
You can turn on the super useful hibernate SQL logging for debugging this kind of issue. It will show you exactly how your criterias are being transformed into SQL. At runtime:
org.apache.log4j.Logger.getLogger("org.hibernate").setLevel(org.apache.log4j.Level.DEBUG)
or throw this in your Config.groovy:
environments {
development {
log4j = {
debug 'org.hibernate'
}
}
}
EDIT: use countDistinct("id", "transactionCount") as the projection and order("transactionCount") will sort by the count.

Resources