Sitelet https://maple.dev/docs/reference/sql/
Skip to content
Maple Docs
Open app
Browse the docs
On this page

SQL reference

Query your telemetry with ClickHouse SQL: the tables and columns you can read, the required macros, result shapes for each chart type, and limits.

SQL widgets on dashboards, raw-SQL alert rules, and the MCP run_sql tool all run ClickHouse SQL against the same tables Maple’s own pages read. This page lists what you can query and the rules a query has to follow.

A first query

SELECT toStartOfInterval(Timestamp, INTERVAL $__interval_s SECOND) AS bucket,
       count() AS logs
FROM logs
WHERE $__orgFilter AND $__timeFilter(Timestamp)
GROUP BY bucket
ORDER BY bucket

Every query must contain $__orgFilter. It expands to your organization’s filter, and a query without it is rejected before it runs. Access is also enforced by the credentials the query runs with, so the macro is a correctness check rather than your only protection.

Macros

MacroExpands to
$__orgFilterYour organization filter. Required.
$__timeFilter(col)col >= <start> AND col <= <end> for the selected time range
$__timeGroup(col)toStartOfInterval(col, INTERVAL <bucket> SECOND)
$__startTimeThe range start, as a DateTime
$__endTimeThe range end, as a DateTime
$__interval_sThe bucket width in seconds, an integer of at least 1

The argument to $__timeFilter and $__timeGroup must be a plain column name. Any other $__name is an error. When a widget has no explicit granularity, buckets target about 30 points with a 5-minute minimum.

On dashboards, variables are written $name or ${name} and are substituted as quoted string literals. A variable set to All becomes a comma-separated list, so use it with IN:

WHERE $__orgFilter AND $__timeFilter(Timestamp) AND ServiceName IN ($service)

Tables

The main tables are below. Run describe_warehouse_tables from the MCP server for the full list with every column and its type.

TableOne row perTime column
tracesSpanTimestamp
service_overview_spansEntry-point span (server, consumer or root)Timestamp
logsLog recordTimestamp
error_eventsException occurrenceTimestamp
metrics_sumCounter data pointTimeUnix
metrics_gaugeGauge data pointTimeUnix
metrics_histogramHistogram data pointTimeUnix
metrics_exponential_histogramExponential histogram data pointTimeUnix
session_replaysBrowser sessionStartTime
session_eventsSession timeline eventTimestamp
product_eventsProduct eventTimestamp
service_usageService and hourHour
attribute_keys_hourlyAttribute key and hourHour

traces

ColumnTypeNotes
TimestampDateTime64(9)Span start
TraceId, SpanId, ParentSpanIdString
ServiceName, SpanNameLowCardinality(String)
SpanKindLowCardinality(String)Internal, Server, Client, Producer, Consumer
DurationUInt64Nanoseconds. Divide by 1e6 for milliseconds.
StatusCodeLowCardinality(String)Ok, Error, Unset (Title Case)
StatusMessageString
SpanAttributes, ResourceAttributesMap(LowCardinality(String), String)Read with SpanAttributes['http.route']
EventsTimestamp, EventsName, EventsAttributesArraysSpan events, index-aligned
SampleRateFloat641.0 when unsampled. Multiply counts by it to estimate throughput.
IsEntryPointUInt81 for server, consumer and root spans

For per-service request rate, error rate and latency, query service_overview_spans instead. It has the same core columns, holds only entry-point spans, and is much smaller, but it has no attribute maps.

logs

ColumnTypeNotes
TimestampDateTime64(9)
TimestampTimeDateTimeSecond precision
ServiceNameLowCardinality(String)
SeverityTextLowCardinality(String)Casing varies by SDK
SeverityNumberUInt81-4 trace, 5-8 debug, 9-12 info, 13-16 warn, 17-20 error, 21-24 fatal
BodyString
TraceId, SpanIdString
LogAttributes, ResourceAttributes, ScopeAttributesMap(LowCardinality(String), String)

Filter severity on SeverityNumber rather than SeverityText: some SDKs send Error and others ERROR. SeverityNumber BETWEEN 17 AND 20 matches every error.

Metrics tables

All four metric tables share ServiceName, MetricName, MetricDescription, MetricUnit, Attributes (a map of data-point attributes), ResourceAttributes, StartTimeUnix and TimeUnix. Then:

  • metrics_sum and metrics_gauge carry Value Float64. metrics_sum adds IsMonotonic and AggregationTemporality (1 is delta, 2 is cumulative). Delta rows already hold the per-interval increase, so sum(Value) per bucket is exact; cumulative rows need a rate.
  • metrics_histogram carries Count, Sum, Min, Max, BucketCounts and ExplicitBounds.
  • metrics_exponential_histogram carries Count, Sum, Scale, ZeroCount and the positive and negative bucket arrays.

Things that trip people up

  • Missing map keys read as '', not NULL. Test with mapContains(SpanAttributes, 'key') or SpanAttributes['key'] != ''.
  • Status and span kind are Title Case. StatusCode = 'Error' matches; 'ERROR' does not.
  • Hourly rollups use Hour, not Timestamp. Snap your range with toStartOfHour, or a window shorter than an hour returns nothing.
  • Rollup columns are aggregate states. Read SimpleAggregateFunction columns with sum(...) and AggregateFunction columns with the matching …Merge(...).
  • Filter on the sort key first. traces is sorted by service and span name, so adding ServiceName = '…' makes a query dramatically cheaper.

Result shapes for charts

DisplayReturn
Line, area, barA time column (name it bucket) plus one numeric column per series
StatOne row with a numeric column named value
Pie, funnel, horizontal barA name column and a value column
Heatmapx, y and value columns
HistogramOne value column, one row per observation
TableAny columns

For time series, every non-time column becomes a series named after the column, and non-numeric columns are dropped. Rows are not split by a string column, so to draw several lines, return one column per line, for example with countIf:

SELECT $__timeGroup(Timestamp) AS bucket,
       countIf(StatusCode = 'Error') AS errors,
       count() AS requests
FROM service_overview_spans
WHERE $__orgFilter AND $__timeFilter(Timestamp)
GROUP BY bucket
ORDER BY bucket

What is allowed

A query is a single SELECT or WITH statement. A trailing ; is fine, and a trailing FORMAT clause is dropped. Rejected:

  • More than one statement.
  • Any write or DDL keyword: INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, RENAME, ATTACH, DETACH, CREATE, GRANT, REVOKE, OPTIMIZE, SYSTEM, KILL, and INTO OUTFILE.
  • A SETTINGS clause.
  • Table functions that reach outside Maple, such as url, file, s3, remote, mysql, postgresql and dictionary.

A rejected query returns 400 with one of these codes: MissingOrgFilter, InvalidMacro, UnresolvedMacro, DisallowedStatement, DisallowedFunction, MultipleStatements, ResourceLimit.

Limits

LimitValue
Query text32,768 characters
Rows returned1,000. More is an error, not a silent cut: aggregate or add a LIMIT.
Result size5 MB of JSON, 64,000 characters per cell
Execution time10 seconds for dashboards and run_sql, 5 seconds for alerts
Raw-SQL alert rulesMust use $__timeFilter(…)

Your query is wrapped in an outer LIMIT, so WITH TOTALS, LIMIT BY and WITH FILL in the outermost query are discarded.