Joins across signal types in four lines. A gentle, copy-paste-ready introduction.

HelixQL looks intimidating until you write your first query and realize it reads like a sentence. Let us build one together, one line at a time.

Start with a source

Every query begins by naming a signal — metrics, logs, or traces. That is your table. From there you pipe it through transformations, left to right, like a Unix shell.

traces
| where service == "checkout"
| stats p95 = percentile(duration, 95) by route
| sort p95 desc

Read it out loud

"Take traces, keep the ones from checkout, compute the 95th-percentile duration per route, and sort by the slowest." That is the whole query, and the syntax maps one-to-one to the sentence.

Now join across signals

The moment HelixQL earns its keep is when you join two signal types on a shared key:

traces
| where duration > 2s
| join (logs | where level == "error") on trace_id

Where to go next

You now know sources, filters, aggregations, and joins — enough to answer most real questions. The rest of the language is variations on these four ideas. Open the query editor and change a value; the fastest way to learn HelixQL is to break a working query and fix it.