diff --git a/docs/pages/guides/recipes.mdx b/docs/pages/guides/recipes.mdx index c7bac95261abd..db4008b7f9399 100644 --- a/docs/pages/guides/recipes.mdx +++ b/docs/pages/guides/recipes.mdx @@ -10,6 +10,7 @@ These recipes will show you the best practices of using Cube. ### Analytics - [Calculating daily, weekly, monthly active users](/guides/recipes/analytics/active-users) +- [Calculating the internal rate of return (XIRR)](/guides/recipes/analytics/xirr) - [Implementing event analytics](/guides/recipes/analytics/event-analytics) - [Implementing funnel analysis](/guides/recipes/analytics/funnels) - [Implementing retention analysis & cohorts](/guides/recipes/analytics/cohort-retention) diff --git a/docs/pages/guides/recipes/analytics/_meta.js b/docs/pages/guides/recipes/analytics/_meta.js index a5642e6b584a8..ab5c197514aed 100644 --- a/docs/pages/guides/recipes/analytics/_meta.js +++ b/docs/pages/guides/recipes/analytics/_meta.js @@ -1,5 +1,6 @@ module.exports = { "active-users": "Daily, Weekly, Monthly Active Users (DAU, WAU, MAU)", + "xirr": "XIRR", "event-analytics": "Implementing event analytics", "cohort-retention": "Implementing retention analysis & cohorts", "funnels": "Implementing Funnel Analysis" diff --git a/docs/pages/guides/recipes/analytics/xirr.mdx b/docs/pages/guides/recipes/analytics/xirr.mdx new file mode 100644 index 0000000000000..cd89ff901d225 --- /dev/null +++ b/docs/pages/guides/recipes/analytics/xirr.mdx @@ -0,0 +1,202 @@ +# Calculating the internal rate of return (XIRR) + +## Use case + +We'd like to calculate the internal rate of return (XIRR) for a schedule of cash +flows that is not necessarily periodic. + +## Data modeling + +XIRR calculation is enabled by the `XIRR` function, implemented in [SQL API][ref-sql-api], +[DAX API][ref-dax-api], and [MDX API][ref-mdx-api]. It means that queries to any of these +APIs can use the this function. + +The `XIRR` function is also implemented in Cube Store, meaning that queries to the SQL API +or the [REST API][ref-rest-api] that hit pre-aggregations can also use this function. +That function would need to be used in a measure that makes use of [multi-stage +calculations][ref-multi-stage-calculations]. + + + +Consequently, queries that don't hit pre-aggregations would fail with the following error: +`function xirr(numeric, date) does not exist`. + + + + + +Multi-stage calculations are powered by Tesseract, the [next-generation data modeling +engine][link-tesseract]. Tesseract is currently in preview. Use the +`CUBEJS_TESSERACT_SQL_PLANNER` environment variable to enable it. + + + +Consider the following data model: + + + +```yaml +cubes: + - name: payments + sql: > + SELECT '2014-01-01'::date AS date, -10000.0 AS payment UNION ALL + SELECT '2014-03-01'::date AS date, 2750.0 AS payment UNION ALL + SELECT '2014-10-30'::date AS date, 4250.0 AS payment UNION ALL + SELECT '2015-02-15'::date AS date, 3250.0 AS payment UNION ALL + SELECT '2015-04-01'::date AS date, 2750.0 AS payment + + dimensions: + - name: date + sql: date + type: time + + - name: payment + sql: payment + type: number + + # Everything below this line is only needed for querying + # pre-aggregations in Cube Store + dimensions: + - name: date__day + sql: "{date.day}" + type: time + + measures: + - name: total_payments + sql: payment + type: sum + + - name: xirr + multi_stage: true + sql: "XIRR({total_payments}, {date__day})" + type: number_agg + add_group_by: + - date__day + + pre_aggregations: + - name: main_xirr + measures: + - total_payments + time_dimension: date + granularity: day +``` + +```javascript +cube(`payments`, { + sql: ` + SELECT '2014-01-01'::date AS date, -10000.0 AS payment UNION ALL + SELECT '2014-03-01'::date AS date, 2750.0 AS payment UNION ALL + SELECT '2014-10-30'::date AS date, 4250.0 AS payment UNION ALL + SELECT '2015-02-15'::date AS date, 3250.0 AS payment UNION ALL + SELECT '2015-04-01'::date AS date, 2750.0 AS payment + `, + + dimensions: { + date: { + sql: `date`, + type: `time` + }, + + payment: { + sql: `payment`, + type: `number` + }, + + // Everything below this line is only needed for querying + // pre-aggregations in Cube Store + date__day: { + sql: `${CUBE.date.day}`, + type: `time` + } + }, + + measures: { + total_payments: { + sql: `payment`, + type: `sum` + }, + + xirr: { + multi_stage: true, + sql: `XIRR(${CUBE.total_payments}, ${CUBE.date__day})`, + type: `number_agg`, + add_group_by: [ + date__day + ] + } + }, + + pre_aggregations: { + main_xirr: { + measures: [ + total_payments + ], + time_dimension: date, + granularity: `day` + } + } +}) +``` + + + +## Query + +### DAX API + +You can use the `XIRR` function in DAX. + +### SQL API + +[Query with post-processing][ref-query-wpp] in the SQL API: + +```sql +SELECT + XIRR(payment, date) AS xirr +FROM ( + SELECT + DATE_TRUNC('DAY', date) AS date, + SUM(payment) AS payment + FROM payments + GROUP BY 1 +) AS payments; +``` + +[Regular query][ref-query-regular] in the SQL API that hits a pre-aggregation in Cube Store: + +```sql +SELECT MEASURE(xirr) AS xirr +FROM payments; +``` + +### REST API + +Regular query in the REST API that hits a pre-aggregation in Cube Store: + +```json +{ + "measures": [ + "payments.xirr" + ] +} +``` + +## Result + +All queries above would yield the same result: + +``` + xirr +-------------------- + 0.3748585976775555 +``` + + +[ref-sql-api]: /product/apis-integrations/sql-api/reference#custom-functions +[ref-dax-api]: /product/apis-integrations/dax-api/reference#financial-functions +[ref-mdx-api]: /product/apis-integrations/mdx-api +[ref-rest-api]: /product/apis-integrations/rest-api +[ref-query-wpp]: /product/apis-integrations/queries#query-with-post-processing +[ref-query-regular]: /product/apis-integrations/queries#regular-query +[link-tesseract]: https://cube.dev/blog/introducing-next-generation-data-modeling-engine +[ref-multi-stage-calculations]: /product/data-modeling/concepts/multi-stage-calculations \ No newline at end of file diff --git a/docs/pages/product/apis-integrations/dax-api/reference.mdx b/docs/pages/product/apis-integrations/dax-api/reference.mdx index ddaf9a219610e..8800ed1df1f0d 100644 --- a/docs/pages/product/apis-integrations/dax-api/reference.mdx +++ b/docs/pages/product/apis-integrations/dax-api/reference.mdx @@ -100,7 +100,15 @@ of the DAX documentation. -No financial functions currently supported. +| Function | Unsupported features | Caveats | +| --- | --- | --- | +| [`XIRR`](https://learn.microsoft.com/en-us/dax/xirr-function-dax) | — | — | + + + +See the [XIRR recipe](/guides/recipes/analytics/xirr) for more details. + + ### INFO functions diff --git a/docs/pages/product/apis-integrations/sql-api/reference.mdx b/docs/pages/product/apis-integrations/sql-api/reference.mdx index 28f7d57747749..aeb9235ac3453 100644 --- a/docs/pages/product/apis-integrations/sql-api/reference.mdx +++ b/docs/pages/product/apis-integrations/sql-api/reference.mdx @@ -150,7 +150,8 @@ SHOW ALL; ## SQL functions and operators SQL API currently implements a subset of functions and operators [supported by -PostgreSQL][link-postgres-funcs]. +PostgreSQL][link-postgres-funcs]. Additionally, it supports a few [custom +functions](#custom-functions). ### Comparison operators @@ -407,12 +408,24 @@ of the PostgreSQL documentation. | `IN` | Returns `TRUE` if a left-side value matches **any** of right-side values | ✅ Yes | ✅ Outer
✅ Inner (selections)
✅ Inner (projections) | | `NOT IN` | Returns `TRUE` if a left-side value matches **none** of right-side values | ✅ Yes | ✅ Outer
✅ Inner (selections)
✅ Inner (projections) | +### Custom functions + +| Function | Description | +| --- | --- | +| `XIRR` | Calculates the [internal rate of return][link-xirr] for a series of cash flows | + + + +See the [XIRR recipe](/guides/recipes/analytics/xirr) for more details. + + + [ref-qpd]: /product/apis-integrations/sql-api/query-format#query-pushdown [ref-qpp]: /product/apis-integrations/sql-api/query-format#query-post-processing [ref-sql-api]: /product/apis-integrations/sql-api [ref-sql-api-aggregate-functions]: /product/apis-integrations/sql-api/query-format#aggregate-functions - [link-postgres-funcs]: https://www.postgresql.org/docs/current/functions.html [link-github-sql-api]: https://github.com/cube-js/cube/issues?q=is%3Aopen+is%3Aissue+label%3Aapi%3Asql -[link-github-new-sql-api-issue]: https://github.com/cube-js/cube/issues/new?assignees=&labels=&projects=&template=sql_api_query_issue.md&title= \ No newline at end of file +[link-github-new-sql-api-issue]: https://github.com/cube-js/cube/issues/new?assignees=&labels=&projects=&template=sql_api_query_issue.md&title= +[link-xirr]: https://support.microsoft.com/en-us/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d \ No newline at end of file diff --git a/docs/pages/reference/data-model/types-and-formats.mdx b/docs/pages/reference/data-model/types-and-formats.mdx index ff6099f573ea9..936e553fa5d4f 100644 --- a/docs/pages/reference/data-model/types-and-formats.mdx +++ b/docs/pages/reference/data-model/types-and-formats.mdx @@ -1,9 +1,3 @@ ---- -redirect_from: - - /types-and-formats - - /schema/reference/types-and-formats ---- - # Types and Formats ## Measure Types @@ -139,7 +133,7 @@ cubes: ### `number` -Type `number` is usually used, when performing +The `number` type is usually used, when performing arithmetic operations on arithmetic operations on measures. [Learn more about Calculated Measures][ref-schema-ref-calc-measures]. @@ -207,10 +201,59 @@ cubes: +### `number_agg` + +The `number_agg` type is used when you need to write a custom aggregate function +in the `sql` parameter that isn't covered by standard measure types like `sum`, +`avg`, `min`, etc. + + + +The `number_agg` type is only available in Tesseract, the [next-generation data modeling +engine][link-tesseract]. Tesseract is currently in preview. Use the +`CUBEJS_TESSERACT_SQL_PLANNER` environment variable to enable it. + + + +Unlike the `number` type which is used for calculations on measures (e.g., +`SUM(revenue) / COUNT(*)`), `number_agg` indicates that the `sql` parameter contains +a direct SQL aggregate function. + +The `sql` parameter is required and must include a custom aggregate function that returns a numeric +value. + + + +```javascript +cube(`orders`, { + // ... + + measures: { + median_price: { + sql: `PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)`, + type: `number_agg` + } + } +}) +``` + +```yaml +cubes: + - name: orders + # ... + + measures: + - name: median_price + sql: "PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)" + type: number_agg +``` + + + ### `count` -Performs a table count, similar to SQL’s `COUNT` function. However, unlike -writing raw SQL, Cube will properly calculate counts even if your query’s +Performs a table count, similar to SQL's `COUNT` function. However, unlike +writing raw SQL, Cube will properly calculate counts even if your query's joins will produce row multiplication. You do not need to include a `sql` parameter for this type. @@ -254,7 +297,7 @@ cubes: ### `count_distinct` -Calculates the number of distinct values in a given field. It makes use of SQL’s +Calculates the number of distinct values in a given field. It makes use of SQL's `COUNT DISTINCT` function. The `sql` parameter is required and must include any valid SQL expression @@ -332,9 +375,9 @@ cubes: ### `sum` -Adds up the values in a given field. It is similar to SQL’s `SUM` function. +Adds up the values in a given field. It is similar to SQL's `SUM` function. However, unlike writing raw SQL, Cube will properly calculate sums even if your -query’s joins will result in row duplication. +query's joins will result in row duplication. The `sql` parameter is required and must include any valid SQL expression of the numeric type (without an aggregate function). @@ -387,9 +430,9 @@ cubes: ### `avg` -Averages the values in a given field. It is similar to SQL’s AVG function. +Averages the values in a given field. It is similar to SQL's AVG function. However, unlike writing raw SQL, Cube will properly calculate averages even if -your query’s joins will result in row duplication. +your query's joins will result in row duplication. The `sql` parameter is required and must include any valid SQL expression of the numeric type (without an aggregate function). @@ -494,7 +537,7 @@ cubes: ## Measure Formats -When creating a **measure** you can explicitly define the format you’d like to +When creating a **measure** you can explicitly define the format you'd like to see as output. ### `percent`