Skip to main content

measure

Definition

A measure is a computed numeric aggregation, which you would want to refer to in questions directly or in other measure definitions.

Usage

A measure is usually defined inside a top-level class (e.g. Order) and has the following properties:

knowledge-base/classes/Order/class.vkl
kb {

class Order {
...

measure Revenue {

/** The name of the measure so you can refer to it in questions.
* If this measure is just an intermediate definition, which should not be used in questions, use display_name instead. */
name.en: "Revenue"

/** The return type of the measure. Currently, it can be either a number (or sometimes more specifically an integer). */
return: number

/** The definition of the measure. This is written in VQL (Veezoo Query Language), which is our optimized syntax to define measures. */
definition: """
sum(this.Order_Amount)
"""
}
}
}

Let's go over some examples to understand how to use measures in Veezoo.

Examples

Basic Measures

First of all, in Veezoo, unlike in other BI tools, you don't need to define a measure to be able to ask questions involving aggregations. For instance, you don't need to have an "average order amount" explicitly defined to be able to ask about it, since Veezoo knows what average means. This also applies to totals (e.g. total order amount), counts (e.g. number of users) and even percentages (e.g. percentage of First Class orders).

However, sometimes you will still want to define explicitly the measure. The main case we see in Veezoo is when the name of the measure doesn't capture completely the logic behind it. You may also want to add a description to it and display it more prominently in Autocomplete to guide users better.

Let's start with simple examples:

kb {

class Order {

...

/** A numeric value defined in the database. */
number Order_Amount {
...
}

measure Revenue {
name.en: "Revenue"
return: number

definition: """
sum(this.Order_Amount)
"""
}

measure Avg_Order_Value {
name.en: "Average Order Value"
return: number

definition: """
average(this.Order_Amount)
"""
}

measure Number_of_Orders {
name.en: "Number of Orders"
return: integer

definition: """
count(this)
"""
}
}
}

The first thing you notice is the usage of the word this. It refers to the top-level class this measure is based on, in this case: kb.Order.

Measures with Filters

As mentioned before, a common case for measures is when there is a non-obvious logic to it, meaning it is not in the name of the measure itself. "Revenue in Germany" for instance does not need to be defined as a measure if Germany is just a country in the database. But maybe "Revenue" itself would require a flag 'is_cancelled' to be false or another filter to be set.

Boolean filters

kb {

class Order {
...

number Order_Amount { ... }

boolean is_Cancelled { ... }

measure Revenue {
name.en: "Revenue"

description.en: "Revenue only includes orders that were not cancelled"

return: number

definition: """
this.is_Cancelled = false

sum(this.Order_Amount)
"""
}

}
}

Alternatively, if the boolean filter needs to be true, we would have had this.is_Active = true.

Entity filters

What if we didn't have a boolean flag, but rather a specific entity (e.g. 'Active') that we needed to filter for?

kb {

class Order {
...

number Order_Amount { ... }

/** Possible Status: Cancelled, Active, On_Hold */
class Status { ... }

measure Revenue {
name.en: "Revenue"

description.en: "Revenue only includes orders that were active."

return: number

definition: """
this.Status = kb.Order.Status.Active # alternatively, you can also use a string directly, e.g. "Active"

sum(this.Order_Amount)
"""
}

}
}

What if you need to filter out an entity, e.g. not cancelled? Let's see just how the definition changes:

definition: """
this.Status != kb.Order.Status.Cancelled

sum(this.Order_Amount)
"""

Finally, maybe there are multiple statuses that you need to consider. For this case, we use a union:

definition: """
this.Status = union(kb.Order.Status.Active, kb.Order.Status.On_Hold)

sum(this.Order_Amount)
"""

Numeric filters

Let's say instead that to calculate Revenue, we should ignore all orders with negative Order_Amount.

definition: """
this.Order_Amount >= 0

sum(this.Order_Amount)
"""

For numeric filters, we support: <, <=, > and >=. We also support numeric operators like *, +, / and -.

Measures using other classes

What if for Revenue we actually need to filter out customers that are inactive (according to a separate class and table)?

This is how the measure definition would change:

kb {

class Order {
...

relationship from_Customer { ... }

measure Revenue {
name.en: "Revenue"

description.en: "Revenue only includes orders from active customers."

return: number

definition: """
var c: kb.Customer # think of this like the following SQL: FROM customers c

this.from_Customer = c
c.is_Active = true

sum(this.Order_Amount)
"""
}

}
}

This can be made shorter:

definition: """
this.from_Customer.is_Active = true

sum(this.Order_Amount)
"""

Now, let's go a bit wild. What if Revenue should be defined as total order amount only from customers that had at least 2 orders at any point in time.

definition: """

var c: kb.Customer

# c is this order's customer
this.from_Customer = c

var o: kb.Order # these are separate orders
o.from_Customer = c # from the same customer

# let's count these orders here for each customer
# the parenthesis are required
var number_of_orders = count(o) by (c)

# only those customers with at least 2 orders
number_of_orders >= 2

sum(this.Order_Amount)
"""

It is important to notice here that if you ask "Revenue this month", Veezoo will compute the revenue from orders this month with customers that at any point in time did at least 2 orders, not restricted to this month. This happens because we introduce a new order var o: kb.Order that does not have any of the filters from this.

Compositional Measures

Sometimes we will want to reuse existing measures in new definitions, because we depend on them or because we want to keep the code more readable.

Let's say we want to define gross profit margin in %. We will start by defining it in one measure.

kb {

class Order {
...

number Gross_Profit_Margin { ... }

number Sale_Price { ... }

measure Gross_Profit_Margin_Percentage {
name.en: "Gross Profit Margin (in %)"

return: number

# So we can see in %
unit: onto.Percentage

definition: """
var total_gpm = sum(this.Gross_Profit_Margin)
var total_sale = sum(this.Sale_Price)

(total_gpm / total_sale) * 100 # Multiply by 100 so we get a percentage
"""
}

}
}

Alternatively, you could also define it shorter as (sum(this.Gross_Profit_Margin) / sum(this.Sale_Price)) * 100.

Great. But this assumes that gross profit margin and sale price just require a sum. As we have seen before, these could be arbitrarily complex.

So let's say we have two measures Total_Gross_Profit_Margin and Total_Sale_Price. And now we will define it again using these measures.

kb {

class Order {
...

measure Total_Gross_Profit_Margin { ... }

measure Total_Sale_Price { ... }

measure Gross_Profit_Margin_Percentage {
name.en: "Gross Profit Margin (in %)"

return: number

# So we can see in %
unit: onto.Percentage

definition: """
var total_gpm = kb.Order.Total_Gross_Profit_Margin(this)
var total_sale = kb.Order.Total_Sale_Price(this)

(total_gpm / total_sale) * 100 # Multiply by 100 so we get a percentage
"""
}

}
}

The syntax to refer to a measure is similar to an aggregation function (e.g. sum, count), meaning it takes an argument. The argument is usually this, which is the top-level class with all the filters from the question.