If your database has an existing table-valued function (TVF) that accepts parameters (e.g. a date range), you can call it dynamically from a Cube model using FILTER_PARAMS. The function executes with the exact range the user selects in the dashboard — no static table scan required.
How it works
Write the cube’s sql as a subquery that:
- Uses
FILTER_PARAMStwice — once to extract thefromboundary, once for thetoboundary — using a lambda that returns just one value each time. - Passes those values as parameters to your function via
CROSS JOIN LATERAL(PostgreSQL) or the equivalent for your database.
cubes:
- name: my_tvf_cube
sql: |
SELECT t.*
FROM (
SELECT
{FILTER_PARAMS.my_tvf_cube.event_time.filter(
lambda x, y: f"{x}"
)}::timestamp AS from_time,
{FILTER_PARAMS.my_tvf_cube.event_time.filter(
lambda x, y: f"{y}"
)}::timestamp AS to_time
) params
CROSS JOIN LATERAL public.my_function(params.from_time, params.to_time) t
dimensions:
- name: event_time
sql: event_time
type: time
# ... other columns returned by your function
The lambda x, y: f"{x}" / lambda x, y: f"{y}" callbacks tell Cube to extract just the start or end boundary of the filter — rather than generating a full col >= x AND col <= y condition.
Supported databases
| Database | Lateral join syntax |
|---|---|
| PostgreSQL | CROSS JOIN LATERAL my_func(from_time, to_time) t |
| Snowflake | TABLE(my_func(from_time, to_time)) in the FROM clause |
| BigQuery | CROSS JOIN UNNEST(my_tvf(from_time, to_time)) |
| SQL Server | CROSS APPLY dbo.my_func(from_time, to_time) |
Can a TVF cube join to other models?
Yes. You can define joins on a TVF cube exactly as you would on any other cube. FILTER_PARAMS resolves correctly when the cube is queried alongside a joined model. A common pattern is enriching TVF results with a dimension table (e.g. businesses, users, products):
joins:
- name: businesses
sql: "{CUBE}.business_id = {businesses}.id"
relationship: many_to_one
Two things to keep in mind:
- Only matched rows are returned — dimension table rows with no corresponding TVF results are excluded. If a business has no transactions in the selected date range, it won’t appear in the results.
- The join is defined on the TVF cube — the TVF cube owns the foreign key and declares the
many_to_onerelationship to the dimension table.
Do I need a TVF, or will a stored procedure work?
You need a table-valued function. Stored procedures won’t work here.
The reason is that CROSS JOIN LATERAL (and its equivalents) require something that returns a result set — i.e. something you can SELECT from. TVFs satisfy this because they return a table. Stored procedures do not — they use CALL and have no return value that can sit in a FROM clause.
This applies across all supported databases:
| Database | Stored procedure | Table-valued function |
|---|---|---|
| PostgreSQL | CREATE PROCEDURE + CALL — not queryable |
CREATE FUNCTION ... RETURNS TABLE ✓ |
| SQL Server | CREATE PROCEDURE + EXEC — not queryable |
CREATE FUNCTION ... RETURNS TABLE ✓ |
| Snowflake | CREATE PROCEDURE + CALL — not queryable |
CREATE FUNCTION ... RETURNS TABLE ✓ |
If you’re currently using a stored procedure, convert it to a table-valued function first. In PostgreSQL, that means replacing CREATE PROCEDURE with CREATE FUNCTION ... RETURNS TABLE (...).
Limitations
- Time ranges only — the
x/ycallback only populates forinDateRangefilters. Other filter types (equals, contains, etc.) don’t provide two boundary values. - No filter = likely error — if the user doesn’t apply a date range, both values are
null, which will typically cause a database error or return no rows. Enforce a default date range in your component to prevent this. - No pre-aggregation support — pre-aggregations cannot accelerate queries built on TVFs.