TL;DR
- Query Context: Full-text search that calculates relevance score
- Filter Context: Only checks conditions without score, cached for speed
- match/match_phrase: Used for full-text search (text fields)
- term/terms/range: Used for exact value search (keyword fields)
- bool: Combines queries with must/should/must_not/filter
Target Audience: Developers who understand Elasticsearch basics Prerequisites: Core Components, JSON basics
Learn how to write various search queries using Elasticsearch’s Query DSL (Domain Specific Language).
Basic Query Structure#
GET /products/_search
{
"query": {
// Search conditions
},
"from": 0, // Pagination offset
"size": 10, // Number of documents to return
"sort": [ // Sorting
{ "price": "asc" }
],
"_source": ["name", "price"] // Fields to return
}Query Context vs Filter Context#
Elasticsearch queries execute in two contexts.
| Aspect | Query Context | Filter Context |
|---|---|---|
| Question | “How well does it match?” | “Does it match or not?” |
| Score | Calculated (relevance score) | Not calculated |
| Caching | No | Yes |
| Use Case | Full-text search | Exact value filtering |
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "MacBook" } } // Query context (score calculated)
],
"filter": [
{ "term": { "category": "Laptop" } }, // Filter context (cached)
{ "range": { "price": { "lte": 2000000 } } }
]
}
}
}Performance Tip: Put exact value comparisons in
filterto benefit from caching.
Key Points
- Query Context calculates “how well does it match” and assigns a score
- Filter Context only determines “does it match or not” and is cached
- Put exact value filtering (category, status, etc.) in filter to improve performance
Full-Text Queries#
match#
The most common full-text search.
GET /products/_search
{
"query": {
"match": {
"name": "MacBook Pro"
}
}
}“MacBook Pro” → Analyzer → [“macbook”, “pro”] → Search documents containing either (OR)
operator Option#
{
"match": {
"name": {
"query": "MacBook Pro",
"operator": "and" // Must contain both (default: or)
}
}
}minimum_should_match#
{
"match": {
"name": {
"query": "MacBook Pro 14-inch",
"minimum_should_match": "2" // At least 2 of 3 must match
}
}
}match_phrase#
Word order must also match.
GET /products/_search
{
"query": {
"match_phrase": {
"description": "M3 Pro chip"
}
}
}- ✅ “M3 Pro chip included”
- ❌ “M3 chip and Pro display”
slop Option#
{
"match_phrase": {
"description": {
"query": "M3 chip",
"slop": 1 // Allow 1 word between
}
}
}→ “M3 Pro chip” also matches
multi_match#
Search across multiple fields:
GET /products/_search
{
"query": {
"multi_match": {
"query": "MacBook",
"fields": ["name^2", "description"] // name has 2x weight
}
}
}type Options#
| type | Behavior |
|---|---|
best_fields | Use highest scoring field (default) |
most_fields | Sum scores from all fields |
cross_fields | Treat multiple fields as one |
phrase | Search as match_phrase |
Key Points
match: Basic full-text search, OR condition (change to AND with operator)match_phrase: Word order must also match, adjust allowed gap with slopmulti_match: Search multiple fields simultaneously, set field weights with ^
Term Level Queries#
Search for exact values without analysis.
term#
Search for exactly matching values:
GET /products/_search
{
"query": {
"term": {
"category": "Laptop"
}
}
}Warning: Don’t use on
textfields. It compares against analyzed tokens and won’t work as expected.
// ❌ Wrong usage
{ "term": { "name": "MacBook Pro" } } // Won't work if name is text type
// ✅ Correct usage
{ "term": { "name.keyword": "MacBook Pro" } } // Use keyword field
{ "term": { "category": "Laptop" } } // keyword type fieldterms#
Match any of multiple values:
GET /products/_search
{
"query": {
"terms": {
"category": ["Laptop", "Tablet", "Smartphone"]
}
}
}SQL: WHERE category IN ('Laptop', 'Tablet', 'Smartphone')
range#
Range search:
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 1000000, // >=
"lte": 2000000 // <=
}
}
}
}| Operator | Meaning |
|---|---|
gt | > |
gte | >= |
lt | < |
lte | <= |
Date Range#
{
"range": {
"created_at": {
"gte": "2024-01-01",
"lt": "2024-02-01",
"format": "yyyy-MM-dd"
}
}
}Relative dates:
{
"range": {
"created_at": {
"gte": "now-7d/d", // From 7 days ago
"lt": "now/d" // Until today
}
}
}exists#
Documents where field exists:
GET /products/_search
{
"query": {
"exists": {
"field": "discount_price"
}
}
}prefix#
Prefix search:
GET /products/_search
{
"query": {
"prefix": {
"name.keyword": "Mac"
}
}
}wildcard#
Wildcard pattern search:
GET /products/_search
{
"query": {
"wildcard": {
"sku": "PROD-*-2024"
}
}
}Performance Warning: Very slow when
*comes first. Avoid if possible.
fuzzy#
Typo-tolerant search:
GET /products/_search
{
"query": {
"fuzzy": {
"name": {
"value": "Macbok", // Typo
"fuzziness": "AUTO" // Auto edit distance
}
}
}
}| fuzziness | Behavior |
|---|---|
0 | Exact match |
1 | Allow 1 character difference |
2 | Allow 2 character differences |
AUTO | Auto based on length (recommended) |
Key Points
term: Don’t use on text fields! Use on keyword fields or.keywordsubfieldsrange: Range search with gte/gt/lte/lt, use relative expressions likenow-7dfor dateswildcard: Very slow when*comes first, avoid if possiblefuzzy: Typo-tolerant search with fuzziness=AUTO
Bool Query#
Combine multiple queries.
GET /products/_search
{
"query": {
"bool": {
"must": [], // AND - Must match, affects score
"should": [], // OR - Match increases score
"must_not": [], // NOT - Exclude if matches
"filter": [] // AND - Must match, no score, cached
}
}
}Practical Example: Product Search#
“Category is Laptop, price $1,000,000-$2,000,000, ‘MacBook’ keyword, exclude out of stock”
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "MacBook" } }
],
"filter": [
{ "term": { "category": "Laptop" } },
{ "range": { "price": { "gte": 1000000, "lte": 2000000 } } }
],
"must_not": [
{ "term": { "status": "sold_out" } }
]
}
}
}should Behavior#
If must or filter exists, should is optional (only boosts score):
{
"bool": {
"must": [
{ "match": { "name": "MacBook" } }
],
"should": [
{ "term": { "is_promotion": true } } // Promotion items score higher
]
}
}If no must or filter, at least one should must match:
{
"bool": {
"should": [
{ "term": { "category": "Laptop" } },
{ "term": { "category": "Tablet" } }
],
"minimum_should_match": 1
}
}Nested Bool Query#
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "Pro" } }
],
"should": [
{
"bool": {
"must": [
{ "term": { "brand": "apple" } },
{ "range": { "price": { "gte": 2000000 } } }
]
}
},
{
"bool": {
"must": [
{ "term": { "brand": "samsung" } },
{ "range": { "price": { "gte": 1500000 } } }
]
}
}
]
}
}
}Key Points
must: AND condition, affects scorefilter: AND condition, ignores score, cached (excellent performance)should: OR condition, optional if must/filter exists (used for score boosting)must_not: NOT condition, excludes matches- Bool queries can be nested to express complex conditions
Controlling Search Results#
Pagination#
GET /products/_search
{
"from": 0, // Start position (0-based)
"size": 10, // Number to fetch
"query": { "match_all": {} }
}Warning:
from + sizeis limited to 10,000 by default. Usesearch_afterfor large pagination.
Sorting#
GET /products/_search
{
"sort": [
{ "price": "asc" },
{ "created_at": "desc" },
"_score" // Relevance score
],
"query": { "match": { "name": "MacBook" } }
}Field Selection#
GET /products/_search
{
"_source": ["name", "price"], // Fields to include
// or
"_source": {
"includes": ["name", "price"],
"excludes": ["description"]
},
"query": { "match_all": {} }
}Highlighting#
Highlight search terms:
GET /products/_search
{
"query": {
"match": { "description": "M3 chip" }
},
"highlight": {
"fields": {
"description": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}
}
}Response:
{
"hits": [{
"_source": { "description": "M3 Pro chip included..." },
"highlight": {
"description": ["<em>M3</em> Pro <em>chip</em> included..."]
}
}]
}Key Points
from + sizeis limited to 10,000 by default, usesearch_afterfor large pagination- Sort with
sort, limit returned fields with_source- Highlight search terms with
highlight(customize with pre_tags/post_tags)
SQL Comparison#
| SQL | Query DSL |
|---|---|
SELECT * | "_source": true |
WHERE name = 'x' | "term": { "name": "x" } |
WHERE name LIKE '%x%' | "match": { "name": "x" } |
WHERE price > 100 | "range": { "price": { "gt": 100 } } |
WHERE a AND b | "bool": { "must": [a, b] } |
WHERE a OR b | "bool": { "should": [a, b] } |
WHERE NOT a | "bool": { "must_not": [a] } |
ORDER BY price ASC | "sort": [{ "price": "asc" }] |
LIMIT 10 OFFSET 20 | "from": 20, "size": 10 |
Common Mistakes#
1. Using term query on text fields#
// ❌ Wrong - "MacBook Pro" is stored as ["macbook", "pro"]
{ "term": { "name": "MacBook Pro" } }
// ✅ Correct
{ "match": { "name": "MacBook Pro" } }
// or
{ "term": { "name.keyword": "MacBook Pro" } }2. Using term query without filter#
// ⚠️ Unnecessary score calculation
{ "query": { "term": { "category": "Laptop" } } }
// ✅ Better with filter for caching
{
"query": {
"bool": {
"filter": [
{ "term": { "category": "Laptop" } }
]
}
}
}3. Large pagination#
// ❌ Error if exceeds 10000
{ "from": 10000, "size": 10 }
// ✅ Use search_after
{
"size": 10,
"sort": [{ "created_at": "desc" }, { "_id": "asc" }],
"search_after": ["2024-01-15T10:00:00", "abc123"]
}Key Points
- Don’t use term query on text fields → use match or
.keyword- Put exact value filtering inside filter to benefit from caching
- Use
search_afterfor pagination exceeding 10,000 items
Next Steps#
| Goal | Recommended Document |
|---|---|
| Improve search quality | Search Relevance |
| Data analysis | Aggregations |
| Hands-on practice | Basic Examples |