Search code examples
grailsgrails-orm

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

Solution

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