@bonnard/cli 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bon.mjs +1919 -100
- package/dist/bin/models-IsV2sX74.mjs +76 -0
- package/dist/bin/{validate-Bd1D39Bj.mjs → validate-C4EHvJzJ.mjs} +47 -4
- package/dist/docs/README.md +78 -0
- package/dist/docs/_index.md +70 -0
- package/dist/docs/topics/cubes.data-source.md +92 -0
- package/dist/docs/topics/cubes.dimensions.format.md +195 -0
- package/dist/docs/topics/cubes.dimensions.md +184 -0
- package/dist/docs/topics/cubes.dimensions.primary-key.md +106 -0
- package/dist/docs/topics/cubes.dimensions.sub-query.md +174 -0
- package/dist/docs/topics/cubes.dimensions.time.md +111 -0
- package/dist/docs/topics/cubes.dimensions.types.md +107 -0
- package/dist/docs/topics/cubes.extends.md +149 -0
- package/dist/docs/topics/cubes.hierarchies.md +174 -0
- package/dist/docs/topics/cubes.joins.md +115 -0
- package/dist/docs/topics/cubes.md +117 -0
- package/dist/docs/topics/cubes.measures.calculated.md +99 -0
- package/dist/docs/topics/cubes.measures.drill-members.md +158 -0
- package/dist/docs/topics/cubes.measures.filters.md +86 -0
- package/dist/docs/topics/cubes.measures.format.md +153 -0
- package/dist/docs/topics/cubes.measures.md +162 -0
- package/dist/docs/topics/cubes.measures.rolling.md +119 -0
- package/dist/docs/topics/cubes.measures.types.md +122 -0
- package/dist/docs/topics/cubes.public.md +172 -0
- package/dist/docs/topics/cubes.refresh-key.md +153 -0
- package/dist/docs/topics/cubes.segments.md +121 -0
- package/dist/docs/topics/cubes.sql.md +61 -0
- package/dist/docs/topics/pre-aggregations.md +126 -0
- package/dist/docs/topics/pre-aggregations.rollup.md +162 -0
- package/dist/docs/topics/syntax.context-variables.md +153 -0
- package/dist/docs/topics/syntax.md +133 -0
- package/dist/docs/topics/syntax.references.md +174 -0
- package/dist/docs/topics/views.cubes.md +162 -0
- package/dist/docs/topics/views.folders.md +154 -0
- package/dist/docs/topics/views.includes.md +139 -0
- package/dist/docs/topics/views.md +138 -0
- package/dist/docs/topics/workflow.deploy.md +128 -0
- package/dist/docs/topics/workflow.mcp.md +100 -0
- package/dist/docs/topics/workflow.md +147 -0
- package/dist/docs/topics/workflow.query.md +198 -0
- package/dist/docs/topics/workflow.validate.md +152 -0
- package/dist/templates/claude/rules/bonnard.md +15 -0
- package/dist/templates/claude/settings.json +7 -0
- package/dist/templates/claude/skills/bonnard-cli/SKILL.md +59 -0
- package/dist/templates/claude/skills/bonnard-queries/SKILL.md +68 -0
- package/dist/templates/cursor/rules/bonnard-cli.mdc +47 -0
- package/dist/templates/cursor/rules/bonnard-queries.mdc +49 -0
- package/dist/templates/cursor/rules/bonnard.mdc +20 -0
- package/dist/templates/shared/bonnard.md +81 -0
- package/package.json +13 -8
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# cubes.extends
|
|
2
|
+
|
|
3
|
+
> Reuse members from other cubes to reduce duplication.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `extends` property allows a cube to inherit measures, dimensions, segments, and joins from another cube. This promotes code reusability and keeps your data model DRY.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
cubes:
|
|
13
|
+
# Base cube with shared members
|
|
14
|
+
- name: base_events
|
|
15
|
+
sql_table: events
|
|
16
|
+
|
|
17
|
+
measures:
|
|
18
|
+
- name: count
|
|
19
|
+
type: count
|
|
20
|
+
|
|
21
|
+
dimensions:
|
|
22
|
+
- name: id
|
|
23
|
+
type: number
|
|
24
|
+
sql: "{CUBE}.id"
|
|
25
|
+
primary_key: true
|
|
26
|
+
|
|
27
|
+
- name: created_at
|
|
28
|
+
type: time
|
|
29
|
+
sql: "{CUBE}.created_at"
|
|
30
|
+
|
|
31
|
+
# Child cube extends base
|
|
32
|
+
- name: page_views
|
|
33
|
+
extends: base_events
|
|
34
|
+
sql_table: page_view_events
|
|
35
|
+
|
|
36
|
+
dimensions:
|
|
37
|
+
- name: page_url
|
|
38
|
+
type: string
|
|
39
|
+
sql: "{CUBE}.url"
|
|
40
|
+
|
|
41
|
+
# Another child cube
|
|
42
|
+
- name: purchases
|
|
43
|
+
extends: base_events
|
|
44
|
+
sql_table: purchase_events
|
|
45
|
+
|
|
46
|
+
measures:
|
|
47
|
+
- name: total_revenue
|
|
48
|
+
type: sum
|
|
49
|
+
sql: "{CUBE}.amount"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How It Works
|
|
53
|
+
|
|
54
|
+
When a cube extends another:
|
|
55
|
+
|
|
56
|
+
1. **Measures merge** — child gets all parent measures plus its own
|
|
57
|
+
2. **Dimensions merge** — child gets all parent dimensions plus its own
|
|
58
|
+
3. **Segments merge** — child gets all parent segments plus its own
|
|
59
|
+
4. **Joins merge** — child gets all parent joins plus its own
|
|
60
|
+
|
|
61
|
+
## Important: Use {CUBE}
|
|
62
|
+
|
|
63
|
+
Always use `{CUBE}` when referencing columns in extendable cubes:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
# Good - works in parent and child cubes
|
|
67
|
+
dimensions:
|
|
68
|
+
- name: created_at
|
|
69
|
+
type: time
|
|
70
|
+
sql: "{CUBE}.created_at"
|
|
71
|
+
|
|
72
|
+
# Bad - breaks in child cubes
|
|
73
|
+
dimensions:
|
|
74
|
+
- name: created_at
|
|
75
|
+
type: time
|
|
76
|
+
sql: "{base_events}.created_at"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Overriding Members
|
|
80
|
+
|
|
81
|
+
Child cubes can override parent members by defining them with the same name:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
cubes:
|
|
85
|
+
- name: base_orders
|
|
86
|
+
measures:
|
|
87
|
+
- name: count
|
|
88
|
+
type: count
|
|
89
|
+
description: "Total orders"
|
|
90
|
+
|
|
91
|
+
- name: completed_orders
|
|
92
|
+
extends: base_orders
|
|
93
|
+
measures:
|
|
94
|
+
- name: count
|
|
95
|
+
type: count
|
|
96
|
+
description: "Completed orders only"
|
|
97
|
+
filters:
|
|
98
|
+
- sql: "{CUBE}.status = 'completed'"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Common Patterns
|
|
102
|
+
|
|
103
|
+
### Event Tables
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
cubes:
|
|
107
|
+
- name: base_event
|
|
108
|
+
dimensions:
|
|
109
|
+
- name: id
|
|
110
|
+
sql: "{CUBE}.id"
|
|
111
|
+
primary_key: true
|
|
112
|
+
- name: user_id
|
|
113
|
+
sql: "{CUBE}.user_id"
|
|
114
|
+
- name: timestamp
|
|
115
|
+
type: time
|
|
116
|
+
sql: "{CUBE}.timestamp"
|
|
117
|
+
|
|
118
|
+
- name: clicks
|
|
119
|
+
extends: base_event
|
|
120
|
+
sql_table: click_events
|
|
121
|
+
|
|
122
|
+
- name: impressions
|
|
123
|
+
extends: base_event
|
|
124
|
+
sql_table: impression_events
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Multi-Tenant Tables
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
cubes:
|
|
131
|
+
- name: base_orders
|
|
132
|
+
sql: "SELECT * FROM orders WHERE tenant_id = '{{COMPILE_CONTEXT.tenant}}'"
|
|
133
|
+
|
|
134
|
+
- name: tenant_orders
|
|
135
|
+
extends: base_orders
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Best Practices
|
|
139
|
+
|
|
140
|
+
1. **Use {CUBE} everywhere** — never hardcode cube names in SQL
|
|
141
|
+
2. **Keep base cubes focused** — only include truly shared members
|
|
142
|
+
3. **Don't over-extend** — deep inheritance chains are hard to maintain
|
|
143
|
+
4. **Document inheritance** — make it clear which cubes extend others
|
|
144
|
+
|
|
145
|
+
## See Also
|
|
146
|
+
|
|
147
|
+
- cubes
|
|
148
|
+
- syntax.references
|
|
149
|
+
- syntax.context-variables
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# cubes.hierarchies
|
|
2
|
+
|
|
3
|
+
> Define drill-down paths for dimensional analysis.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Hierarchies group dimensions into levels of granularity, enabling drill-down and roll-up analysis. Users can navigate from high-level summaries (e.g., country) down to details (e.g., city).
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
cubes:
|
|
13
|
+
- name: orders
|
|
14
|
+
sql_table: orders
|
|
15
|
+
|
|
16
|
+
dimensions:
|
|
17
|
+
- name: country
|
|
18
|
+
type: string
|
|
19
|
+
sql: country
|
|
20
|
+
|
|
21
|
+
- name: state
|
|
22
|
+
type: string
|
|
23
|
+
sql: state
|
|
24
|
+
|
|
25
|
+
- name: city
|
|
26
|
+
type: string
|
|
27
|
+
sql: city
|
|
28
|
+
|
|
29
|
+
hierarchies:
|
|
30
|
+
- name: location
|
|
31
|
+
title: "Geographic Location"
|
|
32
|
+
levels:
|
|
33
|
+
- country
|
|
34
|
+
- state
|
|
35
|
+
- city
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Properties
|
|
39
|
+
|
|
40
|
+
| Property | Required | Description |
|
|
41
|
+
|----------|----------|-------------|
|
|
42
|
+
| `name` | Yes | Unique identifier |
|
|
43
|
+
| `levels` | Yes | Ordered list of dimensions (least to most granular) |
|
|
44
|
+
| `title` | No | Human-readable display name |
|
|
45
|
+
| `public` | No | API visibility (default: true) |
|
|
46
|
+
|
|
47
|
+
## Syntax
|
|
48
|
+
|
|
49
|
+
### Basic Hierarchy
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
hierarchies:
|
|
53
|
+
- name: time_hierarchy
|
|
54
|
+
levels:
|
|
55
|
+
- year
|
|
56
|
+
- quarter
|
|
57
|
+
- month
|
|
58
|
+
- day
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### With Title
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
hierarchies:
|
|
65
|
+
- name: product_category
|
|
66
|
+
title: "Product Categories"
|
|
67
|
+
levels:
|
|
68
|
+
- department
|
|
69
|
+
- category
|
|
70
|
+
- subcategory
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Multiple Hierarchies
|
|
74
|
+
|
|
75
|
+
A dimension can appear in multiple hierarchies:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
hierarchies:
|
|
79
|
+
- name: fiscal_time
|
|
80
|
+
levels:
|
|
81
|
+
- fiscal_year
|
|
82
|
+
- fiscal_quarter
|
|
83
|
+
- fiscal_month
|
|
84
|
+
|
|
85
|
+
- name: calendar_time
|
|
86
|
+
levels:
|
|
87
|
+
- calendar_year
|
|
88
|
+
- calendar_quarter
|
|
89
|
+
- calendar_month
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Cross-Cube Hierarchies
|
|
93
|
+
|
|
94
|
+
Include dimensions from joined cubes using dot notation:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
cubes:
|
|
98
|
+
- name: orders
|
|
99
|
+
joins:
|
|
100
|
+
- name: users
|
|
101
|
+
relationship: many_to_one
|
|
102
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
103
|
+
|
|
104
|
+
hierarchies:
|
|
105
|
+
- name: customer_location
|
|
106
|
+
levels:
|
|
107
|
+
- users.country
|
|
108
|
+
- users.state
|
|
109
|
+
- users.city
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Common Patterns
|
|
113
|
+
|
|
114
|
+
### Geographic Hierarchy
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
hierarchies:
|
|
118
|
+
- name: geography
|
|
119
|
+
title: "Location"
|
|
120
|
+
levels:
|
|
121
|
+
- continent
|
|
122
|
+
- country
|
|
123
|
+
- region
|
|
124
|
+
- city
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Time Hierarchy
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
hierarchies:
|
|
131
|
+
- name: time
|
|
132
|
+
title: "Time Period"
|
|
133
|
+
levels:
|
|
134
|
+
- year
|
|
135
|
+
- quarter
|
|
136
|
+
- month
|
|
137
|
+
- week
|
|
138
|
+
- day
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Organizational Hierarchy
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
hierarchies:
|
|
145
|
+
- name: organization
|
|
146
|
+
title: "Org Structure"
|
|
147
|
+
levels:
|
|
148
|
+
- division
|
|
149
|
+
- department
|
|
150
|
+
- team
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Product Hierarchy
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
hierarchies:
|
|
157
|
+
- name: product
|
|
158
|
+
title: "Product Categories"
|
|
159
|
+
levels:
|
|
160
|
+
- brand
|
|
161
|
+
- category
|
|
162
|
+
- product_line
|
|
163
|
+
- sku
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## BI Tool Support
|
|
167
|
+
|
|
168
|
+
Hierarchy support varies by visualization tool. Check your specific tool's documentation for compatibility.
|
|
169
|
+
|
|
170
|
+
## See Also
|
|
171
|
+
|
|
172
|
+
- cubes.dimensions
|
|
173
|
+
- cubes.joins
|
|
174
|
+
- views.folders
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# cubes.joins
|
|
2
|
+
|
|
3
|
+
> Connect cubes together to enable cross-cube analysis.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Joins define relationships between cubes, allowing queries to combine measures and dimensions from multiple cubes. Cube automatically generates the appropriate SQL JOINs.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
cubes:
|
|
13
|
+
- name: orders
|
|
14
|
+
sql: SELECT * FROM orders
|
|
15
|
+
|
|
16
|
+
joins:
|
|
17
|
+
- name: users
|
|
18
|
+
relationship: many_to_one
|
|
19
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
20
|
+
|
|
21
|
+
- name: products
|
|
22
|
+
relationship: many_to_one
|
|
23
|
+
sql: "{CUBE}.product_id = {products.id}"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Relationship Types
|
|
27
|
+
|
|
28
|
+
### many_to_one
|
|
29
|
+
Many rows in this cube relate to one row in the joined cube.
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
# Many orders belong to one user
|
|
33
|
+
- name: users
|
|
34
|
+
relationship: many_to_one
|
|
35
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### one_to_many
|
|
39
|
+
One row in this cube relates to many rows in the joined cube.
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
# One user has many orders
|
|
43
|
+
- name: orders
|
|
44
|
+
relationship: one_to_many
|
|
45
|
+
sql: "{CUBE}.id = {orders.user_id}"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### one_to_one
|
|
49
|
+
One row in this cube relates to exactly one row in the joined cube.
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# One user has one profile
|
|
53
|
+
- name: profiles
|
|
54
|
+
relationship: one_to_one
|
|
55
|
+
sql: "{CUBE}.id = {profiles.user_id}"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Join Properties
|
|
59
|
+
|
|
60
|
+
| Property | Required | Description |
|
|
61
|
+
|----------|----------|-------------|
|
|
62
|
+
| `name` | Yes | Name of the cube to join |
|
|
63
|
+
| `relationship` | Yes | `many_to_one`, `one_to_many`, or `one_to_one` |
|
|
64
|
+
| `sql` | Yes | Join condition |
|
|
65
|
+
|
|
66
|
+
## Syntax
|
|
67
|
+
|
|
68
|
+
### {CUBE} Reference
|
|
69
|
+
Use `{CUBE}` to reference the current cube:
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Referencing Joined Cube
|
|
76
|
+
Use `{cube_name}` to reference the joined cube:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
sql: "{CUBE}.product_id = {products.id}"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Multi-hop Joins
|
|
83
|
+
|
|
84
|
+
Cubes can access data through transitive joins:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# orders -> users -> countries
|
|
88
|
+
# Query can use countries.name with orders.count
|
|
89
|
+
|
|
90
|
+
cubes:
|
|
91
|
+
- name: orders
|
|
92
|
+
joins:
|
|
93
|
+
- name: users
|
|
94
|
+
relationship: many_to_one
|
|
95
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
96
|
+
|
|
97
|
+
- name: users
|
|
98
|
+
joins:
|
|
99
|
+
- name: countries
|
|
100
|
+
relationship: many_to_one
|
|
101
|
+
sql: "{CUBE}.country_id = {countries.id}"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Best Practices
|
|
105
|
+
|
|
106
|
+
1. **Define joins on the "many" side** — put the join in orders, not users
|
|
107
|
+
2. **Use primary keys** — ensure joined cubes have `primary_key` defined
|
|
108
|
+
3. **Keep joins simple** — avoid complex conditions
|
|
109
|
+
4. **Consider views** for specific join paths
|
|
110
|
+
|
|
111
|
+
## See Also
|
|
112
|
+
|
|
113
|
+
- cubes
|
|
114
|
+
- cubes.dimensions.primary-key
|
|
115
|
+
- views
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# cubes
|
|
2
|
+
|
|
3
|
+
> Define data models that map to your database tables.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Cubes are the foundational building blocks of your semantic layer. Each cube represents a dataset (typically a database table or SQL query) and defines the measures (metrics) and dimensions (attributes) available for analysis.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
cubes:
|
|
13
|
+
- name: orders
|
|
14
|
+
sql_table: public.orders
|
|
15
|
+
|
|
16
|
+
measures:
|
|
17
|
+
- name: count
|
|
18
|
+
type: count
|
|
19
|
+
|
|
20
|
+
- name: total_revenue
|
|
21
|
+
type: sum
|
|
22
|
+
sql: amount
|
|
23
|
+
|
|
24
|
+
- name: average_order_value
|
|
25
|
+
type: number
|
|
26
|
+
sql: "{total_revenue} / NULLIF({count}, 0)"
|
|
27
|
+
|
|
28
|
+
dimensions:
|
|
29
|
+
- name: id
|
|
30
|
+
type: number
|
|
31
|
+
sql: id
|
|
32
|
+
primary_key: true
|
|
33
|
+
|
|
34
|
+
- name: status
|
|
35
|
+
type: string
|
|
36
|
+
sql: status
|
|
37
|
+
|
|
38
|
+
- name: created_at
|
|
39
|
+
type: time
|
|
40
|
+
sql: created_at
|
|
41
|
+
|
|
42
|
+
joins:
|
|
43
|
+
- name: users
|
|
44
|
+
relationship: many_to_one
|
|
45
|
+
sql: "{CUBE}.user_id = {users.id}"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Core Properties
|
|
49
|
+
|
|
50
|
+
| Property | Required | Description |
|
|
51
|
+
|----------|----------|-------------|
|
|
52
|
+
| `name` | Yes | Unique identifier in snake_case |
|
|
53
|
+
| `sql` or `sql_table` | Yes | Data source query or table |
|
|
54
|
+
| `measures` | No | Aggregations (count, sum, avg, etc.) |
|
|
55
|
+
| `dimensions` | No | Attributes for grouping/filtering |
|
|
56
|
+
| `joins` | No | Relationships to other cubes |
|
|
57
|
+
| `segments` | No | Predefined filters |
|
|
58
|
+
| `pre_aggregations` | No | Materialized query results |
|
|
59
|
+
|
|
60
|
+
## Data Source
|
|
61
|
+
|
|
62
|
+
### sql_table (recommended)
|
|
63
|
+
|
|
64
|
+
For simple table references:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
cubes:
|
|
68
|
+
- name: orders
|
|
69
|
+
sql_table: public.orders
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### sql
|
|
73
|
+
|
|
74
|
+
For complex queries:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
cubes:
|
|
78
|
+
- name: orders
|
|
79
|
+
sql: >
|
|
80
|
+
SELECT o.*, u.name as user_name
|
|
81
|
+
FROM orders o
|
|
82
|
+
LEFT JOIN users u ON o.user_id = u.id
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Naming Conventions
|
|
86
|
+
|
|
87
|
+
- Use `snake_case` for names
|
|
88
|
+
- Use plural nouns: `orders`, `users`, `products`
|
|
89
|
+
- Be descriptive: `order_line_items` not `oli`
|
|
90
|
+
|
|
91
|
+
## File Organization
|
|
92
|
+
|
|
93
|
+
One cube per file in the `models/` directory:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
models/
|
|
97
|
+
├── orders.yaml
|
|
98
|
+
├── users.yaml
|
|
99
|
+
├── products.yaml
|
|
100
|
+
└── line_items.yaml
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Best Practices
|
|
104
|
+
|
|
105
|
+
1. **Define a primary key** — required for joins and count_distinct
|
|
106
|
+
2. **Start with basic measures** — count, sum of key metrics
|
|
107
|
+
3. **Add dimensions for common filters** — status, dates, categories
|
|
108
|
+
4. **Use joins sparingly** — define on the "many" side
|
|
109
|
+
5. **Document with descriptions** — help consumers understand the data
|
|
110
|
+
|
|
111
|
+
## See Also
|
|
112
|
+
|
|
113
|
+
- cubes.sql
|
|
114
|
+
- cubes.measures
|
|
115
|
+
- cubes.dimensions
|
|
116
|
+
- cubes.joins
|
|
117
|
+
- cubes.segments
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# cubes.measures.calculated
|
|
2
|
+
|
|
3
|
+
> Build complex metrics from other measures.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Calculated measures combine existing measures to create derived metrics. Use the `number` type and reference other measures with `{measure_name}` syntax.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
measures:
|
|
13
|
+
- name: total_orders
|
|
14
|
+
type: count
|
|
15
|
+
|
|
16
|
+
- name: total_revenue
|
|
17
|
+
type: sum
|
|
18
|
+
sql: amount
|
|
19
|
+
|
|
20
|
+
- name: average_order_value
|
|
21
|
+
type: number
|
|
22
|
+
sql: "{total_revenue} / NULLIF({total_orders}, 0)"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Syntax
|
|
26
|
+
|
|
27
|
+
### Referencing Measures
|
|
28
|
+
|
|
29
|
+
Use curly braces to reference other measures:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
- name: profit_margin
|
|
33
|
+
type: number
|
|
34
|
+
sql: "({revenue} - {cost}) / NULLIF({revenue}, 0)"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Handling Division by Zero
|
|
38
|
+
|
|
39
|
+
Always use `NULLIF` to prevent division by zero:
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
- name: conversion_rate
|
|
43
|
+
type: number
|
|
44
|
+
sql: "{conversions} / NULLIF({visits}, 0)"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common Patterns
|
|
48
|
+
|
|
49
|
+
### Percentages
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
- name: completion_rate
|
|
53
|
+
type: number
|
|
54
|
+
sql: "100.0 * {completed} / NULLIF({total}, 0)"
|
|
55
|
+
format: percent
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Ratios
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
- name: orders_per_user
|
|
62
|
+
type: number
|
|
63
|
+
sql: "{order_count} / NULLIF({user_count}, 0)"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Profit Calculations
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
- name: gross_profit
|
|
70
|
+
type: number
|
|
71
|
+
sql: "{revenue} - {cost_of_goods}"
|
|
72
|
+
|
|
73
|
+
- name: gross_margin
|
|
74
|
+
type: number
|
|
75
|
+
sql: "{gross_profit} / NULLIF({revenue}, 0)"
|
|
76
|
+
format: percent
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Year-over-Year
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
- name: yoy_growth
|
|
83
|
+
type: number
|
|
84
|
+
sql: "({revenue_this_year} - {revenue_last_year}) / NULLIF({revenue_last_year}, 0)"
|
|
85
|
+
format: percent
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Best Practices
|
|
89
|
+
|
|
90
|
+
1. **Build on base measures** — don't duplicate aggregation logic
|
|
91
|
+
2. **Use descriptive names** — `conversion_rate` not `calc1`
|
|
92
|
+
3. **Add format** — helps consumers understand the value
|
|
93
|
+
4. **Document complex formulas** with `description`
|
|
94
|
+
|
|
95
|
+
## See Also
|
|
96
|
+
|
|
97
|
+
- cubes.measures
|
|
98
|
+
- cubes.measures.types
|
|
99
|
+
- cubes.measures.rolling
|