Skip to content

Commit 1134df4

Browse files
keydunovigorlukaninhassankhan
authored
docs: update direction of joins section (#6541)
* docs: update direction of joins section * chore: fix view doc * Update docs/content/Schema/Fundamentals/Working-with-Joins.mdx Co-authored-by: Igor Lukanin <[email protected]> * Update docs/content/Schema/Reference/view.mdx Co-authored-by: Hassan Khan <[email protected]> * Update docs/content/Schema/Fundamentals/Working-with-Joins.mdx Co-authored-by: Igor Lukanin <[email protected]> --------- Co-authored-by: Igor Lukanin <[email protected]> Co-authored-by: Hassan Khan <[email protected]>
1 parent dde9bc4 commit 1134df4

File tree

2 files changed

+88
-185
lines changed

2 files changed

+88
-185
lines changed

docs/content/Schema/Fundamentals/Working-with-Joins.mdx

Lines changed: 66 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ cubes:
372372
373373
</CodeTabs>
374374
375-
### <--{"id" : "Many-to-many joins"}--> Many-to-many joins without an associative table
375+
### <--{"id" : "Many-to-many joins"}--> Using virtual associative cube
376376
377377
Sometimes there is no associative table in the database, when in reality, there
378378
is a many-to-many relationship. In this case, the solution is to extract some
@@ -644,7 +644,9 @@ cubes:
644644
## Directions of joins
645645
646646
The direction of [joins][ref-schema-ref-joins] greatly influences the final
647-
result set. As an example, let's take two cubes, `orders` and `customers`:
647+
result set. It can be explicitly controlled on a [view][ref-schema-ref-view] level.
648+
649+
As an example, let's take two cubes, `orders` and `customers`:
648650

649651
<CodeTabs>
650652

@@ -681,6 +683,11 @@ cube(`customers`, {
681683
sql: `id`,
682684
type: `count`,
683685
},
686+
687+
total_revenue: {
688+
sql: `revenue`,
689+
type: `sum`,
690+
},
684691
},
685692

686693
dimensions: {
@@ -702,6 +709,10 @@ cubes:
702709
- name: count
703710
sql: id
704711
type: count
712+
713+
- name: total_revenue
714+
sql: revenue
715+
type: sum
705716

706717
dimensions:
707718
- name: id
@@ -730,233 +741,103 @@ cubes:
730741
731742
</CodeTabs>
732743
733-
The first case is to calculate the total revenue per customer. Let's name this
734-
metric `total_revenue`. We also need to be aware of the fact that orders can be
735-
placed without customer registration (anonymous customers/guest checkouts).
736-
Because of anonymous customers, we should start the join from the `orders` onto
737-
the `customers` cube so that we do not lose data from anonymous orders:
744+
With the given data model, we have two valid analytics use cases that require different join directions.
738745
739-
<CodeTabs>
746+
__The first case is to calculate the total revenue per customer.__
747+
To do this, we'll use the `total_revenue` measure that is defined on the orders cube.
748+
We need to be aware that orders can be placed without customer registration (anonymous customers/guest checkouts).
749+
Therefore, we should start the join from the `orders` cube onto the `customers` cube to ensure that we do not lose data from anonymous orders.
740750

741-
```javascript
742-
cube(`orders`, {
743-
sql_table: `orders`,
744751

745-
joins: {
746-
customers: {
747-
relationship: `many_to_one`,
748-
sql: `${CUBE}.customer_id = ${customers.id}`,
749-
},
750-
},
752+
<CodeTabs>
751753

752-
measures: {
753-
count: {
754-
sql: `id`,
755-
type: `count`,
756-
},
754+
```yaml
755+
views:
756+
- name: total_revenue_per_customer
757+
description: Total revenue per customer
757758
758-
total_revenue: {
759-
sql: `revenue`,
760-
type: `sum`,
761-
},
762-
},
759+
cubes:
760+
- join_path: orders
761+
includes:
762+
- total_revenue
763+
- created_at
763764
764-
dimensions: {
765-
id: {
766-
sql: `id`,
767-
type: `number`,
768-
primary_key: true,
769-
public: true,
770-
},
765+
- join_path: orders.customers
766+
includes:
767+
- company
768+
```
769+
770+
```javascript
771+
view(`total_revenue_per_customer`, {
771772

772-
customer_id: {
773-
sql: `customer_id`,
774-
type: `number`,
775-
},
776-
},
777773
});
778774
```
779775

780-
```yaml
781-
cubes:
782-
- name: orders
783-
sql_table: orders
776+
</CodeTabs>
784777

785-
joins:
786-
- name: customers
787-
relationship: many_to_one
788-
sql: "{CUBE}.customer_id = {customers.id}"
789778

790-
measures:
791-
- name: count
792-
sql: id
793-
type: count
794-
795-
- name: total_revenue
796-
sql: revenue
797-
type: sum
798-
799-
dimensions:
800-
- name: id
801-
sql: id
802-
type: number
803-
primary_key: true
804-
public: true
805-
806-
- name: customer_id
807-
sql: customer_id
808-
type: number
809-
```
779+
We can query this view as follows:
810780

811-
</CodeTabs>
812781

813-
After adding the join to the data model, we can query the cube as follows:
814782

815783
```json
816784
{
817-
"dimensions": ["users.company"],
818-
"measures": ["orders.total_revenue"],
785+
"dimensions": ["total_revenue_per_customer.company"],
786+
"measures": ["total_revenue_per_customer.total_revenue"],
819787
"timeDimensions": [{
820-
"dimension": "orders.created_at"
788+
"dimension": "total_revenue_per_customer.created_at"
821789
}]
822790
}
823791
```
824792

825-
The second case is to find customers without any orders. Let's call this metric
826-
`count`. In this case we should join the `customers` cube with the `orders` cube
827-
to find customers with 0 orders placed. The reverse order of joins would result
828-
in a dataset without data for customers with no orders. Therefore, in this
829-
instance, we declare the join in the `customers` cube:
830-
831-
<CodeTabs>
832-
833-
```javascript
834-
cube(`customers`, {
835-
sql_table: `customers`,
836-
837-
joins: {
838-
orders: {
839-
relationship: `one_to_many`,
840-
sql: `${CUBE}.id = ${orders.customer_id}`,
841-
},
842-
},
843793

844-
measures: {
845-
count: {
846-
sql: `id`,
847-
type: `count`,
848-
},
849-
},
850-
851-
dimensions: {
852-
id: {
853-
sql: `id`,
854-
type: `number`,
855-
primary_key: true,
856-
public: true,
857-
},
794+
__The second case is to find customers who have not placed any orders.__
795+
We will use the count measure on the customers cube for that.
796+
In this case, we should join the customers cube with the orders cube to find customers with zero orders placed.
797+
The reverse order of joins would result in a dataset without data for customers with no orders.
858798

859-
customer_id: {
860-
sql: `customer_id`,
861-
type: `number`,
862-
},
863-
},
864-
});
865-
```
799+
<CodeTabs>
866800

867801
```yaml
868-
cubes:
869-
- name: customers
870-
sql_table: customers
871-
872-
joins:
873-
- name: orders
874-
relationship: one_to_many
875-
sql: "{CUBE}.id = {orders.customer_id}"
876-
877-
measures:
878-
- name: count
879-
sql: id
880-
type: count
802+
views:
803+
- name: customers_without_orders
804+
description: Customers without orders
881805

882-
dimensions:
883-
- name: id
884-
sql: id
885-
type: number
886-
primary_key: true
887-
public: true
806+
cubes:
807+
- join_path: customers
808+
includes:
809+
- company
888810

889-
- name: customer_id
890-
sql: customer_id
891-
type: number
811+
- join_path: customers.orders
812+
prefix: true
813+
includes:
814+
- created_at
815+
- count
892816
```
893817
818+
```javascript
819+
```
894820
</CodeTabs>
895821

822+
823+
896824
We can then query the cube as follows:
897825

898826
```json
899827
{
900-
"dimensions": ["users.company"],
828+
"dimensions": ["customers_without_orders.company"],
901829
"timeDimensions": [{
902-
"dimension": "orders.created_at"
830+
"dimension": "customers_without_orders.orders_created_at"
903831
}],
904832
"filters": [{
905-
"member": "orders.count",
833+
"member": "customers_without_orders.orders_count",
906834
"operator": "equals",
907835
"values": ["0"]
908836
}]
909837
}
910838
```
911839

912-
### <--{"id" : "Directions of joins"}--> Using views
913-
914-
Views can also be used in Cube to represent the previous two scenarios:
915-
916-
<CodeTabs>
917-
918-
```javascript
919-
view(`total_revenue_per_customer", {
920-
description: `Total revenue per customer`,
921-
922-
includes: [
923-
orders.total_revenue,
924-
users.company
925-
],
926-
});
927-
928-
view(`customers_without_orders`, {
929-
description: `Customers without orders`,
930-
931-
includes: [
932-
users.company,
933-
// Note the nested path to orders.count
934-
users.orders.count
935-
]
936-
});
937-
```
938-
939-
```yaml
940-
views:
941-
- name: total_revenue_per_customer
942-
description: Total revenue per customer
943-
944-
includes:
945-
- orders.total_revenue
946-
- users.company
947-
948-
- name: customers_without_orders
949-
description: Customers without orders
950-
951-
includes:
952-
- users.company
953-
# Note the nested path to orders.count
954-
- users.orders.count
955-
```
956-
957-
</CodeTabs>
958-
959-
### <--{"id" : "Directions of joins"}--> Transitive join pitfalls
840+
## Transitive join pitfalls
960841

961842
Let's consider an example where we have a many-to-many relationship between
962843
`users` and `organizations` through an `organization_users` cube:
@@ -1147,6 +1028,7 @@ cubes:
11471028
11481029
</CodeTabs>
11491030
1031+
[ref-schema-ref-view]: /schema/reference/view
11501032
[ref-schema-ref-joins]: /schema/reference/joins
11511033
[ref-schema-ref-joins-relationship]:
11521034
/schema/reference/joins#parameters-relationship

docs/content/Schema/Reference/view.mdx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ the above view with SQL API:
100100
```sql
101101
SELECT
102102
users_city,
103-
total_amount
103+
MEASURE(total_amount)
104104
FROM orders
105105
GROUP BY 1
106106
```
@@ -280,6 +280,27 @@ The `public` property is used to manage the visibility of a view. Valid values
280280
for `public` are `true` and `false`. When set to `false`, this view **cannot**
281281
be queried through the API. Defaults to `true`.
282282

283+
<CodeTabs>
284+
285+
```yaml
286+
views:
287+
- name: orders
288+
public: false
289+
```
290+
291+
```javascript
292+
view(`orders`, {
293+
public: false
294+
});
295+
```
296+
297+
</CodeTabs>
298+
299+
300+
You can also use `COMPILE_CONTEXT` for dynamic visibility if necessary, check out our [Controlling access to cubes and views
301+
](https://cube.dev/docs/recipes/controlling-access-to-cubes-and-views) recipe.
302+
303+
283304
<CodeTabs>
284305

285306
```javascript

0 commit comments

Comments
 (0)