SQL formula reference — recipes and per-database syntax
Recipe book and per-database syntax reference for DashboardFox formulas. The lesson that walks you in the door is report-formulas (Composer side) and formula-fields (App Builder side). This page is what to come back to when a formula gets harder than one function call.
By the end of this lesson
- Quick reference for SQL functions by category and database platform
- Reusable recipes for the patterns that recur — safe divide, decimal coercion, percentages, NULL-safe concat, CASE bucketing
Background
Composer's function-type picker (the dropdown that opens when you type @ in any Formula box) covers most of what new users need: aggregates, common string operations, date extraction, math basics. The picker fills in syntax for your specific database, so you don't have to remember MSSQL's LEN vs Postgres's LENGTH vs MySQL's CHAR_LENGTH.
This page picks up where the picker leaves off — the patterns that recur in real reporting but aren't pickable in one click, plus the per-database syntax tables you'll want when you're writing by hand or working with an AI tool.
What's on this page
Recipes
Patterns that solve a recurring problem in 3–5 lines of SQL. Copy, adapt, paste.
- Safe divide (divide-by-zero protection)
- Integer-to-decimal coercion
- Percentage calculation
- NULL-safe CONCAT
- Multi-branch CASE bucketing
Per-DB syntax tables
One quick-lookup table per function category, with MSSQL / PostgreSQL / MySQL syntax side by side.
- Aggregate (COUNT, SUM, AVG, MIN, MAX)
- String (UPPER, LENGTH, TRIM, SUBSTRING, CONCAT, …)
- DateTime (extraction, math, current)
- Math (ROUND, ABS, CEILING, FLOOR, POWER)
- Other (CASE, COALESCE, NULLIF, IS/IF)
Syntax convention used on this page
Examples use ? for the current card's field and [alias].[field] for cross-field references — matching what Composer's Insert Report Field emits on MSSQL. On PostgreSQL it emits "alias"."field" with double quotes; on MySQL it emits backticks. If you're writing a formula for App Builder's SQL Name field instead of Composer: App Builder has no card context, so replace every ? with the explicit alias-qualified reference (discover it in Composer first via Insert Report Field, then paste into App Builder). See formula-fields for the full App Builder workflow.
Skim, don't read — three ways to use this page
Find what you need, copy the pattern, paste into your Formula box. Come back when the next formula puzzle shows up.
Stuck on a formula that isn't covered here? Email team@dashboardfox.com with the formula you're trying, your DB platform and version, and what error or wrong result you're seeing. We'll trace it. Same business day.
Do it
-
Recipe — Safe divide (divide-by-zero protection)
The problem. SQL throws a divide-by-zero error the moment your divisor hits 0. One bad row, and the whole report breaks.
The fix. Wrap the divisor in
NULLIF(..., 0)— converts 0 to NULL, and dividing by NULL returns NULL instead of erroring. The reporting row just shows a blank instead of crashing the query.? / NULLIF([alias].[divisor], 0)
Per-DB note. Identical syntax across MSSQL, PostgreSQL, and MySQL —
NULLIFis one of the genuinely portable functions. Set Data Type tonum(it returns a numeric result, possibly NULL).If you'd rather show 0 instead of NULL on the bad rows, wrap the whole thing in COALESCE:
COALESCE(? / NULLIF([alias].[divisor], 0), 0). -
Recipe — Integer-to-decimal coercion
The problem. When both operands of a division are integers, most databases return an integer result.
5 / 2gives2, not2.5. Your "percentage of total" report shows 0% for everything that isn't a clean 100%.The fix. Cast at least one operand to a decimal type before the math. Syntax differs per DB:
Set Data Type to
num. If your displayed numbers need to be tidied, wrap withROUND(..., 2)for two decimal places — see the next recipe for the combined pattern. -
Recipe — Percentage calculation (safe divide + casting + rounding)
The three previous patterns combined. Use this for any "X as a percent of Y" calculation — % of total, completion rate, conversion rate, anything ratio-shaped.
MSSQL / MySQL:
ROUND( CAST(? AS DECIMAL(18,4)) / NULLIF([alias].[total], 0) * 100, 2 )
PostgreSQL:
ROUND( ?::numeric / NULLIF([alias].[total], 0) * 100, 2 )
Set Data Type to
numif you want the percent sign managed at the viz layer, orperif you want DashboardFox to render the%automatically. Theperdata type expects a value already expressed as a percent (75.5, not 0.755) — the* 100at the end of the formula gets you there. -
Recipe — NULL-safe CONCAT (full name from first + last)
The problem. If any input to CONCAT is NULL, some databases return NULL for the whole expression (or behave inconsistently depending on session settings). A customer with no middle name ends up with no name at all.
The fix. Wrap each potentially-null input in
COALESCE(..., '')— converts NULL to empty string before the concatenation.CONCAT( COALESCE(?, ''), ' ', COALESCE([alias].[last_name], '') )
Per-DB note.
CONCATwith multiple arguments works on MSSQL 2012+, PostgreSQL, and MySQL.COALESCEis universal. Set Data Type tocha.For three or more parts (first + middle + last, or street + city + state + zip), keep extending the same pattern. Each piece gets its own COALESCE; literal separators (spaces, commas) sit between as plain strings.
-
Recipe — Multi-branch CASE bucketing
Roll up many raw values into a smaller set of categories. The pattern from report-formulas generalized — match values into groups, with an
ELSEcatch-all for whatever doesn't match.CASE WHEN ? IN ('Shoplite Grocery', 'Genesis Groceries', 'FreshMart') THEN 'Grocery' WHEN ? IN ('Cafe X', 'Coffee Y') THEN 'Cafe' WHEN ? LIKE '%Restaurant%' OR ? LIKE '%Diner%' THEN 'Restaurant' ELSE 'Other' END
Three things worth noting:
- Order matters. The first matching WHEN wins, so put more-specific conditions before catch-alls. A row matching both Cafe and Restaurant would resolve to whichever WHEN comes first.
- Mix operators.
IN,LIKE, comparison operators, and BETWEEN all work inside CASE conditions. Combine withAND/ORas needed. - Numeric buckets work the same. Score ranges → letter grades, ages → age groups, anything where you want to fold a continuum into named bins.
Set Data Type to
cha(the output is text). Identical syntax across MSSQL, PostgreSQL, and MySQL — CASE is one of the most portable SQL constructs. -
Reference — Aggregate functions
Aggregates collapse many rows into one summary value. Used in every "count by," "sum by," or "average by" report. Always set Data Type to
num.Aggregates work cleanly across all three databases. The non-visible-column pattern from report-formulas is the most common way to apply them.
-
Reference — String functions
Operate on text columns. Output is text (
cha) unless noted otherwise.LENGTHreturns a number.The TRIM row deserves a note: MSSQL only added
TRIM(?)in 2017. On MSSQL 2016 or earlier, useLTRIM(RTRIM(?))— works on every MSSQL version including current. If you're not sure what version your DB is on, theLTRIM(RTRIM())form is the safe default. -
Reference — DateTime functions
Date extraction and arithmetic. The function-type picker in Composer fills in the right syntax for your DB, but the table below is what you'll want when writing by hand or working with AI.
Two extraction patterns recur often enough to flag: month name sorts alphabetically (Apr, Aug, Dec…) unless you also bring in the month-number column and sort by that, then mark month-number non-visible. The same trick applies to day-of-week name. See report-formulas for the non-visible-sort-key pattern.
-
Reference — Math functions
Numeric operations. All return
numData Type.Math functions are the most portable category — six of the seven above are identical across all three databases. The integer-division-needs-casting pattern from the recipes section above is the one to remember.
-
Reference — Other (conditional & NULL handling)
Conditional logic and NULL handling. These are the most-asked-about functions after aggregates — they show up in almost every advanced formula.
Two practical notes:
- Prefer COALESCE over ISNULL/IFNULL/NVL. COALESCE is part of the SQL standard and works on every database listed. The vendor-specific aliases (ISNULL on MSSQL, IFNULL on MySQL, NVL on Oracle) all do the same thing but reduce portability. If you ever migrate or write formulas across DBs, COALESCE saves rewrites.
- NULLIF is the divide-by-zero hero. Almost every safe-divide pattern uses it. See the first recipe above.
If you're stuck
Cross-cutting issues that show up regardless of which recipe or function you're using.
I see "column 'X' does not exist" where X is a snake_case word
The picker dropped a placeholder you didn't replace. When you pick a function, Composer fills in the SQL shape with named placeholders — decimal_places, number_of_chars, start_position, number_of_days, [insert_field], [insert_divisor_field], [insert_end_date_field] — that you have to swap for real values before saving. The database treats anything else as a column name and errors when it can't find one. The placeholder name tells you what kind of value to put there: integer for decimal_places, a field reference (via Insert Report Field) for anything starting with insert_, text in quotes for things like 'matching_result'. Look at the formula, find the snake_case word the error names, and replace it.
PostgreSQL: "function round(real, integer) does not exist"
PostgreSQL has ROUND(numeric, integer) but no ROUND(real, integer) or ROUND(double precision, integer). If your field is stored as real or double precision in the database, the two-argument ROUND errors with code 42883. The fix is to cast first: ROUND(CAST(? AS NUMERIC), 2). The current picker's ROUND and ROUND_2_DECIMAL entries on PostgreSQL already wrap with this cast — if you're seeing the error, either the picker is older than this fix or you hand-wrote the formula without the cast.
SAFE_DIVIDE returned 0 when I expected a decimal
Integer division. On MSSQL and PostgreSQL, when both operands of / are integer-typed, the result is integer — 1 / 2 is 0, not 0.5. The fix is to cast the numerator to decimal/numeric: CAST(? AS DECIMAL(18, 4)) / NULLIF([insert_divisor_field], 0) on MSSQL, or CAST(? AS NUMERIC) / NULLIF([insert_divisor_field], 0) on PostgreSQL. The current picker's SAFE_DIVIDE entries on MSSQL and PG already include this cast. MySQL and Oracle do floating-point division natively, so they don't need the cast.
PostgreSQL: DATEDIFF_DAYS on a TIMESTAMP field returns something strange
The unfixed PG expression ([alias].[end_date] - ?) returns an INTERVAL (not an integer) when either operand is a TIMESTAMP. The fix is to cast both sides to DATE: (CAST([insert_end_date_field] AS DATE) - CAST(? AS DATE)). The current picker's DATEDIFF_DAYS entry on PostgreSQL already does this. Oracle has the same issue and the same fix using TRUNC.
MySQL: CONCAT returned NULL even though I had text in both fields
MySQL's CONCAT(NULL, anything) returns NULL — different from MSSQL, PostgreSQL, and Oracle, which treat NULL operands as empty strings. If any field in the concatenation has a NULL value on a row, that row's result is NULL. The fix is to wrap each operand with COALESCE(operand, ''): CONCAT(COALESCE(?, ''), COALESCE([insert_field], '')). The current picker's CONCAT entry on MySQL already does this. See the "NULL-safe CONCAT" recipe above for the multi-part pattern.
AI gave me SQL that errors on my database
Dialect mismatch. DATENAME(month, ?) works on MSSQL but not PostgreSQL; TO_CHAR(?, 'Month') works on PostgreSQL and Oracle but not MSSQL. Tell AI exactly which platform and version ("Microsoft SQL Server 2019", "PostgreSQL 14") in the first line of the prompt. Re-run; the SQL matches your database. The DateTime table above shows the most common per-DB differences.
My Composer formula works but breaks when I paste it into App Builder
App Builder's SQL Name field doesn't understand ? — there's no current-card context, so the token doesn't resolve. Replace every ? in your formula with the explicit [alias].[field] reference. Discover the reference in Composer first via Add Formula → @ → Insert Report Field, copy what it emits, paste into App Builder. The full App Builder workflow is in formula-fields.
MSSQL 2016 says TRIM doesn't exist
TRIM(?) was added to MSSQL in 2017. On 2016 or earlier, use LTRIM(RTRIM(?)) — same result, works on every MSSQL version. If you're not sure what version you're on (or you maintain reports for multiple environments), default to the LTRIM(RTRIM()) form everywhere. It's slightly longer but never breaks.
My CAST won't convert — error about the target type
Target type names differ slightly across databases. DECIMAL(18,4) works on MSSQL and MySQL; PostgreSQL prefers NUMERIC (or the shorthand ?::numeric). If you copy a cast from one DB to another, the most common fix is renaming the target type. The integer-to-decimal recipe above has the per-DB syntax side by side.
None of these match my situation
Email team@dashboardfox.com with the formula you're trying to write, your database platform and version, and what error or wrong result you're seeing. Screenshot of the Formula box helps. Same business day reply.
