Blog

Elasticsearch ES|QL: Now with Views, Subqueries, and Schema-on-Read

Query fields you never mapped, combine indices with different schemas in one pipeline, and reuse query logic as named views. ES|QL's most significant data access expansion yet.

ES|QL adds three capabilities in this release that change how you model and query data: logical views let you define a query once and reference it by name across any dashboard or alert; subqueries in FROM let you combine indices with incompatible schemas in a single pipeline; schema-on-read lets you query fields that were never mapped, against data already indexed, without touching the mapping or reindexing. Alongside these: timezone support graduates to GA, LIMIT BY adds grouped top-N natively, and lookup joins get faster through Lucene structure reuse. Each headline feature has its own deep dive linked below.

ES|QL schema-on-read features overview: unmapped fields, JSON extraction, subqueries and logical views, all marked Tech Preview.

How ES|QL views, subqueries and schema-on-read compose

Views, subqueries, and schema-on-read are designed to layer together. A subquery gives each index its own pipeline. A view wraps that subquery so consumers reference one name. Schema-on-read lets those pipelines access fields that were never in the mapping. The result: one FROM view_name query that combines multiple services, normalizes their schemas, and surfaces fields you forgot to map at ingest time. Each capability has its own deep dive linked in the sections that follow.

Logical views (Tech Preview)

Logical views are virtual indices: query definitions stored at the Elasticsearch cluster level that you reference by name in any FROM clause, exactly like a real index. Define a view once via the _query/view REST API. Every dashboard, alert, and ad-hoc query using the view picks up definition changes automatically. Views support nesting, cross-cluster search, and dedicated RBAC privileges.

PUT _query/view/error_triage
{
  "query": """
    FROM svc-gateway-*
    | WHERE http.response.status_code >= 500
    | KEEP @timestamp, http.response.status_code, url.path, source.ip
  """
}

FROM error_triage
| STATS error_count = COUNT(*) BY url.path
| SORT error_count DESC
Kibana Discover showing a three-line ES|QL query (FROM error_triage, STATS error_count, SORT error_count DESC) with autocomplete labelling error_triage as a logical view. Results show a bar chart and table with error counts by service: 104 for payments, 37 for gateway, 19 for auth.

Read the full deep dive: Elasticsearch ES|QL Views: One Query to Rule Twelve Dashboards

Subqueries in FROM (Tech Preview)

ES|QL subquery pipeline showing Index A and Index B each passing through WHERE, EVAL and KEEP stages before merging into a unified result stream via UNION ALL.

Subqueries give you the composition primitive: combine data from multiple indices, each with different schemas, in a single query. Each branch gets its own WHERE, EVAL, KEEP, and the optimizer pushes filters independently into each index. Results combine with UNION ALL semantics.

FROM
  (FROM svc-gateway-*
   | WHERE http.response.status_code >= 500
   | EVAL service = "gateway",
         error_detail = CONCAT("HTTP ", http.response.status_code::string)
   | KEEP @timestamp, service, error_detail, source.ip),
  (FROM svc-payments-*
   | WHERE transaction.status IN ("failed", "timeout")
   | EVAL service = "payments",
         error_detail = transaction.status
   | KEEP @timestamp, service, error_detail, source.ip),
  (FROM svc-auth-*
   | WHERE event.action == "login" AND event.outcome == "failure"
   | EVAL service = "auth",
         error_detail = CONCAT(event.action, " ", event.outcome)
   | KEEP @timestamp, service, error_detail, source.ip)
| SORT @timestamp DESC
| LIMIT 20

Read the full deep dive: Three Indices Walk Into a FROM Clause: ES|QL Subqueries in Elasticsearch

Schema-on-read: unmapped fields + JSON extraction (Tech Preview)

Unmapped field access lets you query fields that were never declared in the mapping, against data already in the index, without reindexing. SET unmapped_fields="load" is the strategic abstraction: one line, and fields missed at ingest time become queryable from _source. JSON_EXTRACT is the lower-level tool for surgical extraction from raw JSON strings and flattened fields.

SET unmapped_fields="load";
FROM otel-logs-*
| WHERE log.level IN ("error", "warn")
| STATS errors = COUNT(*), latest = MAX(@timestamp)
    BY service.name, resource.cost_center
| SORT errors DESC
ES|QL unmapped fields behaviour: default mode hides fields, nullify mode shows null columns, load mode reads fields from raw source at query time.

Read the full deep dive: Elasticsearch ES|QL "Schema on Read": Your Unmapped Fields Were There All Along

Timezone support (GA)

SET time_zone is now generally available, bringing timezone-aware date/time operations to every ES|QL query. Set it once at the query level and all DATE_TRUNC, date aggregations and timestamp output reflect the local timezone, with no post-processing needed. It accepts any IANA timezone string.

SET time_zone="America/Los_Angeles";
FROM error_triage
| EVAL hour = DATE_TRUNC(1 hour, @timestamp)
| STATS errors = COUNT(*) BY hour, service
| SORT hour DESC

Output timestamps include the timezone offset (e.g., 2026-04-09T11:00:00.000-07:00 instead of ...T18:00:00.000Z). There is no per-function timezone argument; SET time_zone is the mechanism. In Kibana, ES|QL dashboard panels and Discover queries can now produce timezone-aware results by local business hours without any post-processing.

LIMIT BY: grouped top-N (Tech Preview)

"Show me the top 3 error types per service" previously required post-processing because SORT and LIMIT applied globally, giving you the top N overall rather than the top N per group. LIMIT BY solves this natively:

FROM error_triage
| STATS cnt = COUNT(*) BY service, error_detail
| SORT cnt DESC
| LIMIT 3 BY service

Returns the top 3 rows for each distinct service value. The number comes before BY.

ES|QL LIMIT BY command filtering mixed rows into top-N results per group, with outcome callout showing top 3 error types per service without post-processing.

ES|QL FIRST, LAST, EARLIEST and LATEST aggregation functions

FIRST, LAST, EARLIEST, LATEST (GA) return the value associated with the min or max of a sort field:

FROM svc-auth-*
| WHERE event.outcome == "failure"
| STATS first_seen    = FIRST(@timestamp, @timestamp),
        last_seen     = LAST(@timestamp, @timestamp),
        first_user    = EARLIEST(user.name),
        attempts      = COUNT(*)
    BY source.ip
| SORT attempts DESC

FIRST and LAST take two arguments: the value field and the sort field. EARLIEST and LATEST are single-argument aliases that implicitly sort by @timestamp, useful for retrieving which value appeared first or last in a time series.

ES|QL URI_PARTS, USER_AGENT and REGISTERED_DOMAIN commands

URI_PARTS, USER_AGENT, REGISTERED_DOMAIN are three new pipe commands that expand a single field into multiple structured output columns:

FROM svc-gateway-*
| WHERE http.response.status_code >= 400
| URI_PARTS parts = url.full
| STATS errors = COUNT(*) BY parts.domain, parts.path
| SORT errors DESC

FROM svc-auth-*
| USER_AGENT ua = user_agent.original
| STATS cnt = COUNT(*) BY ua.name, ua.version

The syntax is `| COMMAND target = source_field. 
`URI_PARTS` produces `target.domain`, `target.path, target.scheme`, `target.port`, and more. 
`USER_AGENT` produces `target.name`, `target.version`, `target.os.name`, etc.
`REGISTERED_DOMAIN` produces `target.registered_domain`, `target.top_level_domain`, and `target.subdomain`.

ES|QL lookup join optimizations and GA promotions

Several features graduate to generally available, and lookup joins get faster.

  • VALUES (GA): returns all distinct values within a group as a multivalue field.

  • MV_EXPAND (GA): expands multivalue fields into separate rows.

  • FORK (GA): parallel execution branches from the same input, now generally available after previewing since 9.1.

  • SPARKLINE (Tech Preview): inline sparkline visualizations in query results.

  • MV_UNION, MV_DIFFERENCE, MV_INTERSECTS (Tech Preview): set operations on multivalue fields.

  • _size metadata: access document size via METADATA _size in the FROM clause.

Lookup join optimizations

Lookup joins, introduced in 9.1, get two significant optimizations:

ES|QL lookup join optimizations: Lucene structure reuse caches doc values to bypass disk reads and single-keyword joins use a faster execution path to reduce per-row overhead.
  • Lucene structure reuse. Repeated lookups against the same index now cache low-level Lucene structures (doc values and TermsEnum) across queries, avoiding repeated disk reads. This matters most for the typical enrichment pattern: many rows joined against a small lookup index. The improvement comes from reusing these structures rather than rebuilding them per query.

  • Single-keyword join optimization. Lookup joins on a single keyword field (the most common join key type) now use a faster execution path that reduces overhead per joined row.

Together, these make lookup joins more practical for high-volume enrichment workloads.

ES|QL time-series support and approximate queries

This post focuses on the query language and data access features. ES|QL also gains time-series support at GA (including the TS command, PromQL-style functions, and rate/changes/cumulative_sum) and approximate queries for faster exploratory analysis on large datasets, covered in their own posts.

ES|QL roadmap: materialized views, WHERE subqueries, Kibana CRUD UI

ES|QL development continues at pace. On the roadmap:

  • Materialized views: pre-compute once, read instantly. For expensive aggregations where millisecond freshness isn't required, materialized views will trade recency for speed.

  • WHERE subqueries: WHERE field IN (FROM other_index | ...) and other correlated forms, extending the composition model from FROM into filtering.

  • Kibana CRUD UI for views: a "Save as View" experience in Discover, bringing view management out of Dev Tools.

  • Native flattened field support: eliminating the JSON_EXTRACT on _source workaround for the most common schema-on-read use case.

Try it

All headline features are available in Elasticsearch as Tech Preview (views are not yet available in Serverless). Timezone support, FIRST/LAST/EARLIEST/LATEST, VALUES, MV_EXPAND, and FORK are generally available. Try them in Kibana Dev Tools or Discover.

We'd love your feedback. If you hit an issue or have a feature request, file a GitHub issue with the ES|QL label.

ES|QL subqueries, logical views, JSON extraction, unmapped field access, LIMIT BY, SPARKLINE, MV_UNION, MV_DIFFERENCE, and MV_INTERSECTS are Tech Preview features. Tech Preview features are subject to change and are not covered by the support SLA of GA features. The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.

Related Content

Close enough is fast enough: How ES|QL Fast mode makes Kibana dashboards up to 100x faster

Teresa Alvarez Soler

Prompt to dashboard in under a minute, 5x cheaper: AI dashboards and custom Vega-Lite charts in Kibana

Marta Bondyra

15 lines of click tracking code that tell you what search logs can't

Matthew Adams

Using ES|QL COMPLETION + an LLM to write a Chuck Norris fact generator in 5 minutes

Aurélien Foucret

How to instrument your search API with OpenTelemetry and query it with ES|QL

Matthew Adams

Ready to build state of the art search experiences?

Sufficiently advanced search isn’t achieved with the efforts of one. Elasticsearch is powered by data scientists, ML ops, engineers, and many more who are just as passionate about search as you are. Let’s connect and work together to build the magical search experience that will get you the results you want.

Try it yourself