Skip to content

[JAY-796] Add intersection support to QueryBuilder - #83

Open
sergio-bobillier wants to merge 1 commit into
sb-JAY-796-query-unionsfrom
sb-JAY-796-query-intersect
Open

sergio-bobillier wants to merge 1 commit into
sb-JAY-796-query-unionsfrom
sb-JAY-796-query-intersect

Conversation

@sergio-bobillier

@sergio-bobillier sergio-bobillier commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Add QueryBuilder#intersection to combine two queries with a boolean must clause, equivalent to AND, while retaining the existing merge behavior for other builder options. Cover empty and non-empty query combinations, and document the method.

This behavior is desired when combining query clauses that involve one or more should subclauses. Combining the following two queries with the #merge method:

{
  "bool": {
    "must": [
      { "range": { "timestamp": { "gte": "now-1d/d" } } }
    ]
  }
}
{
  "bool": {
    "should": [
      { "term": { "field": "value" } },
      { "term": { "another_field": "another_value" } }
    ]
  }
}

The resulting query is:

{
  "bool": {
    "must": [
      { "range": { "timestamp": { "gte": "now-1d/d" } } }
    ],
    "should": [
      { "term": { "field": "value" } },
      { "term": { "another_field": "another_value" } }
    ]
  }
}

Because of the way should behaves when paired with must, the results produced by this query might not be what one expects. From the Elasticsearch documentation:

Matches in the should clauses are optional. They will only boost the relevance scores of documents that already match the required must criteria and don't add new documents to the result set.

So, in the above example, the query will basically match all documents whose timestamp is within the last day. If they happen to match the should clauses, it will boost their relevance scores. However, one might actually want to get only the documents that are within the last day and also match either of the clauses inside should.

For that, the QueryBuilder#intersection method can be used to combine the queries with a must clause, ensuring that only documents matching both conditions are returned. The resulting query from doing that would be:

{
  "bool": {
    "must": [
      {
        "bool": {
          "must": [
            { "range": { "timestamp": { "gte": "now-1d/d" } } }
          ]
        }
      },
      {
        "bool": {
          "should": [
            { "term": { "field": "value" } },
            { "term": { "another_field": "another_value" } }
          ]
        }
      }
    ]
  }
}

Which produces the intended result set.

end
end

describe '#intersection' do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great to have some test cases to test union and intersection operations combined in a more sophisticated AND/OR clause

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I have added an integration test with such a case, it also servers as an illustration of one of those cases when one might want an explicit intersection instead of a regular merge.

Comment thread spec/jay_api/elasticsearch/query_builder/query_clauses_spec.rb
Add QueryBuilder#intersection to combine two queries with a boolean
'must' clause, equivalent to AND, while retaining the existing merge
behavior for other builder options. Cover empty and non-empty query
combinations, and document the method.

This behavior is desired when combining query clauses that involve one
or more 'should' subclauses. Combining the following two queries with
the #merge method:

{
  "bool": {
    "must": [
      { "range": { "timestamp": { "gte": "now-1d/d" } } }
    ]
  }
}

{
  "bool": {
    "should": [
      { "term": { "field": "value" } },
      { "term": { "another_field": "another_value" } }
    ]
  }
}

The resulting query is:

{
  "bool": {
    "must": [
      { "range": { "timestamp": { "gte": "now-1d/d" } } }
    ],
    "should": [
      { "term": { "field": "value" } },
      { "term": { "another_field": "another_value" } }
    ]
  }
}

Because of the way 'should' behaves when paired with 'must', the results
produced by this query might not be what one expects. From the
Elasticsearch documentation:

  Matches in the should clauses are optional. They will only boost the
  relevance scores of documents that already match the required must
  criteria and don't add new documents to the result set.

So, in the above example, the query will basically match all documents
whose timestamp is within the last day. If they happen to match the
'should' clauses, it will boost their relevance scores. However, one
might actually want to get only the documents that are within the last
day and also match either of the clauses inside 'should'.

For that, the QueryBuilder#intersection method can be used to combine
the queries with a 'must' clause, ensuring that only documents matching
both conditions are returned. The resulting query from doing that would
be:

{
  "bool": {
    "must": [
      {
        "bool": {
          "must": [
            { "range": { "timestamp": { "gte": "now-1d/d" } } }
          ]
        }
      },
      {
        "bool": {
          "should": [
            { "term": { "field": "value" } },
            { "term": { "another_field": "another_value" } }
          ]
        }
      }
    ]
  }
}

Which produces the intended result set.
@sergio-bobillier
sergio-bobillier force-pushed the sb-JAY-796-query-intersect branch from 08df482 to 2a4bb8a Compare October 27, 2026 15:21

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants