How do I use boolean operators with Hibernate Search
Asked Answered
G

4

8

I'm learning the Hibernate Search Query DSL, and I'm not sure how to construct queries using boolean arguments such as AND or OR.

For example, let's say that I want to return all person records that have a firstName value of "bill" or "bob".

Following the hibernate docs, one example uses the bool() method w/ two subqueries, such as:

QueryBuilder b = fts.getSearchFactory().buildQueryBuilder().forEntity(Person.class).get();
Query luceneQuery = b.bool()
    .should(b.keyword().onField("firstName").matching("bill").createQuery())
    .should(b.keyword().onField("firstName").matching("bob").createQuery())
    .createQuery();

logger.debug("query 1:{}", luceneQuery.toString());

This ultimately produces the lucene query that I want, but is this the proper way to use boolean logic with hibernate search? Is "should()" the equivalent of "OR" (similarly, does "must()" correspond to "AND")?.

Also, writing a query this way feels cumbersome. For example, what if I had a collection of firstNames to match against? Is this type of query a good match for the DSL in the first place?

Gigahertz answered 19/4, 2012 at 0:51 Comment(1)
must is and, but should seems like some sort of softer version of and. Definitely not or. In theory you can get or as not and not, getting must(b.must().not().must().not()).not() but that's not a very satisfying answer.Synsepalous
I
3

Yes your example is correct. The boolean operators are called should instead of OR because of the names they have in the Lucene API and documentation, and because it is more appropriate: it is not only influencing a boolean decision, but it also affects scoring of the result.

For example if you search for cars "of brand Fiat" OR "blue", the cars branded Fiat AND blue will also be returned and having an higher score than those which are blue but not Fiat.

It might feel cumbersome because it's programmatic and provides many detailed options. A simpler alternative is to use a simple string for your query and use the QueryParser to create the query. Generally the parser is useful to parse user input, the programmatic one is easier to deal with well defined fields; for example if you have the collection you mentioned it's easy to build it in a for loop.

Indiscrimination answered 25/4, 2012 at 19:54 Comment(0)
P
2

You can also use BooleanQuery. I would prefer this beacuse You can use this in loop of a list.

    org.hibernate.search.FullTextQuery hibque = null; 

    org.apache.lucene.search.BooleanQuery bquery = new BooleanQuery();

    QueryBuilder qb = fulltextsession.getSearchFactory().buildQueryBuilder()
              .forEntity(entity.getClass()).get();
    for (String keyword : list) {
        bquery.add(qb.keyword().wildcard().onField(entityColumn).matching(keyword)
              .createQuery()  , BooleanClause.Occur.SHOULD);
    }

    if (!filterColumn.equals("") && !filterValue.equals("")) {
       bquery.add(qb.keyword().wildcard().onField(column).matching(value).createQuery()
                        , BooleanClause.Occur.MUST);
    } 

    hibque = fulltextsession.createFullTextQuery(bquery, entity.getClass());

    int num = hibque.getResultSize();
Psf answered 10/3, 2015 at 6:30 Comment(0)
P
1

To answer you secondary question:

For example, what if I had a collection of firstNames to match against?

I'm not an expert, but according to (the third example from the end of) 5.1.2.1. Keyword queries in Hibernate Search Documentation, you should be able to build the query like so:

Collection<String> namesCollection = getNames(); // Contains "billy" and "bob", for example
StringBuilder names = new StringBuilder(100);
for(String name : namesCollection) {
    names.append(name).append(" "); // Never mind the space at the end of the resulting string.
}

QueryBuilder b = fts.getSearchFactory().buildQueryBuilder().forEntity(Person.class).get();
Query luceneQuery = b.bool()
    .should(
        // Searches for multiple possible values in the same field
        b.keyword().onField("firstName").matching( sb.toString() ).createQuery()
    )
    .must(b.keyword().onField("lastName").matching("thornton").createQuery())
    .createQuery();

and, have as a result, Persons with (firstName preferably "billy" or "bob") AND (lastName = "thornton"), although I don't think it will give the good ol' Billy Bob Thornton a higher score ;-).

Prebend answered 24/11, 2012 at 8:9 Comment(0)
C
1

I was looking for the same issue and have a somewhat different issue than presented. I was looking for an actual OR junction. The should case didn't work for me, as results that didn't pass any of the two expressions, but with a lower score. I wanted to completely omit these results. You can however create an actual boolean OR expression, using a separate boolean expression for which you disable scoring:

val booleanQuery = cb.bool();
val packSizeSubQuery = cb.bool();

packSizes.stream().map(packSize -> cb.phrase()
     .onField(LUCENE_FIELD_PACK_SIZES)
     .sentence(packSize.name())
     .createQuery())
     .forEach(packSizeSubQuery::should);

booleanQuery.must(packSizeSubQuery.createQuery()).disableScoring();
fullTextEntityManager.createFullTextQuery(booleanQuery.createQuery(), Product.class)
return persistenceQuery.getResultList();
Chryso answered 7/11, 2016 at 17:10 Comment(3)
Do you know how compare a value with NULL in Hibernate Search? For example, in SQL I compare a value with NULL this way: WHERE TABLE.FIELD IS NULLImpersonate
Cant you just do a keyword search with null as value? As far as my understanding of Hibernate spatial goes, the field bridge should ensure your null value is serialised equally for the query as it is done for storing the entity, and this should work out of the box.Chryso
Thks Jan. The Answer is here: https://mcmap.net/q/561277/-how-to-query-lucene-for-empty-fields. But need use Lucene native query.Impersonate

© 2022 - 2024 — McMap. All rights reserved.