criteria uses "inner join" instead "left join" approach by default making my query work not the way I planned

Andrzej Bobak picture Andrzej Bobak · Jun 13, 2013 · Viewed 21.6k times · Source

The question is: how do I make GORM generate left joins instead of inner joins in this particular example?

Testbed:

Given classes A, B and C:

class A{
    B someObject
}

class B{
    C importantObject
}

class C{
    boolean interestingFlag
}

I want to list all the elements of A class that:

  • their B.C object is null OR
  • their B.C object interestingFlag value is false

What I tried so far:

This approach produces correct list of A where B.C is null (conditional 2 commented out) OR correct list of A where B.C.interestingFlag = false (no matter if conditional 1 is commented out or not). When both conditionals are uncommented it returns only a list of elements where A.B.C.interestingFlag = false (A.B.C = null conditional is ignored)

// approach 1 (conditional 1 is ignored)
def result = A.withCriteria{
    someObject{
        or{
            isNull('importantObject') // conditional 1, works well when conditional 2 is commented out
            importantObject{
                eq('interestingFlag', false) // conditional 2, works well alone, discards conditional 1 when both of them are uncommented
            }
        }  
    } 
}

Edit: As requested in comment I'm pasting a hibernate generated sql:

Hibernate: select this_.id as id1_2_, this_.version as version1_2_, 
this_.some_object_id as some3_1_2_, someobject1_.id as id2_0_, 
someobject1_.version as version2_0_, someobject1_.important_object_id as 
important3_2_0_, importanto2_.id as id0_1_, importanto2_.version as version0_1_, 
importanto2_.interesting_flag as interest3_0_1_ from a this_ 
inner join b someobject1_ on this_.some_object_id=someobject1_.id 
inner join c importanto2_ on someobject1_.important_object_id=importanto2_.id 
where ((someobject1_.important_object_id is null or (importanto2_.interesting_flag=?)))

When I copy and paste it in the pgAdmin query tool directly with a few things changed (inner joins changed to left joins, and provided the interestingFlag = "false" parameter) everything works as I wanted (I get both A.B.C = null and A.B.C.importantFlag = false objects)

Hibernate: select this_.id as id1_2_, this_.version as version1_2_, 
this_.some_object_id as some3_1_2_, someobject1_.id as id2_0_, 
someobject1_.version as version2_0_, someobject1_.important_object_id as 
important3_2_0_, importanto2_.id as id0_1_, importanto2_.version as version0_1_, 
importanto2_.interesting_flag as interest3_0_1_ from a this_ 
left join b someobject1_ on this_.some_object_id=someobject1_.id 
left join c importanto2_ on someobject1_.important_object_id=importanto2_.id 
where ((someobject1_.important_object_id is null or (importanto2_.interesting_flag=false)))

Answer

Andrzej Bobak picture Andrzej Bobak · Jun 17, 2013

Tested and working solution:

    def result = A.withCriteria{
        createAlias('someObject', 'so', CriteriaSpecification.LEFT_JOIN)
        createAlias('so.importantObject', 'imp', CriteriaSpecification.LEFT_JOIN)
        or {
            isNull('so.importantObject')
            eq('imp.interestingFlag', false)
        } 

    }

Solution update as suggested in comment:

    def result = A.withCriteria{
        createAlias('someObject', 'so', JoinType.LEFT_OUTER_JOIN)
        createAlias('so.importantObject', 'imp', JoinType.LEFT_OUTER_JOIN)
        or {
            isNull('so.importantObject')
            eq('imp.interestingFlag', false)
        } 

    }