Report formulas — calculations, aggregates, and cross-field references
Calculations layered on top of your dataset — counts, averages, date parts, derived columns, bucketed categories. Two tokens carry most of the work: ? for the current card's field, @ for the Insert Report Field picker. The function-type menu does the rest.
By the end of this lesson
- Aggregated reports — counts, sums, averages — built directly in Composer
- Derived columns combining or transforming existing fields
- A clear sense of which formulas belong here vs in the App Builder
You'll need
- A working report — easiest with the one from Lesson 3
- A rough idea of which database platform you're on (MSSQL, PostgreSQL, MySQL) — function syntax varies
Background
Formulas in DashboardFox live in three places. Knowing which tier you're working in is half the battle:
This lesson is tier 3 — what a Composer can do inside their own report, without admin involvement. If the same formula or override keeps showing up across reports, that's the signal to push it up to tier 1 or 2. The decision rule at the end of this section walks you through the call.
Every formula in Composer lives behind the same entry point: the action menu on a field card → Add formula. The dialog that opens has a Data Type dropdown (must match what the formula returns — text, number, date, currency, percent) and a Formula textarea where the SQL goes.
Two tokens carry most of the work in the Formula textarea:
Don't construct alias-qualified references by hand. Auto-generated aliases like svc_8066 or chnl_5967 are easy to mistype, and Composer's Insert Report Field knows the exact format for your database. Whenever you need to reference a different field, type @ and let the picker do it.
Composer vs App Builder — the ? rule.
The ? token is a Composer-only convenience. Every Composer formula is attached to a specific card, so "this card's field" is unambiguous. App Builder's formula field (the SQL Name on a Report Tree field, covered in Module 3's formula fields lesson) has no card context — the formula is the field — so ? doesn't resolve there. If you plan to promote a Composer formula up to the app later, build it with explicit [alias].[field] references from Insert Report Field instead of ?. The copy-paste to App Builder is then clean.
The function-type menu
Typing @ in the Formula box (or clicking the function icon) opens a dropdown organized into six categories. The picker fills in syntax for your specific database, so you don't have to remember MSSQL's DATENAME vs Postgres's TO_CHAR:
- Aggregate —
COUNT,SUM,AVG,MIN,MAX,STDEV. The most-used category by a wide margin. - String —
UPPER,LOWER,TRIM,SUBSTRING,CONCAT, length helpers. - DateTime — month/day/year/hour extraction, current date, date arithmetic.
- Math —
ROUND,CEILING,FLOOR,ABS,POWER,SQRT. - Other —
CASE WHEN,COALESCE,NULLIF, conditional helpers. - Insert Report Field — the cross-field picker described above.
The picker is enough for most of what you'll write. For the deeper recipes — divide-by-zero protection, integer-to-decimal casting, NULL-safe CONCAT, per-DB syntax differences — see the SQL formula reference at the end of the module.
One last rule that catches a lot of new Composers: the Data Type dropdown must match what the formula returns. COUNT(?) returns a number → Data Type must be num. DATENAME(month, ?) returns text → Data Type must be cha. Mismatches sometimes work in preview and break later — sorting goes alphabetic on a numeric field, aggregations silently disappear from text-typed numbers. Set the Data Type when you save the formula, not after.
Where does this formula belong?
A two-question test before you write any formula:
- Is this just a display change? Date shown as date-only, number shown as currency, percent symbol. → Tier 1, data-type override. Fast path: Module 3 for the App-Builder side that affects every report; this lesson's step 1 for the Composer-only version that affects just this report.
- Will more than one report or one Composer need this same formula? Yes → Tier 2, App Builder formula field (Module 3). No → Tier 3, the Composer formula you're about to write. Rule of thumb: 1–2 reports stay in Composer; 3+ get promoted to the app.
Promotion costs five minutes; recurring duplication costs hours. When in doubt, talk to your App Builder.
Stuck on a specific formula or unsure which token to use? Email team@dashboardfox.com. Real human, same business day.
Do it
-
The simplest formula isn't a formula — just change the Data Type
Before reaching for any SQL function, know that the most common Composer "formula" is no formula at all. Open the action menu on a date column → Add formula. Leave the
?in the Formula box exactly as it is. Just change the Data Type dropdown:dat→doz— keep the timestamp, but skip the browser time-zone conversion so every user sees the same wall-clock time.dat→dpo— date-part only, browser-local. "What calendar day was this, in my time zone?"dat→dtz— date-part only, no conversion. Everyone sees the same calendar day.
Click Save & Apply. The same date now renders the way you want, no SQL written.
The same shortcut works for any numeric column:
- Money column → set Data Type to
cur. Dollar sign renders automatically. - Percentage column (already expressed as a percent, e.g. 75.5 for 75.5%) →
per. Percent symbol renders automatically. - Numeric field that's stored as text and won't aggregate →
num. Sum and Average reappear in the menu.
This is the tier-3 / Composer-only version of the data-type override pattern. The change only affects this report. If every Composer is doing this same override across multiple reports, that's the signal to ask your App Builder to bake it in at the source — covered in Module 3 — formula fields. One change in App Builder eliminates the override in every report forever.
The full Data Type cheat sheet with time-zone behavior for each code lives in the Module 3 lesson.
-
Open Add Formula and meet the function-type menu
On any field card, open the action menu (three dots) and click Add formula. A sidebar opens with two controls:
- Data Type — defaults to the card's existing type. Change it to match whatever your formula will return.
- Formula — a textarea. The
?token is pre-loaded as a starting point because most formulas operate on "this card's field."
Type
@in the Formula box (or click the function icon next to it) and the Select Function Type dropdown opens — Aggregate, String, DateTime, Math, Other, Insert Report Field. Pick a category to see the available functions for your database; pick a function to insert its template into the formula. Functions arrive with placeholders you replace (?for the current field, descriptive snake_case names likedecimal_places,number_of_chars, or[insert_field]for additional arguments — each name describes what to put there).When the formula reads the way you want, click Save & Apply. The sidebar closes, the preview updates, and the card now shows the formula's result instead of the raw field.
-
Aggregates with ? — count, sum, average
The most common Composer formula by a wide margin is an aggregate. Three you'll use constantly:
COUNT(?)— counts non-null rows. Works on any field type. Set Data Type tonum.SUM(?)— totals a numeric column. Data Typenum.AVG(?)— averages a numeric column, ignoring nulls. Data Typenum.
Quick-look-at-row-count trick: add any field (a ticket ID, a primary key, whatever), apply
COUNT(?), and the preview tells you immediately how many rows your report has — 72,000? 495? — before you commit to running View Full Document.The "count by" pattern is where aggregates earn their keep. Drop a categorical field (status, customer, channel) into Selected Data and a counted field with
COUNT(?). The preview rebuilds as a grouped result: counts per category. Want to see "tickets per customer per status"? Add both as categorical columns plus a counted one — the grouping cascades.One important gotcha lives here, covered in step 3.
-
The non-visible-column pattern
Sometimes you want to filter on a column without showing it. Classic case: "tickets per customer, but only open ones" — you need the
statusfield for the criterion, but you don't want a Status column in the output because every row would just say "Open."Open the action menu on the column you want to hide. The Visible checkbox at the top is on by default — uncheck it. The column disappears from the preview but stays in the Selected Data pane (it gets a small "non-visible" marker so you can find it). You can add criteria to it normally; the filtering takes effect, the column just doesn't render.
This pattern is what makes count-by reports work cleanly. Without it, you'd have a redundant "Open" column appearing next to every customer's count. With it, the grid reads naturally.
The non-visible field stays in your data flow, so anything else you do — criteria, sorts, joins — works as normal. The only thing that changes is the rendering.
-
Date extraction with the DateTime menu
Open Add Formula on a date column. Type
@→ DateTime. The submenu lists what's available for your DB — month name, month number, day of week (name and number), hour, quarter, year, day of month, and the current-date functions. Pick "month name" (or your DB's equivalent — MSSQL labels it Datetime to month name) and Composer inserts the template. Set Data Type tocha(it returns text), Save & Apply.The preview now shows month names (May, June, July) instead of the original timestamp.
Sort behavior gotcha. Sorting by a month-name column sorts alphabetically — Apr, Aug, Dec, Feb… — not chronologically. The fix is to add the month-number formula (returns
num) on a second card, sort by that one instead, and make it non-visible (step 3 pattern) so the output still shows just month names. You get chronological order with readable labels.The same trick generalizes: any time you need to display a derived label but sort by a different derived value, build both and use non-visible to hide the sort key.
-
Cross-field references with @ — Insert Report Field
Up to this point every formula has been about one field. Real reports need to combine fields: subtract two dates, divide one number by another, concatenate first + last name. That's where the
@token earns its keep.In the Formula box, type
@. The dropdown opens. Scroll to Insert Report Field at the bottom, or click it directly. A field picker appears listing every field in your report. Pick the one you want to reference. Composer inserts the alias-qualified reference —[svc_8066].[ticket_open_time]on MSSQL,"svc_8066"."ticket_open_time"on Postgres — exactly as your DB expects it.Two common patterns:
- Arithmetic between two numbers.
? - [svc_8066].[discount]on a price card subtracts the discount from the price. The same shape works for+,*,/. - String combine.
CONCAT(?, ' ', [svc_8066].[last_name])on a first-name card produces "Jane Doe" as a single column.
For divisions specifically, plan for nulls and zeros — see Block 4 and the SQL formula reference. Integer division also catches new users:
5 / 2on integer columns returns2in most databases, not2.5. The cast-to-decimal recipe is in the reference page. - Arithmetic between two numbers.
-
CASE for bucketing values
When you need to group raw values into broader categories — three grocery-store customers all rolling up to "Grocery", everyone else to "Other" — that's a
CASE WHENexpression.Open Add Formula on the field you want to bucket (the customer column, in this example). The template looks like:
CASE WHEN ? IN ('Shoplite Grocery', 'Genesis Groceries') THEN 'Grocery' ELSE 'Other' END
Set Data Type to
cha(it returns text). Save & Apply. Every row that matched the IN list now shows "Grocery"; everything else shows "Other." Pair withCOUNT(?)on a different card and you have a two-row report: Grocery 495, Other 8200.CASE generalizes to any bucketing. Score ranges → letter grades. Amounts → "low/medium/high." Statuses → "open/closed/other." The pattern stays the same; only the WHEN conditions change.
If you don't know CASE syntax for your DB offhand, ask an AI tool — see Block 4. The function-type menu's Other submenu also has a CASE template you can use as a starting point.
-
When to push a formula up to the App Builder
Composer formulas live on one report — written here, used here, invisible to anyone else. That's the right home for one-off calculations and exploratory work. But if you find yourself writing the same formula across three reports, or you know other Composers will need it, the formula belongs in the app instead.
Talk to your App Builder. The same SQL works in App Builder's SQL Name field — with one adjustment: replace any
?tokens with explicit[alias].[field]references (use Insert Report Field to discover the exact references first, then copy them into the App Builder side). App Builder's formula field has no card context, so?doesn't resolve there. The Module 3 lesson Formula fields covers the full App Builder workflow.Rule of thumb: one or two reports → Composer formula. Three or more, or a formula your team will keep needing → push it to the app. Promotion costs five minutes; recurring duplication costs hours.
You have a working connection. Below is what to tighten before real users see it. Skip if you're just testing.
Make it real
Production hygiene that compounds. Apply these from your first formula and you'll dodge most of the recurring frustrations.
Hardcode while building, flip to prompt at the end
Same rule as the criteria-prompts lesson applies here too. Dynamic prompts force a save-then-test cycle that's painful when iterating on formulas. Hardcode every prompt to a real value while you build, save fast and often, swap back to Prompt for Value right before the final save.
The non-visible-column pattern is the power move
Non-visible fields show up in nearly every advanced Composer report. The mental model: display columns and working columns are two different things. Display columns are what your audience sees. Working columns are what the SQL needs to filter, sort, or group correctly. The Visible checkbox is how you keep working columns in the data flow while keeping the rendered report clean.
Once you start using it, you'll find yourself using it everywhere — sort keys, criteria fields, intermediate calculations.
Always use Insert Report Field for cross-field references
Auto-generated aliases like svc_8066 and chnl_5967 look guessable but they're not — a single character off and the formula fails. Don't transcribe them by eye. Type @ in the Formula box, scroll to Insert Report Field, pick the field, let Composer drop the reference in. This is faster than typing and always correct for your DB's quoting style.
AI is good at SQL syntax — tell it your DB
For anything more complex than a function-menu template — divide-by-zero protection, multi-level CASE, integer-division casting — your AI tool of choice can write the formula in seconds. The trick is telling it which database and version you're on ("Microsoft SQL Server 2019", "PostgreSQL 14") in the first line of the prompt. Without that, AI tends to guess generic ANSI SQL that may or may not run on your platform.
Paste any error back to AI for a fix. The iteration loop is fast — you're reviewing SQL, not writing it.
Reused formulas belong in the app, not in every report
If the same formula appears in three or more reports, it's time to promote it to App Builder. The Composer formula gets shorter (just reference the new field by name), every other report gets the formula for free, and changes happen in one place instead of N. The Module 3 formula-fields lesson covers the App Builder workflow; remember to replace ? with explicit [alias].[field] references before pasting.
The recipes you outgrow this lesson with
Composer's function-type menu handles most one-line formulas. The patterns that recur in real reporting — safe division (avoid divide-by-zero), forcing decimal output on integer division, NULL-safe concatenation, percentage calculations, per-DB syntax variants — live in the SQL formula reference. Treat it as a recipe book; come back to it whenever you hit a formula problem that's the same shape someone has solved before.
If you're stuck
The classic stumbles, in roughly the order they show up.
My formula errored after the picker filled it in — "column does not exist"
The picker drops a function template into the Formula box 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. If you click Save with a placeholder still in the formula, the database treats that name as a column reference and errors. The error message names the exact placeholder: column "decimal_places" does not exist. Look at your formula, find the snake_case word from the error, and replace it. Integer placeholders take literal numbers (e.g. 2). [insert_*] placeholders take a field reference — type @ on the placeholder and pick Insert Report Field. Text placeholders like 'matching_result' take the literal text you want, keeping the single quotes.
My count or sum is wrong — the numbers look way too high
Most likely: you have a sort order on a non-visible column. Composer's SQL pulls that column into the GROUP BY because the sort needs it, which means your aggregate is grouped by an invisible field — so each row counts a smaller slice than you intended. Fix: open the hidden card, action menu, turn the sort off. The aggregate re-groups correctly. This is one of the trickier bugs in Composer because nothing about the rendered grid tells you the sort is the culprit.
Aggregations are missing from the menu — Average and Sum aren't options
The Data Type on the source field is set to character (cha). DashboardFox doesn't offer numeric aggregations on text columns. Open the action menu → Add formula, change Data Type to num, save. Aggregation options reappear immediately. If the field genuinely contains numbers stored as text (a common database design choice), the fix is in App Builder's formula-fields — register the field with a CAST and the right Data Type.
My month-name column sorts in the wrong order
Sorting on a text column sorts alphabetically. Apr, Aug, Dec, Feb, Jan, Jul…. The fix from step 4: add a second formula card for month number, set its Data Type to num, sort by that, and uncheck Visible on it. Output shows month names; SQL sorts by the underlying number.
My cross-field formula errors with "field not found" or similar
You typed an [alias].[field] reference by hand and got it wrong. Auto-generated aliases like svc_8066 are easy to mistype. Don't transcribe — type @ in the Formula box, scroll to Insert Report Field, and let Composer drop the correct reference in. Replace any hand-typed references with picker output and the error usually clears.
My division formula errors out on some rows
Divide-by-zero. A row has a zero (or null) in the divisor. The fix is to wrap the divisor in NULLIF(...) so zero becomes null and the division returns null instead of erroring — for example, ? / NULLIF([alias].[divisor], 0). The SQL formula reference has the full safe-divide recipe per DB.
My division returns 0 (or whole numbers) instead of decimals
Integer division. When both operands are integers, most databases return an integer result — 5 / 2 gives 2, not 2.5. Fix: cast one operand to a decimal/numeric type before dividing. Syntax varies by DB; see the SQL formula reference for the per-DB recipe. Add a ROUND(..., 2) around the result if you want clean two-decimal display.
I wrote a Composer formula and now want to put it in App Builder, but it doesn't work there
App Builder's SQL Name field doesn't understand ? — there's no current-card context in the App Builder. Replace every ? in your Composer formula with the explicit [alias].[field] reference (use Insert Report Field in Composer to find it), then paste into App Builder. The rest of the SQL is identical. The formula-fields lesson covers the full promotion workflow.
I keep changing the same data type on every single report
Every Composer using this app is doing the same override — that's a tier-1 problem masquerading as a tier-3 task. Ask your App Builder to set the Data Type on the source field in the app itself (covered in Module 3 — formula fields). After app reload, the override applies everywhere automatically; nobody has to open Add Formula for it again. Same logic for currency and percent rendering — bake it into the app once, save every Composer the per-report ritual.
None of these match my situation
Email team@dashboardfox.com with the formula you're trying, your DB platform, and what's happening vs what you expected. Screenshot of the Formula box helps. Same business day reply.
