@@ -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
377377Sometimes there is no associative table in the database, when in reality, there
378378is a many-to-many relationship. In this case, the solution is to extract some
@@ -644,7 +644,9 @@ cubes:
644644## Directions of joins
645645
646646The 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+
896824We 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
961842Let'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
0 commit comments