Telemetry
Browse docs
Discussion TopicsUpdated July 28, 2026Reviewed by the Telemetry editorial and product teams5 min read

Use this doc with your coding agent

Open a focused prompt pack for Claude Code, Codex, Cursor, or another coding agent, then adapt it to the workflow covered here.

On this page
  1. Start with a bounded read
  2. Create complete time buckets
  3. Conditional counts and safe rates
  4. Percentiles and distributions
  5. CTEs make definitions reviewable
  6. Joins need an explicit grain
  7. Window functions
  8. Nested fields and identifiers
  9. Supported patterns and pinned limits
  10. Query review checklist

DataFusion SQL Reference for Telemetry

Telemetry queries structured event tables with Apache DataFusion SQL. Telemetry's public recipes are currently planned and executed with Apache DataFusion 45.2.0 before publication. This reference describes the patterns exercised by that pinned test suite.

Field names and types still come from your event contract. Adapt every example to the table, units, statuses, identity rules, and time semantics that your application actually uses.

Start with a bounded read

Operational queries should usually start with a UTC time filter and return only fields needed by the question:

SELECT
  route_template,
  status_code,
  latency_ms,
  timestamp_utc
FROM api_requests
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
ORDER BY timestamp_utc DESC
LIMIT 100;

timestamp_utc is the server-managed query timestamp. A source-provided timestamp field is normalized during ingestion and should not replace the generated query column in recipes.

Use a LIMIT while inspecting raw rows. For a complete export, use the asynchronous Query API instead of removing every safeguard from an interactive query.

Create complete time buckets

date_trunc creates a stable grain for a trend:

SELECT
  date_trunc('hour', timestamp_utc) AS hour,
  COUNT(*) AS requests
FROM api_requests
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY date_trunc('hour', timestamp_utc)
ORDER BY hour;

The newest hour or day may still be filling. Exclude that bucket from an alert when partial volume would make the result misleading. Use the same time zone, bucket size, and completeness rule in every comparison.

Conditional counts and safe rates

Conditional CASE expressions calculate several outcomes from the same grouped rows:

SELECT
  route_template,
  COUNT(*) AS requests,
  SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END) AS errors,
  100.0 * SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END)
    / NULLIF(COUNT(*), 0) AS error_rate_pct
FROM api_requests
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route_template
HAVING COUNT(*) >= 20
ORDER BY error_rate_pct DESC;

NULLIF protects division from a zero denominator. Multiplying by 100.0 keeps percentage arithmetic from becoming integer division. A HAVING minimum prevents one failure in a quiet group from outranking a busy route with meaningful impact.

Percentiles and distributions

Use approx_percentile_cont(latency_ms, 0.95) for an efficient p95 estimate:

SELECT
  route_template,
  approx_percentile_cont(latency_ms, 0.50) AS p50_ms,
  approx_percentile_cont(latency_ms, 0.95) AS p95_ms,
  approx_percentile_cont(latency_ms, 0.99) AS p99_ms
FROM api_requests
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route_template;

Compare p50 with p95 or p99. A similar increase across the distribution suggests a generally slower workflow; a larger tail change points to a subset of unusually slow operations. Percentiles are estimates, so avoid presenting insignificant decimal precision.

For a histogram-like result, use CASE to assign a numeric value to explicit buckets. Keep bucket labels and ordering columns separate so "1000+" does not sort before "250–499".

CTEs make definitions reviewable

Common table expressions separate a business definition from the final aggregation:

WITH account_activity AS (
  SELECT
    account_id,
    MIN(CASE WHEN event_name = 'signup_completed' THEN timestamp_utc END)
      AS signed_up_at,
    MIN(CASE WHEN event_name = 'activation_completed' THEN timestamp_utc END)
      AS activated_at
  FROM product_events
  WHERE timestamp_utc >= now() - INTERVAL '30 days'
  GROUP BY account_id
)
SELECT
  COUNT(*) AS signed_up_accounts,
  SUM(CASE WHEN activated_at IS NOT NULL THEN 1 ELSE 0 END)
    AS activated_accounts
FROM account_activity
WHERE signed_up_at IS NOT NULL;

Inspect an intermediate CTE by temporarily selecting from it. This is often the fastest way to catch duplicated identities, unexpected nulls, or a milestone definition that includes the wrong rows.

Joins need an explicit grain

Before joining event tables, state what one row represents on each side. A many-to-many join can multiply counts while still returning valid SQL.

Pre-aggregate to one row per account, request, job, webhook delivery, or billing period before joining when that is the unit of analysis. Use stable identifiers created for correlation, and never join on a display label merely because it looks unique.

After a join, compare:

  • row count before and after
  • distinct identifier count before and after
  • unmatched rows on both sides
  • totals against a fixture with a known result

Window functions

Window functions retain row or bucket detail while comparing adjacent values:

WITH daily AS (
  SELECT
    date_trunc('day', timestamp_utc) AS day,
    COUNT(*) AS completed_jobs
  FROM job_events
  WHERE status = 'completed'
    AND timestamp_utc >= now() - INTERVAL '30 days'
  GROUP BY date_trunc('day', timestamp_utc)
)
SELECT
  day,
  completed_jobs,
  LAG(completed_jobs) OVER (ORDER BY day) AS previous_day_jobs,
  AVG(completed_jobs) OVER (
    ORDER BY day
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS rolling_7_bucket_average
FROM daily
ORDER BY day;

LAG exposes a previous value. A framed AVG creates a rolling baseline. Early rows have incomplete windows; decide whether to show them, suppress them, or label them before creating an alert.

Nested fields and identifiers

Telemetry exposes nested JSON through dotted field paths. If a dotted path needs quoting in the current table schema, use a double-quoted identifier such as "data.tool.name". Read Querying nested JSON and inspect the table schema before copying a nested-field query.

Use snake_case field names and avoid reserved or ambiguous words when designing new events. If an existing field requires quoting, quote it consistently rather than creating two spellings of the same concept.

Supported patterns and pinned limits

The tested recipe suite exercises SELECT, CTEs, joins, CASE, common aggregates, date_trunc, intervals, approximate percentiles, LAG, framed windows, NULLIF, COALESCE, ordering, grouping, and limits.

DataFusion is not PostgreSQL, MySQL, BigQuery, or Snowflake. Similar-looking functions can have different names or signatures. The pinned planner used by Telemetry's recipe audit does not accept every aggregate modifier or date helper found in those systems. Prefer syntax demonstrated in this reference and the tested SQL recipe library, then run a small query before adapting an example from another dialect.

Query review checklist

Before saving a query or using it in an alert:

  1. Confirm the table, columns, types, units, and UTC time range.
  2. Define the actor or workflow identifier and verify the row grain.
  3. Decide how retries, duplicates, late events, nulls, and incomplete buckets behave.
  4. Protect ratios with a denominator check and a minimum meaningful volume.
  5. Inspect intermediate CTEs and joined row counts.
  6. Test synthetic success, failure, retry, duplicate, and boundary cases.
  7. Record the definition, owner, threshold, and expected response beside the result.

Every recipe includes a schema, copyable SQL, deterministic synthetic output, a visualization, interpretation notes, edge cases, dashboard suggestions, and alert guidance. Read the SQL testing methodology, then start with API reliability, product analytics, data quality, or infrastructure.

Related product capability

Run read-only DataFusion SQL over structured-event tables and reuse the result.

Ownership and technical references

The Telemetry editorial team owns this explanation; the product team reviews behavior, examples, and boundaries.

Review the editorial standard