Inline Expressions
Define inline SQL expressions and display formatting on dataset metrics and dimensions
Dataset metrics and dataset dimensions can both include an expression field containing a SQL expression. For metrics, the expression transforms source data before aggregation -- useful for unit conversions, conditional logic, and rounding. For dimensions, the expression transforms grouping values at the row level -- useful for date truncation, bucketing, and string manipulation.
In both cases, inline expressions let you define calculations directly on a field without creating a full calculated metric or calculated dimension.
The optional format field (metrics only) controls display formatting -- either as a simple string shorthand (currency, percent, number) or a full object with properties like decimals, currency, and locale. When a user drags the metric onto a card, the format seeds the card's default display settings.
When to Use Inline Expressions vs Calculated Metrics
| Feature | Inline Expression | Calculated Metric |
|---|---|---|
| Where defined | On a dataset metric (datasets[].fields.metrics[]) | In calculatedMetrics section |
| Scope | Single dataset -- can reference fields within the same dataset | Can span multiple datasets |
| Inputs | References fields by name directly in the expression | Named inputs with explicit dataset/field/aggregate mapping |
| Aggregation control | Automatic -- SUM applied by default if the expression is not pre-aggregated | Explicit -- you specify an aggregate per input |
| Use case | Simple transforms (unit conversion, rounding, conditional logic) | Complex KPIs (ratios across datasets, multi-level nesting) |
Expression Syntax
Two authoring styles are supported.
Plain field references:
expression: "amount_cents / 100"Brace notation -- useful when field names contain special characters or for clarity:
expression: "{amount_cents} / 100"Both produce the same result. At compile time, field names are resolved against the dataset's field inventory.
Referencing Other Metrics
Expression metrics can reference other metrics in the same dataset. If you have a metric amount_usd with expression amount_cents / 100, another metric can build on it:
metrics:
- name: amount_usd
expression: "amount_cents / 100"
sourceField: amount_cents
dataType: number
- name: amount_with_tax
expression: "amount_usd * 1.08"
sourceField: amount_cents
dataType: numberSupported SQL Constructs
- Arithmetic:
+,-,*,/ CASE WHEN:CASE WHEN status = 'active' THEN amount ELSE 0 ENDCAST:CAST(amount_cents AS DECIMAL) / 100- Functions:
ROUND(...),COALESCE(...),CONCAT(...),ABS(...),NULLIF(...) EXTRACT:EXTRACT(YEAR FROM order_date)- Aggregate functions:
SUM(...),COUNT(...),AVG(...)-- makes the expression pre-aggregated
Expression syntax follows your database's SQL dialect (PostgreSQL, MySQL, BigQuery, etc.). Use functions supported by your connection type.
Examples
Currency Conversion
metrics:
- name: amount_usd
label: "Amount (USD)"
dataType: number
sourceField: amount_cents
expression: "amount_cents / 100"
format: currencyPercentage Metric
metrics:
- name: conversion_rate
label: "Conversion Rate"
dataType: number
sourceField: conversions
expression: "conversions / NULLIF(visits, 0) * 100"
format: percentConditional Logic
metrics:
- name: active_revenue
label: "Active Revenue"
dataType: number
sourceField: amount
expression: "CASE WHEN status = 'active' THEN amount ELSE 0 END"
format: currencyRounding
metrics:
- name: amount_rounded
label: "Amount (Rounded)"
dataType: number
sourceField: amount
expression: "ROUND(amount, 2)"
format: numberPre-Aggregated Expression
When the expression already contains aggregate functions, Semaphor uses it as-is instead of wrapping it in SUM(...).
metrics:
- name: avg_order_value
label: "Average Order Value"
dataType: number
sourceField: amount
expression: "SUM(amount) / NULLIF(COUNT(DISTINCT order_id), 0)"
format: currencyFormat Specification
The format field controls how a metric's values are displayed. It supports two forms: a string shorthand for common cases, and a full object for fine-grained control.
String Shorthand
For quick setup, pass a format type as a string:
metrics:
- name: amount_usd
expression: "amount_cents / 100"
sourceField: amount_cents
dataType: number
format: currencySupported string values:
| Value | Display Effect | Example Output |
|---|---|---|
currency | Currency symbol (USD), 2 decimal places | $1,234.56 |
percent | Percent suffix, values treated as fractions | 27.5% |
percentage | Alias for percent | 27.5% |
number | Plain number, 2 decimal places | 1,234.56 |
scientific | Scientific notation | 1.23E+3 |
Object Form
For more control, pass a format object with specific properties:
metrics:
- name: revenue_eur
expression: "amount_cents / 100"
sourceField: amount_cents
dataType: number
format:
type: currency
currency: EUR
decimals: 0
locale: de-DE
thousandsSeparator: truemetrics:
- name: margin_pct
expression: "profit / NULLIF(revenue, 0)"
sourceField: profit
dataType: number
format:
type: percent
decimals: 1
percentValueMode: fractionFormat Object Reference
| Property | Type | Applies To | Description |
|---|---|---|---|
type | string | All | number, currency, percent, percentage, scientific, date, string |
decimals | number | Numeric | Fixed decimal places (0-10) |
currency | string | currency | ISO currency code (e.g., USD, EUR, GBP). Defaults to USD |
locale | string | All | BCP 47 locale tag (e.g., en-US, de-DE). Defaults to en-US |
prefix | string | All | Text prepended to the value |
suffix | string | All | Text appended to the value |
thousandsSeparator | boolean | Numeric | Enable grouping separators. Defaults to true |
negativeStyle | string | Numeric | minus (default) or parentheses |
compact | boolean | Numeric | Use compact notation (e.g., 1.2K) |
scale | number | Numeric | Multiply value before display |
percentValueMode | string | percent | fraction (default, multiply by 100) or whole (display as-is) |
When using percentValueMode: fraction, a raw value of 0.275 displays as 27.5%. When using whole, the value 27.5 displays as 27.5%.
Format Precedence
When a user drags a metric with a format onto a card, the format seeds the card's default display settings. The precedence order is:
- User-set formatting (highest priority) -- any formatting the user explicitly configures on the card
- Semantic format -- the
formatvalue from the metric definition - Visual defaults -- Semaphor's built-in defaults
Formats are non-destructive
Format values only apply when a metric is first added to a card and only if the card has no existing format configured. They never overwrite user customizations.
Aggregation Behavior
Non-Aggregated Expressions
When an expression does not contain an aggregate function (SUM, COUNT, AVG, etc.), Semaphor automatically wraps it in SUM(...) when used as a metric. This mirrors the default behavior for regular numeric metrics.
# This expression:
expression: "amount_cents / 100"
# Becomes this in the generated SQL:
# SUM(amount_cents / 100)Pre-Aggregated Expressions
If the expression already contains aggregate functions, Semaphor uses it as-is.
# This expression:
expression: "SUM(amount) / NULLIF(COUNT(DISTINCT order_id), 0)"
# Is used directly in the generated SQL:
# SUM(amount) / NULLIF(COUNT(DISTINCT order_id), 0)Non-Numeric Expressions
For non-numeric data types (string, date), COUNT is applied instead of SUM.
If you want full control over aggregation, include the aggregate function directly in the expression. For example, use AVG(amount) instead of amount (which defaults to SUM(amount)).
Validation Errors
When you save a domain template with inline expressions or metric filters, Semaphor validates them. The following error codes apply to inline expressions:
| Code | Meaning | Common Cause | Fix |
|---|---|---|---|
| E012 | Unknown field reference in metric expression | The expression references a field name that does not exist in the dataset | Check spelling of field names; ensure the referenced field is defined in identifiers, dimensions, or metrics |
| E013 | Invalid expression or metric filter | Circular dependency, unsupported AS aliasing, invalid metric filter, or other syntax issue | Review the expression for cycles (metric A references metric B which references A), remove AS aliases, check metric filter configuration, and verify SQL syntax |
| E014 | Unknown field in metric filter | A metric filter references a field name not in the dataset | Check filter field names against the dataset's field inventory |
| E015 | Unsupported metric filter path | A metric filter references a field across a one-to-many or many-to-many relationship | Use same-dataset fields or fields reachable via many-to-one joins |
| E016 | Unknown field reference in dimension expression | The expression references a field name that does not exist in the dataset | Check spelling; field must be defined in identifiers, dimensions, or metrics |
| E017 | Invalid dimension expression | Expression contains aggregate functions, AS aliases, or syntax errors | Remove aggregates (SUM, COUNT, etc.) and AS aliases; use row-level expressions only |
For a complete list of validation errors, see Validation & Examples.
Restrictions
- AS aliasing --
amount_cents AS usdis not supported. Use the metric'snamefield instead. - Dimension aggregates -- Dimension expressions must be row-level. Aggregate functions (
SUM,COUNT, etc.) are only supported in metric expressions. - Cross-dataset references -- Inline expressions can only reference fields within the same dataset. For cross-dataset calculations, use calculated metrics.
- Braces in string literals -- String values containing
{or}are not supported inside expressions.
Best Practices
- Prefer
sourceFieldfor simple metrics -- If a metric maps directly to a database column with no transformation, usesourceFieldwithoutexpression. Expressions are for when you need to transform the value. - Use
NULLIFto prevent division by zero -- Writeamount / NULLIF(orders, 0)instead ofamount / orders. - Add a
formathint for user convenience -- Format hints save dashboard users from manually configuring number formatting every time they use the metric. - Keep expressions simple -- For complex multi-step calculations involving multiple datasets, use domain-level calculated metrics instead.
- Use descriptive
labelanddescription-- Help dashboard users understand what the transformed metric represents.
Inline Dimension Expressions
Dataset dimensions can also include an expression field for row-level transforms such as date truncation, CASE-WHEN bucketing, and string manipulation. This lets you reshape grouping values directly on a dimension without creating a root-level calculated dimension.
When to Use Inline Dimension Expressions vs Calculated Dimensions
| Feature | Inline Dimension Expression | Calculated Dimension |
|---|---|---|
| Where defined | On a dataset dimension (datasets[].fields.dimensions[]) | In calculatedDimensions section |
| Scope | Single dataset | Can reference fields from any dataset via named inputs |
| Inputs | References fields by name directly | Named inputs with explicit dataset/field mapping |
| Cross-dataset | No | No (but grain mappings enable cross-dataset alignment) |
| Use case | Simple single-column transforms | Complex multi-input transforms, cross-dataset grain alignment |
Expression Syntax
Inline dimension expressions use the same syntax as metric expressions -- plain field references and brace notation. See the Expression Syntax section above for details.
Examples
Date Truncation
dimensions:
- name: order_month
label: "Order Month"
dataType: date
expression: "DATE_TRUNC('month', order_date)"Bucketing with CASE WHEN
dimensions:
- name: status_band
label: "Status Band"
dataType: string
expression: "CASE WHEN status = 'active' THEN 'Active' WHEN status = 'trial' THEN 'Trial' ELSE 'Inactive' END"String Transform
dimensions:
- name: upper_region
label: "Region (Uppercase)"
dataType: string
expression: "UPPER(region)"Concatenation
dimensions:
- name: full_location
label: "Full Location"
dataType: string
expression: "CONCAT(city, ', ', state)"Aggregation Behavior
Dimension expressions are row-level -- they appear in both SELECT and GROUP BY in the generated SQL. They are not wrapped in any aggregate function. This is the opposite of metric expressions, which default to SUM().
# This dimension expression:
expression: "DATE_TRUNC('month', order_date)"
# Appears in the generated SQL as:
# SELECT DATE_TRUNC('month', order_date) ... GROUP BY DATE_TRUNC('month', order_date)Do not use aggregate functions (SUM, COUNT, AVG, etc.) in dimension expressions. Aggregates are only valid in metric expressions. Semaphor returns a validation error (E017) if a dimension expression contains an aggregate.
Validation Errors
| Code | Meaning | Common Cause | Fix |
|---|---|---|---|
| E016 | Unknown field reference | Expression references a field name not in the dataset | Check spelling; field must be defined in identifiers, dimensions, or metrics |
| E017 | Invalid dimension expression | Expression contains aggregate functions, AS aliases, or syntax errors | Remove aggregates (SUM, COUNT, etc.) and AS aliases; use row-level expressions only |
Restrictions
- No aggregate functions -- Dimension expressions are row-level. Use
SUM,COUNT, etc. only in metric expressions. - AS aliasing not supported -- Use the dimension's
namefield instead. - Single-dataset only -- For cross-dataset groupings, use calculated dimensions with grain mappings.
- Braces in string literals -- String values containing
{or}are not supported inside expressions.