Skip to main content

keep_grains

Definition

The keep_grains() function is used when you want your metric to only be evaluated against some specific grain(s)/dimension(s), excluding any other dimensions presented in the query. If the specified grain(s) are not included in the query, the metric will be evaluated against the whole dataset. Note that all filters applied on the excluded dimensions are also ignored.

Knowledge Checkpoint

This documentation assumes you are familiar with the following concepts:

Syntax

keep_grains(metric, model, ..., dimension, ...)
Examples
keep_grains(users.total_spent, users.email)
users.total_spent | keep_grains(users.email)

keep_grains(users.total_spent, users)
users.total_spent | keep_grains(users)

Input:

  • metric: An AQL Metric
  • model (optional, repeatable): The model that contains the dimensions that you want to keep. If a model is specified, the function will keep all the dimensions in that model.
  • dimension (optional, repeatable): The dimension that you want to keep. If a dimension was not specified here, it will be ignored.

Output:

A metric that was only evaluated against at most the specified grain(s)/dimension(s).

Behaviors

When the grains is a specific Dimension

Let's say that you have defined a measure called total_spent in side model users:

Model users {
dimension id {}
dimension name {}
dimension email {}
measure total_spent {
definition: @aql
// Use a specific field as the dimension to keep
sum(order_items.order_value) | keep_grains(users.email)
;;
}
}

When exploring the dataset, if you include the following fields: you include the following fields:

  • Dimension: users.email, order_items.order_month
  • Measure: users.total_spent

Then the measure users.total_spent will be grouped by users.email, and the value will be duplicated for each month:

users.emailorder_items.order_monthusers.total_spent
abc11000
abc21000
abc31000
def11500
def21500
def31500

If you do not include the users.email field when exploring data, the users.total_spent measure will not be grouped by any dimension, and will return the total value across all users (In this case: 1000 + 1500 = 2500):

order_items.order_monthusers.total_spent
12500
22500
32500

When the grain is a Model

Model users {
dimension id {}
dimension name {}
dimension email {}
measure total_spent {
definition: @aql sum(order_items.order_value) | keep_grains(users) ;; // Use the whole Model as the grain
}
}

When using the whole users model as the grain to keep, if you do not include any fields from the users model when exploring, then the users.total_spent measure will not be grouped by any dimension, and will return the total value across all users:

order_items.order_monthusers.total_spent
12500
22500
32500

Examples

Suppose that you have a dataset containing the order_items and users model:

Model order_items {
dimension user_id {}
dimension quantity {}
dimension price {}
dimension order_status {}

dimension order_value {
definition: @aql order_items.price * order_items.quantity
}

measures total_amount {
definition: @aql sum(order_items.order_value) ;;
}

measures total_orders {
definition: @aql count_distinct(order_items.order_id) ;;
}
}

Model users {
dimension full_name {}
}

You want to calculate the Customer’s Average Order Value metric with the following logic:

  1. Calculate the customer’s total orders’ value
  2. Calculate the customer’s total number of orders
  3. Divide (1) by (2)

You would want the two aggregations (1) and (2) to always be calculated against the customer, and not any other dimensions.

The metric will be defined in the ecommerce dataset like this:

Dataset ecommerce {
...

models: [order_items, users]
relationships: [
relationship(order_items_ext.user_id > users.id, true)
]

// AOV measure definition
measure customer_aov {
label: 'Customer AOV'
type: 'number'
definition: @aql
(order_items.total_amount * 1.0 / order_items.total_orders) | keep_grains(users) ;;
}

// With distributive property, you can also use keep_grains() on each measure:
measure customer_aov_dist {
label: 'Customer AOV'
type: 'number'
definition: @aql
(order_items.total_amount * 1.0 | keep_grains(users))
/ (order_items.total_orders | keep_grains(users));;
}
}

Result:

keep-01

Even when we add another dimension like Order Status, the Customer AOV metric will still only be calculated for Customers, and remain unaffected by the new dimension:

keep-02

Let us know what you think about this document :)