Advanced Queries

Build raw Elasticsearch queries with Sigmie's low-level newQuery() API — boolean must/should/filter, term, match, range, and script scoring.

On this page

newQuery() gives you direct access to Elasticsearch’s boolean query DSL. Reach for it when you need control newSearch() doesn’t expose — custom scoring, nested boolean logic, or features specific to your Elasticsearch version.

use Sigmie\Query\Queries\Compound\Boolean;
 
$response = $sigmie->newQuery('disney-movies')
->properties($props)
->sortString('name:asc')
->bool(function (Boolean $bool) {
$bool->filter()->matchAll();
$bool->filter()->multiMatch('goofy', ['name', 'description']);
$bool->must()->term('is_active', true);
$bool->mustNot()->term('is_active', false);
$bool->mustNot()->wildcard('foo', '**/*');
$bool->should()->bool(fn (Boolean $bool) =>
$bool->must()->match('name', 'Mickey')
);
})
->from(0)
->size(15)
->get();

When to use each builder

Use newSearch() for:

  • User-facing search.
  • Built-in features (typo tolerance, highlighting, facets, semantic search).
  • Most ordinary search code.

Use newQuery() for:

  • Complex boolean logic.
  • Custom scoring (scriptScore, functionScore).
  • Direct Elasticsearch DSL features Sigmie doesn’t wrap.
  • Migrating from raw Elasticsearch queries.

Basic queries

use Sigmie\Mappings\NewProperties;
 
$props = new NewProperties;
$props->name('name');
$props->number('age')->integer();
 
$response = $sigmie->newQuery('users')
->properties($props)
->matchAll()
->get();

Properties are required for complex queries — they let Sigmie parse field names and route queries correctly.

Boolean queries

A boolean query has four clause types, each handling matches differently:

Clause Behavior Affects score
must() Must match Yes
mustNot() Must not match No
should() Should match (OR) Yes
filter() Must match No
$sigmie->newQuery('movies')
->properties($props)
->bool(function (Boolean $bool) {
$bool->must()->match('title', 'matrix');
$bool->filter()->range('year', ['>' => 1990]);
$bool->should()->term('genre', 'sci-fi');
$bool->mustNot()->term('rating', 'R');
})
->get();

Must — AND

Every clause inside must() must match:

$sigmie->newQuery('products')
->bool(function (Boolean $bool) {
$bool->must()->term('is_active', true);
$bool->must()->range('stock', ['>' => 0]);
});

SQL equivalent:

SELECT * FROM products WHERE is_active = TRUE AND stock > 0;

Must Not — NOT

Documents matching any mustNot() clause are excluded:

$sigmie->newQuery('products')
->bool(function (Boolean $bool) {
$bool->mustNot()->term('is_active', false);
$bool->mustNot()->term('stock', 0);
});

Should — OR

At least one should() clause must match. Multiple clauses are OR’d:

$sigmie->newQuery('movies')
->bool(function (Boolean $bool) {
$bool->should()->term('category', 'fantasy');
$bool->should()->term('category', 'musical');
});

Filter — AND without scoring

Same logic as must(), but doesn’t influence _score. Filter queries are cached by Elasticsearch — use them whenever scoring doesn’t matter:

$sigmie->newQuery('movies')
->bool(function (Boolean $bool) {
$bool->filter()->term('is_active', true);
});

Standalone queries

Outside a boolean wrapper, query types can be called directly on the builder:

// Instead of wrapping in bool:
$sigmie->newQuery('movies')
->bool(function (Boolean $bool) {
$bool->filter()->term('active', true);
});
 
// Call directly:
$sigmie->newQuery('movies')->term('active', true);

Query types

Match all / match none

$query->matchAll();
$query->matchNone();

Term and terms

term() finds an exact value — best for keyword, bool, integer, etc:

$query->term('active', true);
$query->term('user_id', 13);

terms() matches any of several values:

$query->terms('category', ['horror', 'action']);

Note: term() against an analyzed text field usually doesn’t work — the field is tokenized. Add a .keyword sub-field if you need exact matching:

$props->text('category')->keyword();
// then:
$query->term('category.keyword', 'drama');

Match

Analyzed query — best for text fields:

$query->match('name', 'mickey');

Multi-match

Match across multiple fields:

$query->multiMatch(['name', 'username'], 'mickey');

Range

Filter numeric and date ranges:

$query->range('count', ['>=' => 233]);
$query->range('price', ['>=' => 30, '<=' => 130]);

Operators: >=, >, <=, <.

Exists

Document has any value for the field:

$query->exists('director');

Ids

Match by document _id:

$query->ids(['dkKwMe4UBAUb2dMteRe2', 'wd6Me4UBAUb2dMJT']);

Regex, wildcard, prefix, fuzzy

$query->regex('category', '(horror|action)');
$query->wildcard('name', 'john*');
$query->prefix('name', 'john');
$query->fuzzy('name', 'john');

Parsing a filter string

For ad-hoc queries built from human input, parse() accepts the same syntax as Filter Parser:

$sigmie->newQuery('movies')
->properties($props)
->parse('name:"John Doe" AND age<21')
->get();

Custom scoring

Script score

Replace or multiply the score with a custom Painless script:

$sigmie->newQuery('movies')
->properties($props)
->matchAll()
->scriptScore(
source: "Math.log(2 + doc['popularity'].value)",
boostMode: 'replace',
)
->get();

Function score

$sigmie->newQuery('movies')
->properties($props)
->functionScore()
->get();

Boosting

Boost a query’s contribution to _score:

$query->matchAll(boost: 5);

Aggregations and facets

Add facets the same way as in newSearch():

$response = $sigmie->newQuery('products')
->properties($props)
->matchAll()
->facets('category')
->get();
 
$facets = $response->json('aggregations');

For raw aggregations, see Aggregations.

Sorting

Call sort() or sortString() before the query method (matchAll, bool, parse, etc.). Each call replaces the previous sort — put all fields in a single string.

$query->sortString('name:asc created_at:desc');
$query->sort([['year' => 'desc'], ['_score' => 'desc']]);

_score:asc is not allowed.

Note: Sorting on text fields requires a .keyword sub-field. Add one with $props->text('name')->keyword()->makeSortable().

See Sort Parser for full syntax.

Pagination

from and size are on the Search instance returned after the query method:

$response = $sigmie->newQuery('movies')
->properties($props)
->sortString('title:asc')
->matchAll()
->from(0)
->size(20)
->get();

Reading responses

$response = $sigmie->newQuery('movies')
->properties($props)
->matchAll()
->get();
 
$response->json(); // full response
$response->json('hits.hits'); // hits array
$response->json('hits.total.value'); // total count

Debugging

getDSL() returns the underlying Elasticsearch JSON:

$dsl = $query->getDSL();

Performance

  • Use filter() instead of must() when scoring doesn’t matter. Filter queries are cached.
  • Prefer term() over match() for exact matches.
  • Limit size() to what you need.
  • Use retrieve() (on newSearch()) to drop unused fields from the response.
$sigmie->newQuery('products')
->bool(function (Boolean $bool) {
$bool->filter()->term('status', 'active'); // cached, no scoring
$bool->must()->match('title', $searchTerm); // scored
})
->size(10)
->get();