Skip to content

Commit f056be4

Browse files
igorlukaninmarianore-muttdata
authored andcommitted
docs: XIRR calculation (cube-js#9573)
1 parent d5678f3 commit f056be4

File tree

6 files changed

+287
-19
lines changed

6 files changed

+287
-19
lines changed

docs/pages/guides/recipes.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ These recipes will show you the best practices of using Cube.
1010
### Analytics
1111

1212
- [Calculating daily, weekly, monthly active users](/guides/recipes/analytics/active-users)
13+
- [Calculating the internal rate of return (XIRR)](/guides/recipes/analytics/xirr)
1314
- [Implementing event analytics](/guides/recipes/analytics/event-analytics)
1415
- [Implementing funnel analysis](/guides/recipes/analytics/funnels)
1516
- [Implementing retention analysis & cohorts](/guides/recipes/analytics/cohort-retention)

docs/pages/guides/recipes/analytics/_meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
22
"active-users": "Daily, Weekly, Monthly Active Users (DAU, WAU, MAU)",
3+
"xirr": "XIRR",
34
"event-analytics": "Implementing event analytics",
45
"cohort-retention": "Implementing retention analysis & cohorts",
56
"funnels": "Implementing Funnel Analysis"
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Calculating the internal rate of return (XIRR)
2+
3+
## Use case
4+
5+
We'd like to calculate the internal rate of return (XIRR) for a schedule of cash
6+
flows that is not necessarily periodic.
7+
8+
## Data modeling
9+
10+
XIRR calculation is enabled by the `XIRR` function, implemented in [SQL API][ref-sql-api],
11+
[DAX API][ref-dax-api], and [MDX API][ref-mdx-api]. It means that queries to any of these
12+
APIs can use the this function.
13+
14+
The `XIRR` function is also implemented in Cube Store, meaning that queries to the SQL API
15+
or the [REST API][ref-rest-api] that hit pre-aggregations can also use this function.
16+
That function would need to be used in a measure that makes use of [multi-stage
17+
calculations][ref-multi-stage-calculations].
18+
19+
<InfoBox>
20+
21+
Consequently, queries that don't hit pre-aggregations would fail with the following error:
22+
`function xirr(numeric, date) does not exist`.
23+
24+
</InfoBox>
25+
26+
<WarningBox>
27+
28+
Multi-stage calculations are powered by Tesseract, the [next-generation data modeling
29+
engine][link-tesseract]. Tesseract is currently in preview. Use the
30+
`CUBEJS_TESSERACT_SQL_PLANNER` environment variable to enable it.
31+
32+
</WarningBox>
33+
34+
Consider the following data model:
35+
36+
<CodeTabs>
37+
38+
```yaml
39+
cubes:
40+
- name: payments
41+
sql: >
42+
SELECT '2014-01-01'::date AS date, -10000.0 AS payment UNION ALL
43+
SELECT '2014-03-01'::date AS date, 2750.0 AS payment UNION ALL
44+
SELECT '2014-10-30'::date AS date, 4250.0 AS payment UNION ALL
45+
SELECT '2015-02-15'::date AS date, 3250.0 AS payment UNION ALL
46+
SELECT '2015-04-01'::date AS date, 2750.0 AS payment
47+
48+
dimensions:
49+
- name: date
50+
sql: date
51+
type: time
52+
53+
- name: payment
54+
sql: payment
55+
type: number
56+
57+
# Everything below this line is only needed for querying
58+
# pre-aggregations in Cube Store
59+
dimensions:
60+
- name: date__day
61+
sql: "{date.day}"
62+
type: time
63+
64+
measures:
65+
- name: total_payments
66+
sql: payment
67+
type: sum
68+
69+
- name: xirr
70+
multi_stage: true
71+
sql: "XIRR({total_payments}, {date__day})"
72+
type: number_agg
73+
add_group_by:
74+
- date__day
75+
76+
pre_aggregations:
77+
- name: main_xirr
78+
measures:
79+
- total_payments
80+
time_dimension: date
81+
granularity: day
82+
```
83+
84+
```javascript
85+
cube(`payments`, {
86+
sql: `
87+
SELECT '2014-01-01'::date AS date, -10000.0 AS payment UNION ALL
88+
SELECT '2014-03-01'::date AS date, 2750.0 AS payment UNION ALL
89+
SELECT '2014-10-30'::date AS date, 4250.0 AS payment UNION ALL
90+
SELECT '2015-02-15'::date AS date, 3250.0 AS payment UNION ALL
91+
SELECT '2015-04-01'::date AS date, 2750.0 AS payment
92+
`,
93+
94+
dimensions: {
95+
date: {
96+
sql: `date`,
97+
type: `time`
98+
},
99+
100+
payment: {
101+
sql: `payment`,
102+
type: `number`
103+
},
104+
105+
// Everything below this line is only needed for querying
106+
// pre-aggregations in Cube Store
107+
date__day: {
108+
sql: `${CUBE.date.day}`,
109+
type: `time`
110+
}
111+
},
112+
113+
measures: {
114+
total_payments: {
115+
sql: `payment`,
116+
type: `sum`
117+
},
118+
119+
xirr: {
120+
multi_stage: true,
121+
sql: `XIRR(${CUBE.total_payments}, ${CUBE.date__day})`,
122+
type: `number_agg`,
123+
add_group_by: [
124+
date__day
125+
]
126+
}
127+
},
128+
129+
pre_aggregations: {
130+
main_xirr: {
131+
measures: [
132+
total_payments
133+
],
134+
time_dimension: date,
135+
granularity: `day`
136+
}
137+
}
138+
})
139+
```
140+
141+
</CodeTabs>
142+
143+
## Query
144+
145+
### DAX API
146+
147+
You can use the `XIRR` function in DAX.
148+
149+
### SQL API
150+
151+
[Query with post-processing][ref-query-wpp] in the SQL API:
152+
153+
```sql
154+
SELECT
155+
XIRR(payment, date) AS xirr
156+
FROM (
157+
SELECT
158+
DATE_TRUNC('DAY', date) AS date,
159+
SUM(payment) AS payment
160+
FROM payments
161+
GROUP BY 1
162+
) AS payments;
163+
```
164+
165+
[Regular query][ref-query-regular] in the SQL API that hits a pre-aggregation in Cube Store:
166+
167+
```sql
168+
SELECT MEASURE(xirr) AS xirr
169+
FROM payments;
170+
```
171+
172+
### REST API
173+
174+
Regular query in the REST API that hits a pre-aggregation in Cube Store:
175+
176+
```json
177+
{
178+
"measures": [
179+
"payments.xirr"
180+
]
181+
}
182+
```
183+
184+
## Result
185+
186+
All queries above would yield the same result:
187+
188+
```
189+
xirr
190+
--------------------
191+
0.3748585976775555
192+
```
193+
194+
195+
[ref-sql-api]: /product/apis-integrations/sql-api/reference#custom-functions
196+
[ref-dax-api]: /product/apis-integrations/dax-api/reference#financial-functions
197+
[ref-mdx-api]: /product/apis-integrations/mdx-api
198+
[ref-rest-api]: /product/apis-integrations/rest-api
199+
[ref-query-wpp]: /product/apis-integrations/queries#query-with-post-processing
200+
[ref-query-regular]: /product/apis-integrations/queries#regular-query
201+
[link-tesseract]: https://cube.dev/blog/introducing-next-generation-data-modeling-engine
202+
[ref-multi-stage-calculations]: /product/data-modeling/concepts/multi-stage-calculations

docs/pages/product/apis-integrations/dax-api/reference.mdx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,15 @@ of the DAX documentation.
100100

101101
</InfoBox>
102102

103-
No financial functions currently supported.
103+
| Function | <nobr>Unsupported features</nobr> | Caveats |
104+
| --- | --- | --- |
105+
| [`XIRR`](https://learn.microsoft.com/en-us/dax/xirr-function-dax) |||
106+
107+
<ReferenceBox>
108+
109+
See the [XIRR recipe](/guides/recipes/analytics/xirr) for more details.
110+
111+
</ReferenceBox>
104112

105113
### INFO functions
106114

docs/pages/product/apis-integrations/sql-api/reference.mdx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ SHOW ALL;
150150
## SQL functions and operators
151151

152152
SQL API currently implements a subset of functions and operators [supported by
153-
PostgreSQL][link-postgres-funcs].
153+
PostgreSQL][link-postgres-funcs]. Additionally, it supports a few [custom
154+
functions](#custom-functions).
154155

155156
### Comparison operators
156157

@@ -407,12 +408,24 @@ of the PostgreSQL documentation.
407408
| `IN` | Returns `TRUE` if a left-side value matches **any** of right-side values | ✅ Yes | <nobr>✅ Outer</nobr><br/><nobr>✅ Inner (selections)</nobr><br/><nobr>✅ Inner (projections)</nobr> |
408409
| `NOT IN` | Returns `TRUE` if a left-side value matches **none** of right-side values | ✅ Yes | <nobr>✅ Outer</nobr><br/><nobr>✅ Inner (selections)</nobr><br/><nobr>✅ Inner (projections)</nobr> |
409410

411+
### Custom functions
412+
413+
| Function | Description |
414+
| --- | --- |
415+
| `XIRR` | Calculates the [internal rate of return][link-xirr] for a series of cash flows |
416+
417+
<ReferenceBox>
418+
419+
See the [XIRR recipe](/guides/recipes/analytics/xirr) for more details.
420+
421+
</ReferenceBox>
422+
410423

411424
[ref-qpd]: /product/apis-integrations/sql-api/query-format#query-pushdown
412425
[ref-qpp]: /product/apis-integrations/sql-api/query-format#query-post-processing
413426
[ref-sql-api]: /product/apis-integrations/sql-api
414427
[ref-sql-api-aggregate-functions]: /product/apis-integrations/sql-api/query-format#aggregate-functions
415-
416428
[link-postgres-funcs]: https://www.postgresql.org/docs/current/functions.html
417429
[link-github-sql-api]: https://github.com/cube-js/cube/issues?q=is%3Aopen+is%3Aissue+label%3Aapi%3Asql
418-
[link-github-new-sql-api-issue]: https://github.com/cube-js/cube/issues/new?assignees=&labels=&projects=&template=sql_api_query_issue.md&title=
430+
[link-github-new-sql-api-issue]: https://github.com/cube-js/cube/issues/new?assignees=&labels=&projects=&template=sql_api_query_issue.md&title=
431+
[link-xirr]: https://support.microsoft.com/en-us/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d

docs/pages/reference/data-model/types-and-formats.mdx

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
---
2-
redirect_from:
3-
- /types-and-formats
4-
- /schema/reference/types-and-formats
5-
---
6-
71
# Types and Formats
82

93
## Measure Types
@@ -139,7 +133,7 @@ cubes:
139133
140134
### `number`
141135

142-
Type `number` is usually used, when performing
136+
The `number` type is usually used, when performing arithmetic operations on
143137
arithmetic operations on measures. [Learn more about Calculated
144138
Measures][ref-schema-ref-calc-measures].
145139

@@ -207,10 +201,59 @@ cubes:
207201
208202
</CodeTabs>
209203
204+
### `number_agg`
205+
206+
The `number_agg` type is used when you need to write a custom aggregate function
207+
in the `sql` parameter that isn't covered by standard measure types like `sum`,
208+
`avg`, `min`, etc.
209+
210+
<WarningBox>
211+
212+
The `number_agg` type is only available in Tesseract, the [next-generation data modeling
213+
engine][link-tesseract]. Tesseract is currently in preview. Use the
214+
`CUBEJS_TESSERACT_SQL_PLANNER` environment variable to enable it.
215+
216+
</WarningBox>
217+
218+
Unlike the `number` type which is used for calculations on measures (e.g.,
219+
`SUM(revenue) / COUNT(*)`), `number_agg` indicates that the `sql` parameter contains
220+
a direct SQL aggregate function.
221+
222+
The `sql` parameter is required and must include a custom aggregate function that returns a numeric
223+
value.
224+
225+
<CodeTabs>
226+
227+
```javascript
228+
cube(`orders`, {
229+
// ...
230+
231+
measures: {
232+
median_price: {
233+
sql: `PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)`,
234+
type: `number_agg`
235+
}
236+
}
237+
})
238+
```
239+
240+
```yaml
241+
cubes:
242+
- name: orders
243+
# ...
244+
245+
measures:
246+
- name: median_price
247+
sql: "PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY price)"
248+
type: number_agg
249+
```
250+
251+
</CodeTabs>
252+
210253
### `count`
211254

212-
Performs a table count, similar to SQLs `COUNT` function. However, unlike
213-
writing raw SQL, Cube will properly calculate counts even if your querys
255+
Performs a table count, similar to SQL's `COUNT` function. However, unlike
256+
writing raw SQL, Cube will properly calculate counts even if your query's
214257
joins will produce row multiplication.
215258

216259
You do not need to include a `sql` parameter for this type.
@@ -254,7 +297,7 @@ cubes:
254297
255298
### `count_distinct`
256299

257-
Calculates the number of distinct values in a given field. It makes use of SQLs
300+
Calculates the number of distinct values in a given field. It makes use of SQL's
258301
`COUNT DISTINCT` function.
259302

260303
The `sql` parameter is required and must include any valid SQL expression
@@ -332,9 +375,9 @@ cubes:
332375
333376
### `sum`
334377

335-
Adds up the values in a given field. It is similar to SQLs `SUM` function.
378+
Adds up the values in a given field. It is similar to SQL's `SUM` function.
336379
However, unlike writing raw SQL, Cube will properly calculate sums even if your
337-
querys joins will result in row duplication.
380+
query's joins will result in row duplication.
338381

339382
The `sql` parameter is required and must include any valid SQL expression
340383
of the numeric type (without an aggregate function).
@@ -387,9 +430,9 @@ cubes:
387430
388431
### `avg`
389432

390-
Averages the values in a given field. It is similar to SQLs AVG function.
433+
Averages the values in a given field. It is similar to SQL's AVG function.
391434
However, unlike writing raw SQL, Cube will properly calculate averages even if
392-
your querys joins will result in row duplication.
435+
your query's joins will result in row duplication.
393436

394437
The `sql` parameter is required and must include any valid SQL expression
395438
of the numeric type (without an aggregate function).
@@ -494,7 +537,7 @@ cubes:
494537
495538
## Measure Formats
496539
497-
When creating a **measure** you can explicitly define the format youd like to
540+
When creating a **measure** you can explicitly define the format you'd like to
498541
see as output.
499542
500543
### `percent`

0 commit comments

Comments
 (0)