Skip to content

Splunk → SonQL

A translation guide for Splunk users. Every SonQL command name below was checked against the shipped ANTLR grammars — Splv1Lexer.g4 / Splv1Parser.g4 (the search1 engine) and SplLexer.g4 / SplParser.g4 (the search2 engine) — not carried over from a document. Where a command’s behavior is noted, it was measured against a live instance in Searching logs with SonQL.

The short version. Most of the language is the same words. The traps worth knowing on day one: head → limit, table → fields, sort takes a by clause and sorts descending by default (Splunk ascends), AND binds tighter than OR (Splunk’s a b OR c is a AND (b OR c); SonQL’s is (a AND b) OR c — parenthesise every OR), and SonQL has two engines that disagree on a handful of commands.

SplunkSonQLnote
stats count by servicestats count() as count by servicecount(), avg(), sum(), min(), max(), dc() work; an unknown function errors (no match function) rather than returning an empty column. Alias the count. Splunk names the column count; SonQL names it count(), so a migrated sort — | sort by count desc, which is what sort -count becomes — sorts on a column that does not exist and returns no rows, with no error on search2, the default engine (measured 26 and 30 Sep 2026). Splunk’s sort -count itself does not parse on either engine, so it fails loudly
stats count min(_time) as first max(_time) as last BY hoststats count() as count, min(_time) as first, max(_time) as last by hostseveral aggregations are comma-separated, and as and by are lowercase — on both engines, Splunk’s space-separated list and its uppercase AS / BY each fail to parse (“unexpected symbol [‘min’]”). The same holds for eventstats, chart, timechart and streamstats. Measured 28 Oct 2026
eval duration = end - starteval duration = end - start
where status = 500where status = 500
rex field=_raw "…"rex field=_raw "…"
fields host sourcefields host, sourcecomma-separated on search2, the default engine: fields host source fails to parse there (“unexpected symbol … expected one of [EOF]”); search1 accepts either. Also does what Splunk’s table does (below). Measured 26 Sep 2026
fields - _raw / fields + host sourcefields - _raw / fields + host, sourcesearch2 only, list comma-separated: - drops the fields, + keeps only them. search1 refuses both forms. Measured 28 Oct 2026
fillnull value=NULL a bfillnull value="NULL" a, bsearch2 only. A text value is quoted and the fields are comma-separated; the value defaults to 0, and with no fields every field is filled. The engine is typed where Splunk is not: a number fills a number column as a number, and text filling a number column turns that column to text. A listed field that does not exist is created holding the value, as in Splunk. Measured 28 Oct 2026
rename src as dstrename src as dst
dedup host, sourcededup host, source
bin _time span=1dbin _time span="1d"spelled bin, same as Splunk, but quote the span — unquoted, 1d is not a value the parser accepts and the query fails to parse
timechart count() span=1dtimechart span="1d" count()two differences, not one. The span goes before the aggregate, and it must be quoted. Splunk’s own order fails to parse
chart avg(x) by hostchart avg(x) by hostsame on both since 14 Aug 2026
eventstats avg(x) by hosteventstats avg(x) by host
transaction host maxspan=1mtransaction host maxspan="1m"works on both engines (15 Aug 2026), but the duration is spelled differently: quoted on search2 (unquoted, 1m fails to parse there) and unquoted on search1 (quoted fails there) — measured 26 Sep 2026. maxevents=5 is bare on both. search2 carries the joined member fields as multivalue columns — the added fields are _event_count/_duration on search2, eventcount/duration_second on search1
lookup t a OUTPUT blookup "t.csv" a output bnames the lookup file, extension and all, quoted ("t" answers “lookup file t does not exist”), and output is lowercase — lookup t a OUTPUT b fails to parse, and there is no outputnew. Runs on both engines since September 2026; where the table has several rows for one key, the default engine takes the first and search1 returns them all as a multivalue field (measured 27 Sep 2026: 81 of 1,222 events enriched on both). See the SonQL reference.
inputlookup t.csv / outputlookup t.csvinputlookup "t.csv"the file name is quoted; unquoted it fails to parse on search2. outputlookup replaces a table’s contents but does not create one — upload the CSV first (Configure → Lookup Tables); against a missing file it answers “lookup file … does not exist” (measured 26 Sep 2026)
makeresultsmakeresults
makemv delim="," fieldmakemv delim="," fieldsearch1 only. On search2 — the default engine — it is refused: “makemv is not supported on search2; use search1, or split the field with eval on search2”. Until 2 Sep 2026 it parsed and was silently dropped; a wrong answer delivered quietly is worse than a refusal. Re-verified 7 Sep 2026
replace "a" with "b" in freplace "a" with "b" in f
convert ctime(x)convert ctime(x)
iplocation ipiplocation ipneeds IP geolocation set up once, under Settings > Advanced Settings > IP Geolocation (a free MaxMind account, or an uploaded file) — Sondar ships no GeoIP data. Until then the fields are empty and the search says where to set it up. lang is quoted (lang="de"). See the SonQL reference
accum xaccum xsearch1 only. On search2 — the default engine — it is refused: “accum is not supported on search2; use search1, or compute the running total with streamstats on search1”. Until 2 Sep 2026 it parsed and silently kept the per-row value. Re-verified 7 Sep 2026
addtotalsaddtotals
streamstats count() by hoststreamstats count() by hostsearch1 only, and by is required there (without it the engine answers “expected one of ‘by’”). On search2 — the default engine — it is refused: “streamstats is not supported on search2; use search1, or aggregate with stats on search2”. Until 2 Sep 2026 the clause was silently dropped. Re-verified 7 Sep 2026
SplunkSonQLnote
head 20limit 20head is not a command — it errors (unknown command [head] … did you mean 'limit'?)
table a bfields a, bthere is no table; separate the fields with commas (search2)
sort -errorssort by errors descsort takes a by; defaults to descending, Splunk ascends. A size goes first: sort 10 by errors desc
tail 20sort by _time desc | limit 20no tail; this returns newest-first
spath input=x path=yjsonpath input="x" path="y"option values quoted on search2 — input=_raw fails to parse there; search1 takes either. Splunk’s spath reads _raw when no input is named; name it (measured 26 Sep 2026)
xpath …xmlpath input="…" path="…"the same quoting as jsonpath
join type=inner host [subsearch]join type="inner" host [search …]the subsearch is a bracketed query. On search2 (the default) the option value is quoted, type="inner" or type="left"; unquoted, type=inner fails to parse there, though search1 takes it. There is no on= keyword on either engine — the join field simply follows the options. Measured 26 Sep 2026; an earlier version of this row said on=host, which parses on neither
append [subsearch]append [search …]
search index=…search index=… or search2 index=…two engines (below); index= is aliased to repo= in a search predicate

Checked against both grammars; none of these Splunk commands exist in SonQL.

SplunkSonQL equivalent
appendcolsnot supported
unionnot supported
mapnot supported
gentimesmakeresults (generates a row, not a time series)
xyseriesnot supported
untablenot supported
deltastreamstats + eval on the previous value
trendlinenot supported
predictml forecast (search2)

search2-only commands (no search1 equivalent)

Section titled “search2-only commands (no search1 equivalent)”

mstats, msearch, logcluster, rate, fit, ml forecast, ml anomalies, ml outliers, unnest, topseries, show tag/metric names, explain, fillnull.

fit runs the built-in log-analysis algorithms — LogReduce for log-pattern clustering, ReservoirSampling, TokenAnalyzer. There is no apply: the algorithms fit on the data in front of them and save no models.


The full detail, with the measurements, is in Searching logs with SonQL. The six worth internalizing before you migrate anything:

  1. A . does not break a token by default. NullPointerException does not match an event containing java.lang.NullPointerException. Add . to the index’s word-breaker list (affects new data), or search *NullPointerException.

  2. sort is descending by default, the reverse of Splunk, and it is silent — a migrated query returns the reverse order, not an error.

  3. top / rare need a count on search2. top 5 service, not top service. With by, the limit is per group: top 3 service by host is each host’s three commonest services. The percent is of all events without by, and of the group’s events that have the field with it — the same on both engines. (Before the September 2026 builds search2 refused the grouped form.) mvexpand works on both engines, and both spellings of mvexpand limit=<n> are accepted — Splunk’s mvexpand <field> limit=<n> and the options-first mvexpand limit=<n> <field>.

  4. mvcombine needs a | fields reduction on search2. The bare form returns no rows against a repo holding several sourcetypes (measured 12 Sep 2026); reduce first — which is Splunk’s own advice — or use stats values(<field>) by <group>, which agrees on both with no caveat. Pin the engine in anything you save, because the global Default Search Engine setting decides it otherwise.

  5. OR binds more loosely than it does in Splunk. Splunk evaluates OR before AND, so a b OR c means a AND (b OR c). SonQL evaluates AND first, on both engines, so the same text means (a AND b) OR c — silently, with no error. Parenthesise: a (b OR c). Measured 27 Sep 2026, on both engines, over one day of _internal:

    index="_internal" sourcetype="json" GET OR POST returns 1,711 events, every one of them _audit; index="_internal" sourcetype="json" (GET OR POST) — Splunk’s meaning of the first — returns 0. (Audit events have their own index, _audit, since the audit log shipped; the trap is the same.)

    The first silently drops the sourcetype filter from the POST half. The converter writes Splunk’s grouping out as parentheses for you; a search you port by hand needs them added.

  6. A value with a hyphen, slash or colon needs no quotes after field=. service=checkout-api, host=web-01, sourcetype=aws:cloudtrail and sourcetype=aws:* read as one value on both engines, as in Splunk, and so do the items of field IN (...). Keep the pieces together: host=web - 01 is refused with a message saying why. A bare full-text term (no field=) is still split at a hyphen, so quote it: "web-01". Measured on r1226, 30 Sep 2026. Before that image the unquoted forms failed to parse, or — host=web-01 — quietly searched for host=web and -1; the converter quotes these values, which works on every version.