(Quick Reference)

7.4.2 Where Queries - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari

Version: 3.1.6

7.4.2 Where Queries

The where method, introduced in Grails 2.0, builds on the support for Detached Criteria by providing an enhanced, compile-time checked query DSL for common queries. The where method is more flexible than dynamic finders, less verbose than criteria and provides a powerful mechanism to compose queries.

Basic Querying

The where method accepts a closure that looks very similar to Groovy's regular collection methods. The closure should define the logical criteria in regular Groovy syntax, for example:

def query = Person.where {
   firstName == "Bart"
}
Person bart = query.find()

The returned object is a DetachedCriteria instance, which means it is not associated with any particular database connection or session. This means you can use the where method to define common queries at the class level:

class Person {
    static simpsons = where {
         lastName == "Simpson"
    }
    …
}
…
Person.simpsons.each {
    println it.firstname
}

Query execution is lazy and only happens upon usage of the DetachedCriteria instance. If you want to execute a where-style query immediately there are variations of the findAll and find methods to accomplish this:

def results = Person.findAll {
     lastName == "Simpson"
}
def results = Person.findAll(sort:"firstName") {
     lastName == "Simpson"
}
Person p = Person.find { firstName == "Bart" }

Each Groovy operator maps onto a regular criteria method. The following table provides a map of Groovy operators to methods:

OperatorCriteria MethodDescription
==eqEqual to
!=neNot equal to
>gtGreater than
<ltLess than
>=geGreater than or equal to
<=leLess than or equal to
ininListContained within the given list
==~likeLike a given string
=~ilikeCase insensitive like

It is possible use regular Groovy comparison operators and logic to formulate complex queries:

def query = Person.where {
    (lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" && age > 9)
}
def results = query.list(sort:"firstName")

The Groovy regex matching operators map onto like and ilike queries unless the expression on the right hand side is a Pattern object, in which case they map onto an rlike query:

def query = Person.where {
     firstName ==~ ~/B.+/
}

Note that rlike queries are only supported if the underlying database supports regular expressions

A between criteria query can be done by combining the in keyword with a range:

def query = Person.where {
     age in 18..65
}

Finally, you can do isNull and isNotNull style queries by using null with regular comparison operators:

def query = Person.where {
     middleName == null
}

Query Composition

Since the return value of the where method is a DetachedCriteria instance you can compose new queries from the original query:

def query = Person.where {
     lastName == "Simpson"
}
def bartQuery = query.where {
     firstName == "Bart"
}
Person p = bartQuery.find()

Note that you cannot pass a closure defined as a variable into the where method unless it has been explicitly cast to a DetachedCriteria instance. In other words the following will produce an error:

def callable = {
    lastName == "Simpson"
}
def query = Person.where(callable)

The above must be written as follows:

import grails.gorm.DetachedCriteria

def callable = { lastName == "Simpson" } as DetachedCriteria<Person> def query = Person.where(callable)

As you can see the closure definition is cast (using the Groovy as keyword) to a DetachedCriteria instance targeted at the Person class.

Conjunction, Disjunction and Negation

As mentioned previously you can combine regular Groovy logical operators (|| and &&) to form conjunctions and disjunctions:

def query = Person.where {
    (lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" && age > 9)
}

You can also negate a logical comparison using !:

def query = Person.where {
    firstName == "Fred" && !(lastName == 'Simpson')
}

Property Comparison Queries

If you use a property name on both the left hand and right side of a comparison expression then the appropriate property comparison criteria is automatically used:

def query = Person.where {
   firstName == lastName
}

The following table described how each comparison operator maps onto each criteria property comparison method:

OperatorCriteria MethodDescription
==eqPropertyEqual to
!=nePropertyNot equal to
>gtPropertyGreater than
<ltPropertyLess than
>=gePropertyGreater than or equal to
<=lePropertyLess than or equal to

Querying Associations

Associations can be queried by using the dot operator to specify the property name of the association to be queried:

def query = Pet.where {
    owner.firstName == "Joe" || owner.firstName == "Fred"
}

You can group multiple criterion inside a closure method call where the name of the method matches the association name:

def query = Person.where {
    pets { name == "Jack" || name == "Joe" }
}

This technique can be combined with other top-level criteria:

def query = Person.where {
     pets { name == "Jack" } || firstName == "Ed"
}

For collection associations it is possible to apply queries to the size of the collection:

def query = Person.where {
       pets.size() == 2
}

The following table shows which operator maps onto which criteria method for each size() comparison:

OperatorCriteria MethodDescription
==sizeEqThe collection size is equal to
!=sizeNeThe collection size is not equal to
>sizeGtThe collection size is greater than
<sizeLtThe collection size is less than
>=sizeGeThe collection size is greater than or equal to
<=sizeLeThe collection size is less than or equal to

Subqueries

It is possible to execute subqueries within where queries. For example to find all the people older than the average age the following query can be used:

final query = Person.where {
  age > avg(age)
}

The following table lists the possible subqueries:

MethodDescription
avgThe average of all values
sumThe sum of all values
maxThe maximum value
minThe minimum value
countThe count of all values
propertyRetrieves a property of the resulting entities

You can apply additional criteria to any subquery by using the of method and passing in a closure containing the criteria:

def query = Person.where {
  age > avg(age).of { lastName == "Simpson" } && firstName == "Homer"
}

Since the property subquery returns multiple results, the criterion used compares all results. For example the following query will find all people younger than people with the surname "Simpson":

Person.where {
    age < property(age).of { lastName == "Simpson" }
}

More Advanced Subqueries in GORM

The support for subqueries has been extended. You can now use in with nested subqueries

def results = Person.where {
    firstName in where { age < 18 }.firstName
}.list()

Criteria and where queries can be seamlessly mixed:

def results = Person.withCriteria {
    notIn "firstName", Person.where { age < 18 }.firstName
 }

Subqueries can be used with projections:

def results = Person.where {
    age > where { age > 18 }.avg('age')
}

Correlated queries that span two domain classes can be used:

def employees = Employee.where {
    region.continent in ['APAC', "EMEA"]
    }.id()
    def results = Sale.where {
    employee in employees && total > 100000
    }.employee.list()

And support for aliases (cross query references) using simple variable declarations has been added to where queries:

def query = Employee.where {
    def em1 = Employee
    exists Sale.where {
        def s1 = Sale
        def em2 = employee
        return em2.id == em1.id
    }.id()
}
def results = query.list()

Other Functions

There are several functions available to you within the context of a query. These are summarized in the table below:

MethodDescription
secondThe second of a date property
minuteThe minute of a date property
hourThe hour of a date property
dayThe day of the month of a date property
monthThe month of a date property
yearThe year of a date property
lowerConverts a string property to upper case
upperConverts a string property to lower case
lengthThe length of a string property
trimTrims a string property

Currently functions can only be applied to properties or associations of domain classes. You cannot, for example, use a function on a result of a subquery.

For example the following query can be used to find all pet's born in 2011:

def query = Pet.where {
    year(birthDate) == 2011
}

You can also apply functions to associations:

def query = Person.where {
    year(pets.birthDate) == 2009
}

Batch Updates and Deletes

Since each where method call returns a DetachedCriteria instance, you can use where queries to execute batch operations such as batch updates and deletes. For example, the following query will update all people with the surname "Simpson" to have the surname "Bloggs":

def query = Person.where {
    lastName == 'Simpson'
}
int total = query.updateAll(lastName:"Bloggs")

Note that one limitation with regards to batch operations is that join queries (queries that query associations) are not allowed.

To batch delete records you can use the deleteAll method:

def query = Person.where {
    lastName == 'Simpson'
}
int total = query.deleteAll()