Search code examples
cube.js

How to aggregate data from another cube in cubejs?


I have the following cubes (I'm only showing the data necessary to reproduce the problem):

SentMessages:

cube(`SentMessages`, {
    sql: `Select * from messages_sent`,
    dimensions: {
        campaignId: {
            sql: `campaign_id`,
            type: `number` 
        },
        phone: {
            sql: `phone_number`,
            type: `number`
        }

    }
});

Campaigns:

cube(`Campaign`, {
 sql: `SELECT * FROM campaign`,
  joins: {
    SentMessages: {
        sql: `${Campaign}.id = ${SentMessages}.campaign_id`,
        relationship: `hasMany`
    }
  },
  measures: {
    messageSentCount: {
        sql: `${SentMessages}.phone`,
        type: `count`
    }
  },
  dimensions: {
    name: {
      sql: `name`,
      type: `string`
    },
  }
});

The query being sent looks like this:

  "query": {
    "dimensions": ["Campaign.name"],
    "timeDimensions": [
      {
        "dimension": "Campaign.createdOn",
        "granularity": "day"
      }
    ],
    "measures": [
      "Campaign.messageSentCount"
    ],
    "filters": []
  },
  "authInfo": {
    "iat": 1578961890,
    "exp": 1579048290
  },
  "requestId": "da7bf907-90de-4ba0-80f8-1a802dd442f6"

For some reason this is resulting in the following error:

Error: 'Campaign.messageSentCount' references cubes that lead to row multiplication. Please rewrite it using sub query.

I've searched quite a bit on this error and cant find anything. Can someone please help or provide some insight into the problem? It would be really nice if the framework could show the erroneous sql generated just for troubleshooting purposes.


Solution

  • Campaign has many SentMessages and if joined to calculate Campaign.messageSentCount this calculation results might be affected. There's a simple check that ensures there're no hasMany cubes referenced inside aggregation function. This simple sanity check is required to avoid situation which leads to incorrect calculation results. For example if ReceivedMessages is also added as a join to the Campaign then Campaign.messageSentCount will generate incorrect results if ReceivedMessages and SentMessages are selected simultaneously.

    To avoid this sanity check error, substitution with sub query is expected here as follows:

    SentMessages:

    cube(`SentMessages`, {
      sql: `Select * from messages_sent`,
    
      measures: {
        count: {
          type: `count`
        }
      },
    
      dimensions: {
        campaignId: {
          sql: `campaign_id`,
          type: `number` 
        },
        phone: {
          sql: `phone_number`,
          type: `number`
        }
      }
    });
    

    Campaigns:

    cube(`Campaign`, {
     sql: `SELECT * FROM campaign`,
      joins: {
        SentMessages: {
          sql: `${Campaign}.id = ${SentMessages}.campaign_id`,
          relationship: `hasMany`
        }
      },
      measures: {
        totalMessageSendCount: {
          sql: `${messageSentCount}`,
          type: `sum`
        }
      },
      dimensions: {
        messageSentCount: {
          sql: `${SentMessages.count}`,
          type: `number`,
          subQuery: true
        },
        name: {
          sql: `name`,
          type: `string`
        },
      }
    });
    

    For cases where Campaign.messageSentCount doesn't make any sense as a dimension, schema can be simplified and SentMessages.count can be used directly.