Subchapter 37.9
references/cloudwatch-omni/query/views.mdMarkdown11 KBView on GitHub
Views in CloudWatch Omni are named SQL queries that can be referenced as tables. They allow reusing common query logic without repeating it — use FROM view.<name> in any query to inline the view’s stored SQL.
Reference a view by its name with the view. prefix in the FROM clause:
SELECT `@timestamp`, `@record`
FROM view.my_error_logs
WHERE `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()
ORDER BY `@timestamp` DESCViews behave like inline subqueries:
@timestamp bounds — no need to specify a time range inside the view definitionSELECT e.`@timestamp`, e.`@record`
FROM view.error_logs AS e
INNER JOIN view.slow_traces AS s
ON e.traceId = s.traceId
WHERE e.`@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()
AND s.`@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()A view can reference other views in its definition:
-- If view.base_errors is defined as (projecting severityNumber so it is exposed
-- as a bare top-level column the composed view can narrow on):
-- SELECT `@timestamp`, `@record`, severityNumber FROM logs.default WHERE TRY_CAST(severityNumber AS BIGINT) >= 17
-- Then another view can build on it (a composed view may only narrow on bare
-- top-level columns — see "Views expose only bare top-level columns" in section 4):
-- SELECT * FROM view.base_errors WHERE TRY_CAST(severityNumber AS BIGINT) >= 21
-- And queries can reference the composed view:
SELECT COUNT(*) FROM view.fatal_errors
WHERE `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()Verify field names before filtering or grouping. An unknown or mistyped top-level column resolves to NULL silently — no error is raised — so a wrong field name matches zero rows. Confirm the real field names for your data with
EXPLAIN (ANALYZE_FIELDS), as described in sql-logs-traces.md, before referencing them in a view.
Creates a new named view.
| Parameter | Required | Description |
|---|---|---|
name | Yes | View name. Must match ^view\.[a-z0-9][a-z0-9_-]{0,250}$ and be 6–256 characters total. It must start with the lowercase prefix view., the first character after view. must be alphanumeric ([a-z0-9]), and the remaining characters use the lowercase set [a-z0-9_-] (letters, digits, hyphen, underscore) — a second . is NOT allowed (only the view. prefix contains a dot), and uppercase is NOT allowed. A user view must not start with view.aws. (reserved for AWS-managed views) |
definition | Yes | A single SQL SELECT statement |
description | No | Human-readable description of the view’s purpose |
tags | No | Tag map. CreateView is the only view operation that takes tags |
clientToken | No | Idempotency token, 1–64 chars |
Example:
CreateView
name: "view.error_logs_last_hour"
definition: "SELECT `@timestamp`, `@record` FROM logs.default WHERE TRY_CAST(severityNumber AS BIGINT) >= 17"
description: "All error-level log entries"Updates an existing view’s definition and/or description.
| Parameter | Required | Description |
|---|---|---|
name | Yes | The view name to update |
definition | No | New SQL SELECT statement |
description | No | New description |
A new definition replaces the old one in place, changing results for everything that
reads the view. Draft the change and confirm with the user before calling UpdateView.
Deletes a view by name.
| Parameter | Required | Description |
|---|---|---|
name | Yes | The view name to delete |
Confirm before deleting. Deleting a view is a destructive write and cannot be
undone — any saved query or dashboard panel that reads FROM view.<name> breaks once
it is gone. Name the specific view and confirm with the user before calling DeleteView;
do not delete on inference.
Lists views in the account, optionally filtered by type (USER | MANAGED); paginated with maxResults (1–100) and nextToken. Each ViewSummary carries name, type (USER | MANAGED), description, createdAt and updatedAt. View definitions are not included — call GetView for the SQL. There is no scope field.
Gets a single view by name. Returns name, type (USER | MANAGED), description, definition, createdAt, updatedAt, and arn. There is no scope field.
View names must match ^view\.[a-z0-9][a-z0-9_-]{0,250}$ and be 6–256 characters total:
view.view.aws. — that prefix is reserved for managed/curated views provided by AWSview. must be alphanumeric ([a-z0-9])[a-z0-9_-] (letters, digits, hyphen, underscore) — a second . is NOT allowed (only the view. prefix contains a dot), and uppercase is NOT allowedAlthough a hyphen is legal in a view name, an unquoted hyphenated identifier in a FROM clause parses as subtraction (view.checkout-slow reads as view.checkout minus slow), so a hyphenated name must be quoted or escaped when referenced — which is why the examples below use underscores.
Examples of valid names:
view.my_error_logsview.checkout_slow_requestsview.team_dashboard_metricsExamples of invalid names:
view.checkout.slow-requests (a second dot is not allowed)view.Checkout (uppercase not allowed)logs.default, traces.default, default. Metrics are not SQL and cannot be wrapped in a view — they are queried with PromQL (see promql-metrics.md)FROM view.other_view@timestamp filter — it inherits from the outer queryA view’s schema is limited to bare top-level columns — @timestamp, @record, and simple columns such as severityNumber, name, or durationNano (optionally wrapped in CAST/functions or given an alias). A view definition cannot reference a nested map or struct field with bracket access — resource['attributes'][...], status['code'], attributes[...] — and a view does not expose those fields to the outer query either. Both fail to bind at planning:
Failed to bind view `view.<name>`: Schema error: No field named resource.
Valid fields are "@timestamp", "@record", "severityNumber".This holds even when the nested field is aliased in the view’s SELECT (status['code'] AS status_code still fails to bind), and even when the outer query — not the view — does the bracket access over the view. The same bracket access works fine in a direct query against logs.default / traces.default / default; the restriction is specific to view definitions and to querying through a view. So to filter or group by a nested field (a service name, an environment, a span status code), query the base table directly rather than wrapping it in a view.
view.aws.* prefix is reserved (cannot create or modify)A view definition may only reference bare top-level columns (see Views expose only bare top-level columns), so encapsulate the part of the filter that uses one — here the error-severity threshold:
CreateView
name: "view.severe_logs"
definition: "SELECT `@timestamp`, `@record` FROM logs.default WHERE TRY_CAST(severityNumber AS BIGINT) >= 17"Query the view for a running count of severe logs:
SELECT COUNT(*) AS error_count
FROM view.severe_logs
WHERE `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()To group or filter those errors by a nested field — a service name or an environment — query the base table directly, because a view cannot reference a nested map field. Group severe-log counts by environment (COALESCE covers both attribute keys, deployment.environment.name and deployment.environment):
SELECT COUNT(*) AS error_count,
COALESCE(resource['attributes']['deployment.environment.name'],
resource['attributes']['deployment.environment']) AS environment
FROM logs.default
WHERE TRY_CAST(severityNumber AS BIGINT) >= 17
AND `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()
GROUP BY COALESCE(resource['attributes']['deployment.environment.name'],
resource['attributes']['deployment.environment'])
ORDER BY error_count DESCA log error (severityNumber) and a span error (status['code']) live on different planes but share the unified default table. The span-error test reads the nested status field, so this cannot be a view (see Views expose only bare top-level columns) — run it as a direct query:
SELECT COUNT(*) AS error_count
FROM default
WHERE (TRY_CAST(severityNumber AS BIGINT) >= 17
OR upper(TRY_CAST(status['code'] AS VARCHAR)) IN ('2', 'ERROR', 'STATUS_CODE_ERROR'))
AND `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()name and durationNano are bare span columns, so this filter and projection can live in a view. The span’s service lives in the nested resource map, which a view cannot expose, so it is not selected here — join or read it in a direct query if you need it:
CreateView
name: "view.slow_spans"
definition: "SELECT `@timestamp`, name, CAST(durationNano AS DOUBLE) / 1e6 AS duration_ms FROM traces.default WHERE durationNano IS NOT NULL AND CAST(durationNano AS DOUBLE) > 2e9"Then alert or dashboard on it:
SELECT `@timestamp`, name, duration_ms
FROM view.slow_spans
WHERE `@timestamp` BETWEEN NOW() - INTERVAL '1 HOUR' AND NOW()
ORDER BY duration_ms DESC
LIMIT 50