Datarails Data Query
Query Finance OS tables — fetch records by filter, sample rows, or page through
data. The filter API supports both value-list and advanced (comparison / range /
text / null / date-range) operators, so most questions can be answered with a
single get_data_by_* call.
Workflow
Step 1: Verify Authentication
If any tool call fails with an authentication or connection error, guide the user to connect via the Connectors UI ("+" → Connectors → Datarails → Connect).
Step 2: Resolve the table and its fields
list_data_models to find the table by name or alias. Prefer the alias path
when the table has an alias: list_aliased_fields(<alias>) gives friendly field
names and you query with get_data_by_alias. Otherwise get_fields_by_id(<id>)
gives numeric field ids and you query with get_data_by_id (select and
filters are field-id based). Always select only the columns you need — raw
tables can be hundreds of columns wide in some orgs.
Alias coverage is per field, not per table. A table having an alias does not mean its fields are aliased — real orgs often expose only a handful of aliased fields (e.g. ~5 of ~185 on a mapped financials table), and the load-bearing fields (
amount,scenario, account groups, dates) are frequently not among them. Treat the alias/by-id choice per field:get_fields_by_id(<id>)returns every field with its numericidand itsalias(empty if none). Address a field by alias (via the*_by_aliastools) when it has one, else by numericid(via the*_by_idtools). By-id always works — never abandon the query because the aliased set is thin.
Async fetch — aggregations and distinct values run as start → poll.
start_aggregation_by_id/_by_aliasandstart_distinct_values_by_id/_by_aliastake the same arguments as the retired blocking calls (dimensions/metrics/filters; table id + field id, or alias + field alias) and return immediately with{"status": "pending", "handle": {...}}. Echo thathandleback verbatim to the matchingget_aggregation_result_by_*/get_distinct_values_result_by_*tool: a{"status": "running", "retry_after_seconds": N}response means poll again with the same handle after ~N seconds (≈5s) — it is not an error, and large jobs may take several polls; when ready, the result arrives in the familiar shape (for distinct values, passlimitto the result tool). An expired/unknown-handle error means restart with thestart_*tool. Transitional fallback: if thestart_*tools aren't available on the connector (older server), the blocking twinsget_aggregated_data_by_*/get_distinct_values_by_*still work with the same arguments.
Step 3: Pick the right tool
- Sample / quick look →
get_data_by_alias/get_data_by_idwith a smalllimit(e.g. 20) and no filters. - Filtered records →
get_data_by_alias/get_data_by_idwithfilters(≤500 rows/page; useoffsetto page). - Totals / grouped →
start_aggregation_by_alias/start_aggregation_by_id(same dimensions/metrics/filters arguments; no row cap) → poll the matchingget_aggregation_result_by_alias/get_aggregation_result_by_idwith the returnedhandleuntil ready (async-fetch pattern). Use this when you want a sum/count rather than individual rows.
Step 4: Execute and Present
Format results as a readable table. Highlight any notable patterns. If a result
is empty, say so plainly (it may be a too-narrow filter, not missing data). If a
response carries "truncated": true, the returned rows are an incomplete
prefix — never present the prefix as complete, and never sum it (summing a
truncated prefix silently under-counts). For a grand total, read the top-level
totals field beside the rows ({"data": [...], "totals": {...}}) — it is
computed across all groups, not just the returned prefix, so truncation does
not affect it. It combines the per-group results rather than re-scanning the
rows, so it is exact only where the aggregation is decomposable: SUM, COUNT,
MIN and MAX. It is wrong for AVG (unweighted mean of group averages —
recompute as SUM total ÷ COUNT total from two calls, since a field may be
aggregated at most once per request), COUNT_UNIQUE (sum of per-group
distinct counts) and UNIQUE_VALUES (cross-group de-duplication unverified)
— for a distinct count use the distinct-values tools instead.
totals is absent on dimension-less aggregations (the single row IS the total)
and may be absent on responses cached before the rollout. Narrow the query per
the guidance (more filters / fewer columns / lower limit+offset paging) and
re-fetch only when the rows themselves are needed beyond the cap — with one
exception: a truncated response without totals cannot answer a
grand-total request from its prefix. Re-run the aggregation once — a fresh run
may miss the stale entry and return totals. If it still carries none, narrow
or chunk by dimension until complete and sum those rows; never total the prefix.
Arguments
| Argument | Description |
|---|---|
<table or alias> |
Required — the table id or alias to query |
[filter] |
Filter expression (see syntax below) |
--sample |
Fetch a small unfiltered page (default 20 rows) |
--limit N |
Limit results (max 500 per page; use offset to page further) |
Filter syntax
filters is a list of per-field objects. Address the field by alias (name)
for the by-alias tools, or by numeric field id (field_id) for the by-id
tools. Two forms:
Value list — match any of the listed values (set membership / IN):
{"name": "payment_status", "values": ["Paid", "Pending"]}
Set "is_excluded": true to exclude the listed values (NOT IN).
Advanced — a condition tree for comparisons, ranges, text matching, and null:
{"name": "amount", "values": {"type": "advanced", "val": [
{"condition": "gte", "value": "1000"},
{"condition": "lt", "value": "5000", "operator": "and"}
]}}
Each val entry is {condition, value, operator?}:
- condition:
equals,dn_equals(does not equal),contains,dn_contains,bw(begins with),ew(ends with),gt,gte,lt,lte,in,range(exclusive between),total_range(inclusive between),is null. - value: a string for scalar conditions; a list of strings for
in; a two-item[from, to]list forrange/total_range. Numbers and dates are passed as strings (dates as epoch seconds, e.g."1750000000"); the backend casts per field. Foris null, setvalueto"". - operator: how this condition chains with the previous one —
and(default) ororto start an alternative branch.
is_excluded applies to value lists only, not advanced filters.
Common patterns (all natively supported now)
- Numeric range: advanced
total_rangeon the amount field, e.g.{"condition": "total_range", "value": ["1000", "5000"]}. - Comparison: advanced
gt/gte/lt/lte(e.g. amount over 100000). - Substring match: advanced
contains/bw/ew(e.g. account name contains "adj"). No need to pre-fetch distinct values. - Null check: advanced
is null(withvalue: ""). - Date range: advanced
total_rangeon the date field with epoch-second strings — date filters are accepted (no longer rejected as epoch ints). You can still add the date as an aggregation dimension and filter client-side if you prefer.
Example Interactions
(Illustrative — financials and the field names below stand in for whatever
alias and field aliases Step 2 discovered in your org; your org's names and
values will differ.)
User: "/dr-query financials --sample"
📋 Sample: financials (20 rows)
| account_code | amount | reporting_date | department |
|--------------|-----------|----------------|------------|
| 4000-100 | 12,500.00 | 2024-01-15 | Sales |
| 5100-200 | -3,200.00 | 2024-01-14 | Operations |
...
User: "/dr-query financials department = 'Sales'"
get_data_by_alias(alias="financials", select=["account_code","amount","department"],
filters=[{"name": "department", "values": ["Sales"]}], limit=100)
User: "/dr-query financials amount > 100000"
get_data_by_alias(alias="financials", select=["account_code","amount","department"],
filters=[{"name": "amount", "values": {"type": "advanced",
"val": [{"condition": "gt", "value": "100000"}]}}], limit=100)
Limits
| Method | Max Rows | Use Case |
|---|---|---|
get_data_by_alias / get_data_by_id |
500/page (use offset) |
Row-level records, samples, filtered queries |
start_aggregation_by_alias / start_aggregation_by_id → poll get_aggregation_result_by_* |
none | Totals, grouped breakdowns (no row cap) |
Tips
- Start with a small unfiltered page to learn the data shape and field names.
- If you need a total rather than rows, use the aggregate tools — no row cap.
- If you need more than a few hundred rows of raw data, that's usually a signal you want aggregation, not extraction.
Related Skills
/dr-tables— discover tables / fields and aggregate totals (no row cap)./dr-anomalies— find issues to investigate (computes findings client-side from baseline aggregates)./dr-profile— field-level statistics.