Skip to content

sct lexical

Keyword search over the SNOMED CT SQLite database using FTS5 full-text search.

When to use: you know what words to search for. sct lexical "heart attack" returns concepts containing those words. For meaning-based search (when exact words don't match), use sct semantic.


Usage

sct lexical <QUERY|-> [--db <FILE>] [--hierarchy <NAME>] [--limit <N>] [--format text|json|yaml]

Options

Argument / Flag Default Description
<QUERY> (required) Search query. FTS5 syntax: "exact phrase", prefix*, term AND term, etc. Pass - to read one complete query per line from stdin.
--db <FILE> discovered (see Path resolution) SQLite database produced by sct sqlite.
--hierarchy <NAME> (all) Restrict results to a top-level hierarchy (e.g. "Clinical finding").
--status <STATUS> all Restrict by lifecycle status: all, active, or inactive. Only has an effect on a database built with --include-inactive.
--limit <N> 10 Maximum number of results.
-f, --format <FORMAT> text Output format: text, json, or yaml.
--ids off Emit only matching SCTIDs (newline-delimited) for piping into other commands; mutually exclusive with an explicit --format.
--template <TEMPLATE> (built-in) Override the per-concept line template (text output only). See sct refset for the variable list.
--template-fsn-suffix <TEMPLATE> (built-in) Override the FSN suffix template (rendered only when the FSN differs from the preferred term).
--provenance / --no-provenance on for TTY, off otherwise Show/hide release provenance (edition, release date) on this query's output.

An empty search exits 0. Text and --ids output leave stdout empty and write the "No results" hint to stderr; structured formats emit an empty collection.


Examples

sct lexical "heart attack"
sct lexical "myocardial infarct*"
sct lexical "heart attack" --hierarchy "Clinical finding"
sct lexical "beta blocker" --limit 20 --db /data/snomed.db

# Pipe matching SCTIDs straight into a code list
sct lexical "asthma" --ids --limit 50 | sct codelist add asthma.codelist -

# JSON output for scripting
sct lexical "heart attack" --format json

# Search several queries and retain their separate result sets
printf '%s\n' 'heart attack' diabetes | sct lexical - --format json

Batch input

Passing - reads each trimmed, nonblank stdin line as a complete query, with a 64 KiB line limit, up to 10,000 entries and 100,000 retained results across the batch. Unlike code-list input, # has no comment meaning here because it may be intentional query text. --limit and --hierarchy apply independently to every query, and input order and duplicates are preserved.

Text and --ids output flatten result sets in query order; --ids cannot be combined with an explicit --format. JSON and YAML emit one document shaped as { "items": [{ "input": "heart attack", "result": [...] }] }; use a structured format when the caller needs to retain query/result boundaries. Every query completes before stdout is written. SQLite applies --limit while FTS5 streams rank order, avoiding a full-result sort; equal-rank rows retain stable FTS index order for a fixed database.


Inactive concepts

A concept SNOMED International has retired is prefixed with a flag:

⚠ [INACTIVE] 9468002 | Inactive example disorder (Clinical finding)

The prefix is applied by the shared renderer rather than the line template, so --template cannot remove it: a retired code that looks identical to a live one in a result list is the failure this exists to prevent. Structured output carries the same information as an active boolean on each hit.

This only arises on a database built with sct ndjson --include-inactive; the default build contains active concepts only, so no flag ever appears. Use sct lookup on a flagged concept to see why it was retired and what replaces it.

--status narrows results to one lifecycle state, which is how you answer "which of these has been retired?" without dropping to SQL:

# Only retired concepts - e.g. auditing a code list after a release
sct lexical "disorder" --status inactive --limit 5

# Only concepts still current
sct lexical "disorder" --status active

# Retired ids, piped into a code list for review
sct lexical "disorder" --status inactive --ids | sct codelist add review.codelist -

--ids applies the same filter, so a piped set always matches what was shown. The default is all rather than active: a retired concept is flagged, not hidden, because a search that silently omits it is how an old record gets misread as current.

This changes nothing for most databases

A database built without --include-inactive contains no inactive concepts at all, so every --status value returns identical results and no [INACTIVE] marker can ever appear. The flag and the filter only become visible on a database you deliberately built to include retired concepts.

FTS5 query syntax

Syntax Example Matches
Plain terms heart attack Concepts containing both words (implicit phrase)
Exact phrase "heart attack" Concepts containing the exact phrase
Prefix cardio* Concepts with any word starting with "cardio"
Boolean AND heart AND failure Concepts containing both terms
Boolean OR infarct OR infarction Concepts containing either term
Boolean NOT asthma NOT occupational Asthma, excluding occupational variants

Plain text queries (no operators) are automatically quoted to avoid parse errors on special characters. Results are ranked by FTS5 BM25 relevance; equal-rank rows use the FTS index's stable order within a fixed database.


Comparison with sct semantic

sct lexical sct semantic
Basis Keyword matching (FTS5) Meaning / vector similarity
Input SQLite .db Arrow .arrow + Ollama
Speed Instant ~1–2 s (embedding the query)
Finds synonyms Only if indexed Yes
Finds related concepts without shared words No Yes
Works offline Yes Requires local Ollama

Use sct lexical when you know the SNOMED term. Use sct semantic when you're describing a concept in plain language.