Olav Grønås Gjerde

profile image
Full stack system architect with passion for Unix, Java, Python and databases.
Twitter @olavgg
1 year ago

Grails GORM Criteria with sub query for has many collection

Today I solved something that I had to write about.

I have list of excursions, that can be added to a incident event. So one incident can have multiple excursions. As these excursions are being connected to incidents we only want to display excursions that is not yet connected to a incident.

So how to write this with using Grails Criteria?


List<Excursion> excursions = Excursion.withCriteria {
            
    sensor{
        eq("assetProject", assetProject)
    }
                        
    between("startDateTime", a, b)
    eq("isSuppressed", false)

    if( showOnlyExcursionsNotInIncident ){
                
        // The trick here is the subquery builder
        DetachedCriteria subQuery = DetachedCriteria.forClass(Incident, 'i')
            .with {
                createAlias("i.excursions", "e2")
                setProjection Projections.property('e2.id')
                add Restrictions.conjunction()
                    .add(
                        Restrictions.eqProperty(
                            'e2.id',
                            'this.id'
                        )
                   )
            }
                        
        add Subqueries.notExists(subQuery)
    }
        
    order "startDateTime", "desc"
}

Which will generate the following SQL

select
        ....
    from
        excursion this_ 
    inner join
        sensor sensor_ali1_ 
            on this_.sensor_id=sensor_ali1_.id 
    where
        (
            sensor_ali1_.asset_project_id=?
        ) 
        and this_.start_date_time between ? and ? 
        and this_.is_suppressed=? 
        and not exists (
            select
                ee2x1_.id as y0_ 
            from
                incident i_ 
            inner join
                incident_excursion excursione3_ 
                    on i_.id=excursione3_.incident_excursion_id 
            inner join
                excursion ee2x1_ 
                    on excursione3_.excursion_id=ee2x1_.id 
            where
                (
                    ee2x1_.id=this_.id
                )
        ) 
    order by
        this_.start_date_time desc

Learning to do this stuff with criterias is awesome, as it is much easier to do custom filtering with criterias than using raw SQL. It can also be easier to read.