# Using Whaly Guides

People are using data daily through their personal lives to make better decisions—from which product to eat, to monitoring exercise and health condition, and managing their personal budget. Almost everybody is already using data to make their life a better life and to take the decisions that will make them reach their goals.

But getting data adopted in your organization or team isn't that simple. You need to deeply understand where and when people need data and how they will use it, then make relevant data accessible at those key times. Everyone says they would love to be a data-driven organization, but the reality is that most companies are still in the early stages of embracing modern data and analytics.

With its prescriptive, proven, and actionable guides, Whaly guides curates the best practices and *expertise* of data driven companies to help you, your team, and your organization become more data-driven.

Depending on the scope, size, and maturity of your data project, specific guides will be more suited for your current requirements. Our set of guides provides relevant starting points for organizations, teams, and individuals. ⤵️

### Organizations <a href="#organizations" id="organizations"></a>

For most organizations, it is rare to begin with a fresh start. When deploying your Business Intelligence project, you'll encounter many existing processes and workflow where people share insights and data with disparate processes and tools. Like someone that is filling a spreadsheet every now and then to produce a report before a board meeting.

Switching your organization to a Self Service Business Intelligence platform requires a shift of culture to better centralize, govern and adopt data consumption at scale. It comes with the deployment of new processes and by assigning the proper roles and responsabilities to your team members.

Our [Core Concepts section](/core-concepts/getting-started) will help you understand how your organization should be managing your Business Intelligence project, how everything will be [layered down](/core-concepts/getting-started/data-layers-in-whaly) and integrated with your existing and future Data Stack. All that to help you deploy a robust process to deliver a proper and trusted experience to all your stakeholders.

Whether your organization is new to modern data analysis or you’ve already deployed and need to broaden, deepen, and scale the use of your data, Whaly guides allows you to take a step back to see the big picture of what’s ahead, and it allows you to zoom in on a specific topic to fine tune and improve any relevant point.

### Teams <a href="#teams" id="teams"></a>

For smaller teams or workgroups that are not part of a company-wide initiative, it is important to understand how data is used today and which skills exist among your team mates. Your initial focus should be to understand how to quickly deliver projects with a real business impact. This is why we're offering you our [Recipes](/recipes/customer-care) guides to help you bootstrap your Business Intelligence projects with a set of down to earth guides to deploy Use Cases.

### Individuals <a href="#individuals" id="individuals"></a>

Individuals will benefit from our [training guides](/training/for-viewers) that will help them understand all the capabilities of their Business Intelligence platform so that they can build, share and extract useful knowledge from their business data to produce better decisions.


# Getting started

Getting started is really easy once you've understood the core concepts of the platform. Let's have a look.

## Navigating on the platform

Using Whaly, you will be able to use three different spaces, each one with different focus :&#x20;

* The [workspace](https://docs.whaly.io/workspace/workspace) allows you to create and consult dashboards
* The [workbench](https://docs.whaly.io/data-management/navigating-the-workbench) is where you will import & model your data
* The settings is targeted for admins operations, such as managing user access and permission.

To switch between apps, you can use the app switcher available on every page of the platform:&#x20;

<figure><img src="/files/NBKiryYnKZLmFECTDn1k" alt=""><figcaption></figcaption></figure>

## Workflow for building dashboards

Creating dashboards on the platform is based on a simple workflow :&#x20;

1. **Get your analytics data** : [import your data](https://docs.whaly.io/sources/how-sources-work) into whaly and optionally [create data models](https://docs.whaly.io/data-management/workbench) to get started
2. **Build explorations** : [build explorations](https://docs.whaly.io/data-management/explorations) to start asking question questions on your data
3. **Create dashboards** : use your explorations to create [charts and dashboards](https://docs.whaly.io/data-consumption/what-is-a-report)

## Deep dive into the core concepts

Once you've understood the basis of the platform, it is recommended you read through the rest of the core concepts article. &#x20;


# Data stack architecture

A Data Stack is a set of "layers" that are processing your data through the entire pipeline from extraction to consumption. This stack is needed in order to collect, store, transform, query and offer data to stakeholders so that they can extract useful information and make better decision.

The vendors you select for the different layer of your stack will evolve based on your business needs (number of stakeholders, current data adoption, overall trust and data skills of your team) and on your data (volume, origin, quality). Don't see your stack as being a "frozen/static" setup but more an ever evolving organism.

What is important to understand is that the layers that compose the stack are independent of the set of vendors that compose your stack at a given moment.

Here is a simplified view of a common data stack.

<img src="/files/qGnjKE7GcIIAjUGYplg1" alt="A common Data stack" class="gitbook-drawing">

### Why so many layers?

The rationale behind a data stack is the following:

a. Data needs to be extracted and loaded into a "**Storage layer**" that is designed for Data blending and analysis, called a "Data Warehouse" is needed to consolidate all your data data.

b. To feed this central storage layer, an "**Extraction layer**" is needed to connect to the siloed apps of your business that are producing the data (CRM, Finance, Database).

c. The data coming from your Business Apps needs to be cleaned and abstracted before being served to end users.  This is what is achieved in the "**Transform Layer**"

d. Your stakeholders (C-Levels/Managers/Data Analysts/Data Scientists...) need the proper tools to consume the cleaned and consolidated data. As they all have different jobs, responsibilities and skills, they need different set of tools to get access to their data. This is the "**Consumption Layer**".

## Where is Whaly positioned in the Data Stack?

Whaly is your **Business Intelligence**. It is part of the **Consumption layer**. The Business Intelligence is the main data access point for your whole company. It contains a set of curated Dashboards / Questions and Explorations that are easy to use interfaces to answer quickly to most questions asked in your Business.

The **Business Intelligence** is the most adopted tool of the Consumption Layer as everyone can use it across your organization. Hence it is the cornerstone of your data consumption layer where C-Levels, Engineers, Data Practitioners and regular users meet. The other tools of the consumption layer will be specialized tool for dedicated teams (Data Scientist, Engineers, ...).

#### About Whaly integrated modelling layer and connectors

In order to help you speed up your **Business Intelligence** projects deployment, Whaly offers "enablers" in the form of a **modelling layer** and **integrated connectors**. Those "enablers" can have an overlap with some existing vendors/components of your data stack, and can be replaced when/if you need to.


# Consumers vs Builders

In a data project, the stakeholders can play 2 different roles:

1. Data Builder
2. Data Consumer

It's important to realise that each role is different as the responsibilities and tools that each will use to do their job will be different.

### Data Builder

A Data Builder is someone that is building a data product to be then consumed by the Data Consumer. Usually, Data Builders have the Data Engineers / Analytics Engineers / Data Analysts job title but depending on the Data Project, it can be someone else that have the required skills.

The perfect data product is:

* **Stable**: Numbers / charts shouldn't break without notice
* **Self usable**: Consumers should be able to discover and use the Data Product by themselves
* **Adopted**: A good product is a product that is used by its intended audience. This is the tricky part because a lot of factors can explain why a product is not widely adopted, but a product that isn't used is a useless product.

Hence, the Data Builder is in charge of building the proper data consuming experience, for its whole team. In order to build the perfect data product, a special intention should be given to the following points:

* [Building the right Models ](/core-concepts/data-modeling)that correctly clean and blend the data to define the basis of all analysis
* [Designing the proper Explorations](/core-concepts/explorations) and correctly documenting them so that people can make sense of them
* Testing data and monitoring data pipelines to be alerted on all possible issues in the numbers

In Whaly, the **Data Builders** will do the majority of their work in the **Workbench** interface.

### Data Consumer

A Data Consumer is someone that have questions and/or need to take a decision and that will use the Data Products built by the Data Builder.

The Consumer is often either a regular teammate that need data to do a proper job on a daily basis (Sales rep, CSM, ...) or an executive that needs to take strategic decisions (CxOs, VPs, etc.).

The Consumer plays an essential part in the Data as he is the main user. Without an engaged, trustful consumer, data is useless.

The consumer needs to build the proper level of knowledge to make the proper use of the Data Products that were built for him.&#x20;

In Whaly, the Data Consumers will do the majority of their work in the **Workspace** interface.


# Data layers in Whaly

Whaly offers you 4 main layers to expose your data to stakeholders. Each layer is the foundation for the next one.

Here is a diagram with the 4 data layers offered in Whaly ⤵️

<img src="/files/StXDbZRi3yQROz6wZHxq" alt="" class="gitbook-drawing">

### Why 4 data layers?

Offering 4 layers is provided the proper tools to your organization to properly **govern** your data assets and make sure that your Business Intelligence can be **maintained and remains trusted** by all data consumers in the long run.

Each of the data layer is offered with a set of tools that will enable your organization to remain in control of your Business Intelligence deployment and remain effective even in the long run.

### Layer 1 - Sources / Raw data

At the very base of Whaly Data layers is the "Source" layer. This is where data is being sourced from to be then exposed in the Business Intelligence. Think of this layer as being the "Import layer" of Whaly. Everything analyzed in Whaly will be ultimately linked to a Data Source.

In this layer, it'll be important to:

* Import all the data sources that are needed to build business insights from

{% hint style="info" %}
This layer is managed by **Data Builders** in Whaly through the **Workbench** interface.
{% endhint %}

Depending on the state of your Data Stack and of your current deployment, the Source layer will enable you to:

a. Import Data already existing in your Data Warehouse (that came from an ETL vendor or from a custom pipeline)

b. Import Data managed by a modelling layer such as dbt

c. Import Data directly from one of your Business Tools with an integrated connector of Whaly

### Layer 2 - Models

Above the Sources layer, exist a Models layer. This layer will help you clean and consolidate your various data sources to produce Models that will be analyzed and reported on by your Data Consumer.

In this layer, it'll be important to:

* Build the "Data Model" of your organization, by merging different sources and building relationships between different business entities (ex. Customers and Orders)
* Document the work that has be done to implement your business rules and definition
* Produce data in a shape that will be easily analyzed by your Data Consumer

{% hint style="info" %}
This layer is managed by **Data Builders** in Whaly through the **Workbench** interface.
{% endhint %}

Depending on the state of your Data Stack and of your current deployment, the Models layer will enable you to:

a. Import models managed by a modelling layer such as dbt

b. Build SQL based models directly from Whaly interface

c. Build Flow models with a no-code interface to bootstrap your models at the beginning of your Data Projects

### Layer 3 - Explorations / Semantic Layer

Above the Models layer, exists the Exploration layer. This is also called "Semantic Layer". This layer will be the place where Data Builders and Data Consumers will collaborate. It will be the interface designed by Data Builders for Data Consumers to enable them to make self service queries.

In this layer, it'll be important to:

* Design the proper set of Dimensions / Metrics that will empower your whole team
* Document the Self Service interface to facilitate the adoption of Data Consumers and make them autonomous

{% hint style="info" %}
This layer is configured by **Data Builders** in Whaly through the **Workbench** interface and used by Data Consumers through a dedicated query interface&#x20;
{% endhint %}

### Layer 4 - Reports

The Reports layer are where your Data Consumer will build their own dashboards and questions to suits their business needs. In this layer, they will be able to manage the look & feel of their data visualization, manage the sharing methods (pushs to external systems, sharing links, invitations).

A folder structure and access control settings will help your Data Consumers organize their Dashboards in a content hierarchy to maximize the adoption of the dashboards.

In this layer, it'll be important to:

* Design and shape dashboards and charts so that they expose the right information and help everyone take better decision
* Structure the overall content structure of your organization to make sure that people can find the insights they are looking for

{% hint style="info" %}
This layer is configured by Data Consumers in Whaly in the **Workspace** interface&#x20;
{% endhint %}


# License Mapping

License type defines the features and functionality available when using Whaly. It's important to successfully deploy your data project to assign the right licenses to the right stakeholders.

Whaly offers 3 main licenses:

1. **Builder**: Users who connect data sources, create models and define explorations
2. **Editor**: Users who create dashboards and questions
3. **Viewer**: Users who consume already existing dashboards, questions and exploration. However, everything is read-only for those.

<img src="/files/mjgkavRrN2se8KK1ysv5" alt="" class="gitbook-drawing">

A typical Whaly deployment is using the following License mapping with the organisation:

## **Builders**

### Required skills

* Understand the Data Stack architecture and how to operate its different parts
* Strong spreadsheet experience or SQL knowledge

### Typical audience

* Everyone from the **Data team** (Data engineers, Data analysts, Analytics engineers, ...)
* **Data driven individuals that are operating the data setup** of their team (SalesOps / Growth engineers / ...)

## Editors

### Required skills

* Understand how to build an effective dashboards
* Understand how to answer a question with the Metrics / Dimensions model or has experience with a spreadsheet pivot table

### Typical audience

* Any data driven individual (Engineer, Product Manager, Ops, ...)
* Team leaders that want to build dashboards to run their team

## Viewers

### Required skills

* Understand how to read a dashboard and use filters

### Typical audience

* Everyone in the company that isn't an Editor or a Builder already


# Data modeling

In this series of articles, you will learn what is data modelling and when to use it, as well as the process you need to follow in order to design and maintain data models.

{% content-ref url="/pages/V35Y3uBRCBqWVPdLbRDQ" %}
[Understanding data models](/core-concepts/data-modeling/understanding-data-models)
{% endcontent-ref %}

{% content-ref url="/pages/cnTBfyCd7PDcAXUmSJRx" %}
[Designing data models](/core-concepts/data-modeling/designing-data-models)
{% endcontent-ref %}

{% content-ref url="/pages/QIFFzHra6ROBOhx7Cg86" %}
[Maintaining data models](/core-concepts/data-modeling/maintaining-data-models)
{% endcontent-ref %}

{% content-ref url="/pages/IZxKnv15VybCbx1ifVUW" %}
[Data models best practices](/core-concepts/data-modeling/data-models-best-practices)
{% endcontent-ref %}


# Understanding data models

## What is data modeling

Modelling data is the process of reshaping your data into a new format that is easy to understand and to query for your non-expert, business users. In Whaly, models will be the foundation of your explorations.

<img src="/files/StXDbZRi3yQROz6wZHxq" alt="You can think of a data model as a transformation layer that lives between your raw data and your explorations" class="gitbook-drawing">

## Why data modeling is needed

Raw data may not always perfectly represent the concepts, business logic or definitions your company is using internally. This is often the case if you rely on SaaS data or on your own software data, as the data models are designed to make sense in the context of these applications.

By creating data models, you will hide your data complex logic to ensure that your business users all share the same definitions. This will greatly decrease the risk for wrong calculations and analysis, as well as boost your users ability to embrace data consumption.&#x20;


# Designing data models

## How to model data

Before creating a model, it's always useful to sit down with the people that will consume the explorations built over your model to understand which questions they will want to answer. By doing interview sessions, you will be able to identify :&#x20;

* The schema of your model
* The granularity of your model data
* The raw data you will use as input for your model

Once you know what your model output should be, you can use Whaly's built-in tools to build your data model :

#### Flow models

> You can think of flow models as a building block interface designed to reshape data. It's useful for data savvy users that need to build models but do not want to rely on a programming language such as SQL in order to do so.&#x20;
>
> Flow models are easy to get started with, are auditable and easily modifiable. They are a great choice for creating low to medium complexity models.

{% embed url="<https://docs.whaly.io/data-management/workbench/model-data/flow-models>" %}
To learn more about Flow models
{% endembed %}

#### SQL models

> SQL models allow you to go beyond flow capabilities, and offer the full possibility of a proven data programming language. Whaly models uses the SQL flavor of your datawarehouse.
>
> SQL models are a great choice when building complex models.

{% embed url="<https://docs.whaly.io/data-management/workbench/model-data/sql-models>" %}
To learn more about SQL models
{% endembed %}

Whether you use **Flow models** or **SQL models**, the tools you will be able to use are the following :

* **Joining or combining** data from different data sources
* **Transforming** values: renaming columns, filtering data, cleaning values, bucketing values, ...
* **Filtering** out data, either and entire line or column
* **Aggregating** or **ventilating** data in order to change the grain

Combining the tools above will give you the ability to easily reshape raw data into a new model that makes sense for your business and your users.

## Common modeling operations

When creating data models, we are often doing the following operations:&#x20;

* **Cleaning,** for example removing test values or duplicates.
* **Normalizing data**, for example, renaming values to apply a cross data naming convention, making sure data share the same units, ...
* **Combining data**, for example merging similar data into a new unique table.
* **Calculating complex values,** to simplify the most complex operations or apply your business rules.
* **Creating links** between your data model and the rest of the data your will analyze


# Common modeling patterns


# Event schema

## Introduction

The event schema is a data modeling paradigm to make data modeling simple, fast, and reliable.

The activity schema aims for these design goals

* **only one definition for each concept** - know exactly where to find the right data
* **simple definitions** - no thousand-line SQL queries to model data
* **analyses can be run anywhere** — analyses can be shared and reused across companies with vastly different data.
* **incremental updates** - no more rebuilding data models on every update

At its core an event schema consists of transforming raw tables into a single, time series table called an event schema.

## Conceptual Overview

An event schema models an **entity** taking a sequence of **events** over time. For example, a **customer** (entity) **viewed a web page** (event).

An event schema is implemented as a time series table with (optionally) a set of enrichment tables for additional metadata.

Each row in the table represents a single activity taken by the entity at a point in time. In other words, it has an event identifier, an entity identifier, a timestamp, and some related enrichment tables. The specific structure is covered in the next section.

### **Entities**

Entities are the subject, or actor in the data. Every activity in the activity schema is an action taken by a specific entity with a unique identifier

The most common entity is a **customer**, but there can be other types as well. For example, a bike-sharing company could also have a **bike** entity to analyze things like repair frequency, mileage over time, etc.

An activity schema table will only have one entity type and is typically named `<entity>_stream`. For example, an activity schema implementation for customers would be `customer_stream`, and one for bikes would be `bike_stream`

### Events

Events are specific actions taken by an entity. For example, if the entity is a customer an activity could be 'opened an email' or 'submitted a support ticket'. Each row in a table modeled by an event schema is a single instance of an activity taken by a specific entity.

Events are intended to model real business processes. Taken together, the series of event for a given entity would represent all relevant interactions that entity has had with a company.

### Metadata

Every event has metadata associated with it beyond the customer, the activity, and the timestamp. A 'viewed page' activity will want to store the actual page viewed, while an 'invoice paid' activity will store the total amount paid.

The stream tables of an event schema have a finite number of metadata columns that can be associated with each event.

## Structure

The primary benefit of an event schema is that all data is in a consistent format. This means that it requires tables with specific names, types, and numbers of columns.

### Tables

An event schema uses a single table to store all events. No new tables are created as the data evolves — any future events will create rows in the same table.

There are three types of tables in an Activity Schema

1. event stream (one per event schema)
2. entity table (optional - one per event schema)
3. enrichment tables (optional - as needed)

The activity stream table, (typically called `<entity> - Stream`) stores all events, their timestamps, the entity's identifier, and some metadata.

The entity table (typically called `<entities> - Entity`) stores metadata for each entity. For example, a `Customers - Entity` table can store date of birth, first and last name, etc.

The enrichment tables (typically called `<enrichments> - Enrichment`) stores metadata for each event types. For example, a `Orders - Enrichment` table can store the source, status,  etc.

### Event Stream

The event stream table is the primary table in an activity schema and is the only one required. It houses the bulk of the modeled data in the warehouse.

| **Column** | **Description**                                                          | **Type**  |
| ---------- | ------------------------------------------------------------------------ | --------- |
| ts         | Timestamp in UTC for when the activity occurred                          | timestamp |
| entity     | Globally unique identifier for the entity (ex: customer id)              | string    |
| event      | Name of the activity (ex. 'completed\_order')                            | string    |
| value      | Value for the activity. (ex: 1 if the customer just completed one order) | number    |

### Entity Table

The entity table stores an unlimited number of metadata columns. By convention it takes its name directly from the entity. For example, for an activity schema with an entity named 'customer', the table would be called `Customers - Entity`. For an activity schema about bikes the table would be called `Bikes - Entity`.

### Enrichment Tables

Enrichment tables serve the same purpose as the entity table, but for specific activities. They allow adding any arbitrary amount of metadata to an activity.

An enrichment table is typically named after the activity it enriches, taking the structure `<name> - Enrichment`

An enrichment table has one required columns - **enriched\_object\_id,** which is used to join it into the event stream. From there it can have any number of additional columns of any type.

| **Column**           | **Description**                                                                                | **Type** |
| -------------------- | ---------------------------------------------------------------------------------------------- | -------- |
| enriched\_object\_id | Identifier of the object that this row will enrich                                             | string   |
| feature columns      | (optional) These columns are the additional features used to enrich an activity or activities. | various  |


# Maintaining data models

## Iterating on your models

As building models can be an overwhelming  endeavour, it is important to take things one step at a time. Iterating on your models will help you mitigate some of this complexity by slicing a big work package into smaller and simpler tasks

#### Start small&#x20;

Start by creating the minimum models you will need. Select the bare minimum number of columns you need and start from there.

#### Add what's needed

When you need to add a new information to a model, you should always ask yourself the following question : Should I extend my existing model or create a new one. If you sense that adding a new column will change the purpose of your current models, you should probably create a new one. If not, you should update your model.

#### **Carefully remove what's not needed anymore**

Removing columns may be a tricky operation, as your model columns might be used in other part of the BI (explorations, relationships, drills and even other models). If you need to replace a column, we advise you start by creating a new column, update your model and it's dependencies and then remove the column. This should avoid some downtimes and mitigate risks to break downstream items.&#x20;

#### Repeat

Iteration is a never ending process, so don't be afraid to iterate every time it's needed

## To go further&#x20;

Building models in Whaly is a great way to quickly deliverer value and iterate on feedback. When some of your Whaly models are widely adopted and become a central part of your BI analysis, you should think about moving them to a more stable and testable environment, such as [dbt](https://www.getdbt.com/).

Some core functions of dbt are building, testing and documenting SQL models, as well as providing a complete version control system.

Whaly offers a direct integration with dbt cloud: you will be able to automatically import your dbt models in your Whaly environment in order to use them in your explorations and your charts.


# Data models best practices

When building models, it's always good to follow the commonly accepted best practices in the industry. However, follow them with caution, and do not let them get in the way of getting stuff done.  Here a some best practices that we have identified :&#x20;

### Create small  and reusable models

By creating small models, you will ensure that your future self or other users will quickly understand your model structure and output. By creating reusable models, you will ensure that there is also only one source of truth for every data in your company, which will be valuable in the long run

### Follow a naming convention

A lot of efforts can be spared when everything is consistent, and that's also the case in data. Try to use the same naming convention every time it's possible. A naming convention can be applied to you model names, your column names and even in the values that your models output.

### Be explicit

It's always easier to work with data when you don't question yourself about the meaning or the content of a column. For exemple, if your model output a cost in a specific currency and in cents, you should probably name your column `cost_cents_usd`.&#x20;

### Document everything

Last but not least, try to document your models by using the description feature in Whaly.&#x20;


# Explorations

{% content-ref url="/pages/lRCOx0UogJJSYfJ2fDzD" %}
[Understanding Explorations](/core-concepts/explorations/understanding-explorations)
{% endcontent-ref %}

{% content-ref url="/pages/Gn80jzcFNCvpbDPVLLi9" %}
[Designing Explorations](/core-concepts/explorations/designing-explorations)
{% endcontent-ref %}

{% content-ref url="/pages/8oQgvpifA2598trUlu0b" %}
[Maintaining Explorations](/core-concepts/explorations/maintaining-explorations)
{% endcontent-ref %}

{% content-ref url="/pages/4DMGPN0h6liKXPgluqcK" %}
[Mistakes to avoid](/core-concepts/explorations/mistakes-to-avoid)
{% endcontent-ref %}


# Understanding Explorations

An Exploration is a query interface that will empower your data consumers to ask questions by themselves and get their answers. Properly designing Explorations is a key pillar of offering a "self service" BI experience to your company.

Explorations contains a collection of dimensions and metrics, coming from multiple tables that were created in the "modelization layer" of your Data Stack. Such collections should be fined tuned to contains the proper fields and metric definitions that data consumer need in order to get answers.

A proper setup of well designed Exploration will have the following benefits:

### For Data Consumers:

* **No insight lag**: Being able to get answers fast without having to write complicated SQL queries on the fly
* **Work on cleaned/consolidated data**: Getting access to a consolidated repository of cleaned dimensions and metrics that are safe to use
* **Consistency**: Getting the same results as the rest of the team and not having to troubleshoot difference in KPIs with co-workers
* **Empowerment**: Being able to go beyond "dashboards" to answer complicated questions without having to open a ticket to the data team

### For Data Providers/Builders:

* **Governance**: By defining in a single place the definition of metrics, you don't allow every Data Consumer to invent a new way of calculating important KPIs, lowering data inconsistencies and improving organisation trust.
* **Low maintenance**: Having a single place to build / update metrics. A single metric definition change will update all the dashboards/integrations that are using it.
* **Less low value work**: Data Consumers can run their own extract / simple enquiries by themselves, freeing time for the more value added analysis.
* **Abstraction layer**: Exploration are living above data tables but are not exposing them directly, you can change everything in your Data Warehouse without impacting any Data Consumer as long as you update your Exploration to use the newly created tables.
* **Avoid invalid SQL queries being used**: Exploration are generating a safe SQL that doesn't contains errors that SQL novice can make easily which results in invalid numbers, like [SQL Fanout](/misc/sql-fanout)&#x20;

As you see, properly designing Explorations is a high stake topic.


# Designing Explorations

### What makes a good exploration <a href="#what-makes-a-goodexplore" id="what-makes-a-goodexplore"></a>

There are three things you should be optimizing for when building your explores:

1. **Correctness** Are query run through the exploration giving the proper numbers?
2. **Usability** When you design an Exploration, you’re like a Product Designer and you deliver your users an application. And when you design software, it not only has to work, ***it has to be usable***. When a user who is not an expert in your data loads your exploration, it should be immediately clear how to get the answers they’re looking for.
3. **Performance** While there may be multiple ways to get Whaly to generate a query, there will typically be one way that has the optimal performance. Having a fast exploration will lower your data consumer waiting time, increase their productivity and willingness to query your company data.

## Whaly guidelines to make powerful Explorations

### Make multiple small explores instead of a single big one <a href="#make-multiple-small-explores-instead-of-a-single-bigone" id="make-multiple-small-explores-instead-of-a-single-bigone"></a>

It's really tempting to create a single Exploration and make it grow with every new metric / use case that your Data Consumers are coming with. The power of using "Related Data" can easily make you join a dozen of tables in the blink of an eye.

But every new added table in an Exploration comes at a performance cost, making queries using it slower. Also, opening a very large Exploration can be very intimidating for Data Consumers and might scare them off.

As a rule of thumb, a properly balanced Exploration should have between 3 and 6 related tables. If your Exploration have more tables than that, you probably have to push some of the joining logic into your "Modelisation Layer" to produce Models that are more consolidated.

We generally advise to produce 1 or 2 Explorations that should be very generalist and cover most of your business domains (Marketing / Sales / Product Usage / Revenue) thanks to an "Event Model" approach. Those should be the go to Exploration for very broad questions. Only important metrics and dimensions should be surfaced in those.

When Data Consumer needs to ask more precise question and look further, there is a second range of Exploration that should be built to help them dive in on a specific topic / domain. Those should be "specialist explorations" that will contains more details over what is going on.

There is no silver bullet in Exploration design but rather a good balance between generalist and expert ones.

### Aggressively curate field lists <a href="#aggressively-curate-fieldlists" id="aggressively-curate-fieldlists"></a>

When you design an explorations, Data Consumer will try it and run queries that you never thought of previously. This is a good thing but you don't want it to become: “When I did this, the answer didn’t make sense! I don’t trust the data.”.

> **You want users to have predictable, positive interactions with the explorations you give them.**

When you deliver software, every feature is “exposed area”  it means  stuff that people will start relying upon and that can break.&#x20;

The more features, the more exposed area, the more opportunities for things to go wrong. Simple applications work better because there are fewer things to break.

In the case of Explorations, Dimensions and Metrics are features that you offer to your users. Each new one will create an entire field of new possibilities and of bugs / problems. So every time you add a new Dimension or Metric, you have to weight the pro and cons.

It's not because you can that you should expose every field of the database to your Exploration. A curated list of the most useful fields should be defined with end users to reduce the exposed area.&#x20;

It will as well reduce the cognitive load of Data Consumer when writing down Queries. The less choice, the less "analysis paralysis" you'll create.

### Document everything

Writing documentation is paramount for your Exploration to be properly discovered and used by Data Consumer. It'll decrease the chance of them to do things the wrong way, will improve their chance to run query by themselves and will improve their overall happiness.

It's really hard sometimes to document dimensions / metrics because they make so much sense for us as we build them but for newcomers or people that never worked in a given domain, it can be very hard to understand what is a given for you.

So, document everything, create dummy dashboards and question with your Exploration to demonstrate how they could be used to get business insights and make sure that all descriptions are always filled!

### Do personalized workshops / training

Whenever you build an Exploration, you are doing it for your Data Consumers. You need to train them in the most personalized way to use the tool that you created for them in order to increase adoption.

It'll be also the perfect occasion for you to gather feedback about how they want / need to consume data and what their business challenges are like. It'll grow you as a person and will give you additional insights on how you can deliver new experience for them.


# Maintaining Explorations

### Communication

As explorations are tools that you are building for your Data Consumers, it's really important to have a proper communication process with your stakeholders to keep them in the loop of any changes / enhancements that you have done or are planning to do.

As well, to create impactful exploration, you'll ask to gather from user feedbacks to make sure that you configure the Exploration in the right way.

Hence, you have to create a communication channel with your end users. Depending on how your company is structured, here are some good ideas that you can follow:

* Create an internal newsletter / Slack channel to discuss all changes related to your Explorations and send notifications to your Data Consumers whenever there is something interesting to share with them
* Keep a "changelog" document in which you log all changes done on the Explorations configurations so that people can discover all the new things and understand the changes that are happening
* Send periodically User Feedback forms / NPS survey to your Data Consumers to hear their voice and be sure to build something that they want
* Share examples of great usage of Explorations that you can produce by yourself and that you saw across your organization, this will give ideas to people on how to use the exploration you created

### Deprecation process

Whenever you have to make something disappear from an Exploration, you should follow a deprecation process in order to make things properly and give way to your Data Consumer to adapt to the change.

1. Check the usage of what will be deprecated: by checking the logs and the dashboards/question using the dimensions/metrics that will be deprecated, you will identity who will be impacted the most and you should priorize them in your communication to be sure they understand what will happen
2. If needed, design a "migration strategy/alternatives" for Data Consumers to help them understand how they should adapt their workflows
3. Mark the dimension / metric as being DEPRECATED in its name in the Exploration to avoid future use and to make sure that people will migrate their queries to the proper dimensions / metrics that are not marked as deprecated
4. Define a deprecation schedule, with the date of the deprecation and the date of the final deletion of the dimensions / metrics. The deprecation period length should give enough time for Data Comsumer to migrate their work while still being short enough to move quickly
5. Communicate clearly to all stakeholders the deprecation schedule, the reason behind the deprecation and make sure that they got the message
6. A few day before the "delete date", make sure that people have done what they needed to let you delete what you should
7. At the communicated date, delete the dimension / metric and log its deletion in the exploration changelog

Depending on your organization and to your current adoption rate, a more lightweight process could be followed, but keep in mind that people need security and stable interface to make productive work.

Breaking their workflow will produce frustration and will lower their trust into your data solution.

### Update process

Similarly to the deprecation process, whenever a change of calculation of a metric or of how a dimension is build, a proper process has to be followed so that you onboard people on the change.

### Refining documentation

Documentation is a never ending story, every time that someone ask a question to your support, check if the answer was present in the documentation.

* What is properly explained?
* What it easy to find the answer?
* Was the documentation available to the Data Consumer when he got the question?

The documentation is layered across different interfaces and each one should be properly filled to ensure a global adoption of your Explorations:

* **Naming**: The naming of your Exploration / Dimensions / Metrics is really important as it's the first thing that people see. It should use a vocabulary that everyone understand accross your organization.
* **Metrics / Dimensions description**: How things are calculated and how they should be used, alongside their business meaning and impact should be properly documented in the Metrics / Dimensions description.
* **Exploration description**: How the overall Exploration is positioned in the global context of your organization should be added in the Exploration description. Some examples of the questions that this Exploration can answer should also be added to help people understand how they could use it and if it's for them.
* **Global vocabulary / glossary**: You should maintain somewhere a list of your Business concepts definitions. Like "What is a Sales Qualified Lead"? This should be shared across your entire organization and will help people understanding the data

### Get user feedback

The more you talk to your users, the best insights you'll have on what are their data needs. As well, they will indicate you the proper direction to build the best Exploration that will increase their Data Adoption and increase the impact of Data analysis across your organisation.

Some ideas on how to collect user feedback:

* After having trained someone on how to use an Exploration, ask for their direct feedback
* Send polls / forms to your most frequent users
* Create a "Submit ideas" process to be share across the organization so that people can share their ideas on how to make everything progress


# Mistakes to avoid

### Remove metrics / dimensions without proper warnings

The explorations that you build are used by your team mates to answer their questions. If you do brutal changes to how they can ask their questions, like by deleting a dimension or a metric without notice, it can have a bad impact on their daily routine.

In order to properly deprecate a dimension / metric from an exploration:

* Check its usage:
  * In dashboards and questions
  * In the query log for people that are using the Exploration in an adhoc manner
* Contact the regular users of the exploration to warn them about the upcoming deletion of the Dimension / Metric
* Mark the dimension / metric as being DEPRECATED in its name to avoid future and to make sure that people will migrate their queries to the proper dimensions / metrics
* Log the deprecation notice in your Exploration description with the start date of the deprecation and the date of the upcoming deletion
* At the communicated date, delete the dimension / metric and log its deletion

{% hint style="info" %}
As this process can take time, it's best to do it for multiple metrics / dimensions at the same time to save time.
{% endhint %}

### Creating dimensions / metrics duplicates

Whenever you create a new dimension or metric, check beforehand if the exploration doesn't already contains the same configuration.

It's really easy to just "create" dimensions / metrics without checking if they don't already exists with a different name, but it ends up creating duplicates of the same information.

When presented with duplicates, end users are unsure of which one to use and it increase the maintenance cost of the exploration as you now have to maintain 2+ configuration for the same thing.


# For viewers

As a viewer on Whaly, you will be able to consult the reports of your organization and use the available explorations in order to ask questions on your data.

If you are new to the platform, it is suggested to read the following articles :&#x20;

* What is a [report](https://docs.whaly.io/data-consumption/what-is-a-report)
* What is an [exploration](https://docs.whaly.io/data-management/explorations)

## Navigating the workspace

The first thing you will have to do is [login](https://app.whaly.io/auth/login) to Whaly. If you do not have an account, you should contact an organization admin in order to get invited to the platform.

Once logged in on the platform, you will need to select your organization, which will open the workspace page. You can use the left sidebar to navigate in your org folders. You will see the different reports you have access to. On the top of the left side panel, you can access to the explorations that your team has built.&#x20;

<figure><img src="/files/4QkE6paiOOmzxhBPlnUb" alt=""><figcaption></figcaption></figure>

## Consulting and using reports

From the Workspace, click on a report or a question to open it. When consulting a report, you can:&#x20;

* See the charts that are available
* Click on any charts to drill into the raw data
* Use the dashboard filters, if any are configured on the report
* Explore from a chart to modify the current question

## Exploring data

As a viewer, you can also explore the data using the explorations your team has built. Exploring will allow you to ask your own questions on your team's data. There is two ways to access an exploration :&#x20;

1. **From the workspace menu**, click on the "Explore" menu, then select the exploration you want.
2. **From any chart** of a report or a question, click on the ⋮ icon in the top right corner and then click on explore from here

<figure><img src="/files/HTCiyaFdc4jLGqrNwqOH" alt=""><figcaption></figcaption></figure>

### Structure of the exploration page

<figure><img src="/files/AHl16dMWwTHwBfgNx0e7" alt=""><figcaption></figcaption></figure>

The exploration page is structured with three panels:

1. On the right, you will find :&#x20;
   1. The **query builder,** where we will use the measures and dimensions we have at our disposal in order to ask a question on our data
   2. The **chart options**, allowing use to customise the look of our chart. The options will change based on the type of chart you want to display
2. On the middle, you will find :&#x20;
   1. The **query visualisation** : this is where you query will be turned into a chart
   2. The **breakdown** where you will find the aggregated data that was used to build a chart
3. On the left, you will find the **exploration content**. It contains [metrics](https://docs.whaly.io/data-management/explorations/metrics) (in blue), a [dimensions](https://docs.whaly.io/data-management/explorations/dimensions) (in green).

### Using the exploration page

Using the exploration page is easy. You can compare it to creating a dynamic table in a spreadsheet.

<figure><img src="/files/xbh8r7DrazBI5FEIYeJI" alt=""><figcaption></figcaption></figure>

On the exploration page, you can ask question by :&#x20;

* Drag and dropping measures from the left panel into the chart or the "Measure" section of the query builder
* Drag and dropping dimensions from the left panel into the chart or the "Group by" section of the query builder
* Drag and dropping time dimensions from the left panel into the "Using time" section of the query builder: this is useful when you want to group this by a time period or if you want to see the data in a specific date range.
* Drag and dropping dimensions or use the "Add a filter" button to filter the data based on dimensions values

&#x20;When you are satisfied with the changes you've done on the query builder, click on the "Run Query" button to load the new chart

You can change the visualisation type using :&#x20;

* The chart type button on top of the query builder. Here you will have three choices :&#x20;
  * **Metrics** to create charts that are either plain numbers or gauges.
  * **Categories** to create any other type of charts (funnel, maps, scatter plots, ...)
  * **Timeseries** to create charts that are able to group your metrics over time (line charts, bar charts, table charts)
  * Change the visualization type using the selector on top of the query builder

Finally, you can change the chart options in the right panel in order to customize the look of your chart.&#x20;


# For editors

As an editor on Whaly, you will be able to create reports for your organization by using the available explorations in order to ask questions on your data.

{% hint style="info" %}
Before starting this training, you must complete [the training for viewers](/training/for-viewers).
{% endhint %}

## Creating reports

To create a report, go to the workspace, open a folder and click on the "Create a new report" button. This will create a empty report in your folder.

<figure><img src="/files/7wPVfWmgOBhCC5AYMCfg" alt=""><figcaption></figcaption></figure>

### Adding charts to a report

In order to add a chart, you will need to create it in an exploration (refer to [the training for viewers](/training/for-viewers) if you are unclear on how to use an exploration). There is multiple ways to access an exploration :

#### From the report

Click on the edit button on your report and click on the "Vizualisation" button in the action bar. From there, click on the exploration you want to use. In the exploration panel, create your chart. When you are satisfied with your chart, click on the "Add tile" button in the top right corner. The chart is added to your report!

**From the explore menu**

In the workspace menu, click on Explore, and the select your exploration. Create your chart. When you are satisfied with your chart, click on the "Save" button in the top right corner of your page

**From an existing chart**

If you need to copy an existing chart from another report, go to this report and click on "Explore from here" on the chart you want to copy. From there, you can click on "Add to report" button in the top right corner, select your report and click on save. &#x20;

### Adding text to a report

Adding text to a report is essential to bring structure and context. From a report :&#x20;

* Click on the edit button
* Click on the "Text" button in the action bar
* Type your text and click on save.

<figure><img src="/files/0lhgpvIeMafZ2T9CWdID" alt=""><figcaption></figcaption></figure>

### Adding filters on a report

When you create a report, you may want to let users apply their desired filters. In order to add filters :&#x20;

* Edit the report
* Click on the "Add a filter" button in the action bar
* Configure your filter :&#x20;
  * Give your filter a name
  * Select the dimension you want to filter on
* Click on save

<figure><img src="/files/JLrRlAeTYzatPh0dIJGx" alt=""><figcaption></figcaption></figure>

## Creating questions

Questions allow you to save a single chart in a workspace folder. You can create a question from an exploration page by clicking on "Save as a question".&#x20;

## Sharing your work

Data is the most useful when shared with others. Whaly offers multiple ways to share you data with others : &#x20;

* [**Invite your colleagues**](https://docs.whaly.io/organization/manage-access-to-your-organization#granting-access-to-your-org) to use Whaly by creating a user account for them
* [**Create sharing links**](https://docs.whaly.io/data-consumption/dashboards/share-a-report-by-link) on your reports to share them to your customers or partners.
* [**Push your data**](https://docs.whaly.io/workflows/push/configure-a-push) in external tools such as Slack, google sheets and others tools to improve data adoption

Regardless of the tools you choose to share your data, we always recommend you train your users  when them new reports to make sure they get a full understanding of your work.&#x20;

## To go further

If you want to learn more about the reporting creation, head over to our product documentation. You may be most interested in the following articles, but don't hesitate to dig through the rest of the articles :

{% embed url="<https://docs.whaly.io/data-consumption/dashboards>" %}

{% embed url="<https://docs.whaly.io/data-consumption/questions>" %}


# For builders


# Setting up the training material

**Objectives:** in this section, you will be guided on setting everything up in order to start training.

{% hint style="info" %}
This steps are usually done on your behalf by Whaly's team before the first onboarding call. You should just check that everything is set-up properly before heading on to the next article :relaxed:
{% endhint %}

## High level plan

1. Connecting the "Jaffle Shop" source
2. Creating our base exploration
3. Creating our base report
4. Adding our first chart

### 1. Connecting our Jaffle Shop source

The first thing we will need to do is connect our training source. In order to do so, simply head to the source catalog and click on the `Jaffle Shop (Training material)` source. Then, click on the `Connect` button.&#x20;

<figure><img src="/files/NG3pvARdVOCZ6s4IIXyy" alt=""><figcaption></figcaption></figure>

The Jaffle Shop source is composed of three data tables:

* Customers
* Orders
* Payments

A customer has between 0 and n orders. An order has a customer, and between 1 and n payments. A payment is linked to an order.&#x20;

![Jaffle Shop database schema](/files/MnUqIrMSe08L2koV1xu6)

### 2. Creating our training exploration

In order to create our training exploration, head to workbench, then create a new exploration from a template. Select the Jaffle Shop (training) template. Then, simply click on "use template".

<figure><img src="/files/ucsTlkRvS1FPfLfYVRs6" alt=""><figcaption></figcaption></figure>

The following exploration will be automatically created in your workspace :&#x20;

<figure><img src="/files/ZZpXBu9gmWMG505Ho7lP" alt=""><figcaption></figcaption></figure>

### 3. Creating our base report

In order to create our training report :&#x20;

1. Head to your workspace. Click on "Create a dashboard", give it a name (Training for example) and a folder, then and click on "Continue"
2. Add a text tile with the following text :&#x20;

> Exercise:
>
> 1. \[New Chart] Add Order breakdown per Order status
> 2. \[New Chart + New Metric] Add the payment amount paid with gift cards per month
> 3. \[New dashboard filter] Filter the dashboard on a customer
> 4. \[New model + New Exploration+ New Chart + New Drill] Add the number of customers per number of orders

<figure><img src="/files/hcMHYExwo6GohvApb5AD" alt=""><figcaption></figcaption></figure>

### 4. Adding our first chart

The first chart we will add is the number of order per week. To do so:&#x20;

1. On the report, click on Edit
2. Click on the Add chart button
3. Select the "Order" exploration from the list&#x20;
4. Then, reproduce the following query :&#x20;
   1. Drop "number of orders" and "order date" in the chart.
   2. Select the line chart under timeserie
   3. Set the time to "all times", and group by week
   4. Click on Run query, and display as a bar chart
   5. Click on "Add tile" to add the chart to your dashboard

<figure><img src="/files/uiWjVDynxMFLZWFHgPp4" alt=""><figcaption></figcaption></figure>

Our dashboard should look like this now :&#x20;

<figure><img src="/files/J06AnTldxsL1U5JSh2mr" alt=""><figcaption></figcaption></figure>

That's it, we are now ready to work on the training exercices. Head on the the next step in order to deep dive on charts creation :diving\_mask:


# Creating a chart

**Objectives:** in this first exercise we will learn on to duplicate a tile, how the exploration page work and how to create basic chart.&#x20;

### Duplicating a tile

In the first step we will duplicate our "Order per week" tile. To do so:

1. Open the Training report
2. In edit mode, click on duplicate tile.
3. On the new tile, open the submenu and click on "Edit tile"

<figure><img src="/files/k3KNLjjlCC1qg4MMdgST" alt=""><figcaption></figcaption></figure>

### Using the exploration page

We are now on the exploration page. You can think of this page as the dynamic chart screen in excel. This page is structured with three panels:

1. On the left, you will find the **exploration content**. It contains [metrics](https://docs.whaly.io/data-management/explorations/metrics) (in blue), a [dimensions](https://docs.whaly.io/data-management/explorations/dimensions) (in green).
2. On the middle, you will find :&#x20;
   1. The **query visualisation** : this is where you query will be turned into a chart
   2. The **breakdown** where you will find the aggregated data that was used to build a chart
3. On the right, you will find :&#x20;
   1. The **query builder,** where we will use the measures and dimensions we have at our disposal in order to ask a question on our data
   2. The **chart options**, allowing use to customise the look of our chart. The options will change based on the type of chart you want to display

### Creating our number of order by status chart

The exercise wants to create the following chart :&#x20;

> Add Order breakdown per Order status

To do it based on our initial chart, this is quite simple:&#x20;

1. Group your visualisation using the "Status" dimension
2. Change the chart type to "Stacked bar chart" under timeserie
3. Click on run query
4. Click on update tile

<figure><img src="/files/3Hg3JGw2DkgU3LH3vdti" alt=""><figcaption></figcaption></figure>

That's it :tada: Our chart now represents the number of order grouped by week and by order status. Let's move on the next exercise to learn more about metrics and calculated metrics.


# Using and editing explorations

**Objectives:** in this exercise we will learn how to filter a query, create a metric, create a calculated metric and use the chart formatting options. In this article we will learn about:&#x20;

### Filtering a query

In this exercice we want to :&#x20;

> Add the amount of payment paid with Gift cards per month

Start by duplicating the initial tile from the training dashboard, then:&#x20;

1. Replace the measure "**Number of orders**" with "**Total payment amount**"
2. Add a filter on the query editor: Payment method equals gift card

<figure><img src="/files/gbbDuSZYjzW6LS96i66i" alt=""><figcaption></figcaption></figure>

Great, now we can see the amount paid each week by gift card. However, having a filter at the chart level is not optimal as it does not allow other users to easily re-use this metric.&#x20;

### Creating a filtered metric

We will create a new metric corresponding to the payments with gift card:&#x20;

1. Edit your exploration by clicking on "Edit in workbench"
2. Create a new metric under the payment table&#x20;
   1. The metric should be a sum of the column "amount"
   2. Filter on payment method **gift\_card**
   3. Give your metric a name: **Payments with gift card**
   4. Set the metric type as number, and add $ as a suffix

<figure><img src="/files/6ThvDJPAZlc4898OQGn5" alt=""><figcaption></figcaption></figure>

Now we can add our new metric to our chart and run the query. We can see that the two measures are displayed, but are equals.&#x20;

This is because we still have the filter at the chart level. Let's remove it and run the query again: now we can see both the total payment amount and the total payment amount by gift card side by side.

<figure><img src="/files/D27KvVROdB7HeyZx16cF" alt=""><figcaption></figcaption></figure>

### Creating a calculated metric

Now imagine we want to add the percentage of payment made by gift card on our chart. To do so :&#x20;

1. go to the exploration edition page in the workbench
2. click on the + sign next to Payment in the left side panel
3. click on Add a calculated metric
4. In the formula editor, type :&#x20;

> Payments with gift card / Total payment amount

Give your metric a name, for example : %age of payment amount with gift card, and set the type as percentage in the advanced settings. Save your metric. Now add this metric to the chart and click on run query.&#x20;

The metric is added to our chart, but is too small to be visible. In order to fix, we will need to edit our chart options.

### Formatting our chart

In the right panel:&#x20;

1. Toggle "**labels**" to display labels
2. Enable the right axis
3. Assign the **% of gift card payment** to this right axis, and change it's type to **Area**

<figure><img src="/files/jLqPGKzvyFF5diFTsxSO" alt=""><figcaption></figcaption></figure>


# Filtering a dashboard

**Objectives:** in this exercise we will learn how to add filters to our dashboards

On your dashboard, click on the filter icon then :&#x20;

* Give your filter a name, for example : Customer
* Select the dimension to filter on, here we will select the customer first name

<figure><img src="/files/tfp5HcuDc0LXYFZi3t15" alt=""><figcaption></figcaption></figure>

You can try filtering on a specific customer to see the results !


# Creating explorations and models

**Objectives:** in this exercise we will learn how to create an exploration, and how to create and use models

### Creating an exploration&#x20;

In this exercise we need to:

> Add the number of customers per number of orders

Until now we were using the Order exploration, that was getting data from the order table.

Here, we want to have a chart representing the number of customer per number of order. Some customers may not have placed an order, and we want to count them. So we will need to start our exploration from the customer table and group our customer per number of order.<br>

Let's create our exploration :&#x20;

* Head to the Workbench
* Click on the + the "Add an exploration"
* Select your Jaffle Shop customer table

<figure><img src="/files/YsnlwrUXKm7kbcFX1eLX" alt=""><figcaption></figcaption></figure>

On our new exploration, we need to have :&#x20;

* The number of customers as a metric
* The number of orders as a dimension.

This number of order columnis not available in the customer table, but we know we have the data in our database. We have a modelization problem, meaning that the data we have is not in a format that allows our analysis. We will create a data model to overcome this problem and create our chart.

## Creating our data model

In the workbench, go to the dataset tab and create a new model. We will create a flow (no-code) model that will start from the customer table of Jaffle shop :&#x20;

<figure><img src="/files/864nW68O6JHv53R649Kj" alt=""><figcaption></figcaption></figure>

Now we can go the the configuration tab of our model to add calculated columns to our Customer table.

For example let's create a full\_name column that will contain the first name and last name of our customer. We will use the CONCAT formula in order to do so :&#x20;

<figure><img src="/files/Ar5fiMdlBJXqGq8htfK5" alt=""><figcaption></figcaption></figure>

To add the number of order per customer, we will use a rollup column (which consists of a lookup that matches multiple results in a related table and aggregates these results) :&#x20;

<figure><img src="/files/s2lEceMGiz2jDyX2peJ6" alt=""><figcaption></figcaption></figure>

That's it, now we just need to save our model before being able to use this column in our exploration. To do it, set the output on the last step and click on save.

## Updating our exploration

We will need to update our exploration to make it use our new "Customer model" instead of the raw customer table. To do so :&#x20;

* Go to the exploration
* Click on the customer table, and replace the raw data table with your model
* Create your dimensions and metrics

<figure><img src="/files/OOUZHrtcK89bpjp9j0WP" alt=""><figcaption></figcaption></figure>

## Creating our chart

We can now create our chart using these metrics and dimensions

<figure><img src="/files/W72KBxuYIZ0pRNNUGl3i" alt=""><figcaption></figcaption></figure>


# Use cases


# Billing / Invoicing

#### Shared dashboard to external accountant

When using an external accountant to generate invoices, Whaly users are using a shared dashboard to communicate on the invoices that should be created.

This way, the communication with the external accountant is automatic and the information is always live, the customer is no longer having long chains of emails to ask for each invoices to be created.

#### Matching invoices between billing system and the CRM

When invoices are generated manually, it’s common that some are slipping away for many reasons (people forget, holidays, people are swamped with work and invoices are not the priority, etc.).

However, when sending the invoices late, the risk of never getting the money increase. It’s also hard for the customer when he is billed later as sometimes he didn’t foresee the cash need in advance and it can get tricky, especially with small companies.

That’s why Whaly customers are doing a reconciliation between their CRM (what **should** be billed) and their invoicing system (what **have been** billed) to identify the discrepencies and alert the proper team members to ask them to issue the invoice that they forgot as soon as possible.


# Customer success

#### Dashboards to animate customers reviews meetings

Customers Success teams are using Whaly to build dashboards reporting their customers metrics and are using those to drive “Customer review meetings / Quaterly Business Reviews”.

Sharing a dashboard that surface the usage of your product to your customers is a great way to orient the discussions and be sure to not leave any topic on the table.

It brings more awareness to the customers about their current usage (they know it less than what you would expect) and is a great tool for the customers success team so that they can ask the proper question to understand how the product is being used, why, and how to boost its usage and identify expansion opportunities.

#### Fraud detection

When having a lot of users, you always will find some that will try to profit from your company. Rather they find a hole in your system to make you pay them or they benefit too much of a service and go beyond limits.

That’s why some Whaly users have “suspicious activity” dashboards to be able to identify any customer misbehaviour and investigate on them.&#x20;

Maybe it’s something OK and you learned something new, and maybe the user account need to be suspended / blocked, but the sooner the customer success team can identify those troublemakers, the better your organisation is.


# Fundraising

#### Investor Data Room

When running their fundraising rounds, Whaly users are reporting that they are building a set of dashboards that they share with their investors to communicate their metrics.

This helps them save time as they can simply expose the metrics that they already track.

It’s also a good sign that they give to their investors that they are on top of their data and know how to report on them in a single dedicated tool.


# Marketing

#### Supervision of the delivery of external marketing agency

Relying on an external marketing agency is a great way to delegate operational work (campaign management, landing page design, etc.) to focus on your core operations, however it comes with many challenges.

When doing so, it’s not easy to understand what the agency is doing, the only available information is what the agency is telling you and it’s hard to know if there are things that your agency is not telling you. Most often, the agency is biased for different reasons.

Whaly users that build an agency supervision dashboard can ensure that the agency is doing the work they promise to do and raise alerts whenever there is something weird coming up in their delivery.

It’s a great tool to build trust in the relationship with the external marketing agency, it provides more control on what is being done and more knowledge on how the marketing efforts are working and what should be improved.

#### Identify the campaigns that generate no revenue

Standard Marketing reports that you find in all marketing platform are tracking are good, but they are focused on the “top of the funnel”. This means that the “conversions / actions” that many people track in their Marketing dashboard are things like “a form was completed”, “a sales call was booked”, etc. Those are easy to track and happens quickly after the campaign is run, but they don't  bring more revenue right away.

It is especially true for company with a long sales lifecycle.

Whaly users that are bridging their marketing analysis with their “bottom of the funnel” (e.g. CRM data: Deal Won / Revenue) are realising that some marketing channels have very low conversion rate in their sales funnel and are bringing a very small revenue in the end while the initial conversion was promising.

Whaly users using those analysis were able to give the proper feedback to their marketing team and re-orient their marketing strategy and channels to more “revenue impactful” ones.

#### Customer Acquisition Cost per channel for budgeting / business planning purposes

When planning the marketing budget for next year, Whaly users are finding it really useful to analyse their past year performance, understand the channels on which they want to focus and the associated customer acquisition cost.

Alongside their growth objective, this is helping them plan how much marketing budget they will need.

It’s giving them better control over their budget planning and they can do it in an efficient manner based on what they already have built instead of creating single usage spreadsheets.


# Partnerships

#### Partnership review + Revenue sharing

Whaly users that are relying on a network of partners (influencers, software companies, consultings partners, …) to attract leads are creating Partners Portals to share informations to their Partners to report on the number of passed leads, their status in the pipeline and when applicable, the revenue share that should be retributed.

As a great Partnerships are built on trust, sharing those informations is really useful to strengthen the relationships with the Partners and remove the need for manual extracts from the partnership managers.


# Product

#### Churn analysis

When customers are going away, it’s always important to make sure that we learn something. That’s why Whaly users are tracking their Churn and more specifically the rate at which it goes and the most common reasons.

This is important to raise alert if the rate goes up or to give feedbacks to all the company teams about what they could have done better to prevent the customer from churning.

From product feedbacks to customer success improvements, there is always something to learn from a churn.

#### Product Margin analysis for marketplace

For business that are buying and reselling goods or services coming from multiple vendors (as known as marketplace), it’s important to analyse the margin at the item level to understand which offer is the most profitable.

Based on this information, the “best margin” products can be given a better place in the marketplace to boost revenue.

For marketplace where the customer can negotiate deals, it’s also important to check that the level of margin is staying healthy, which is not trivial when there is a lot of proposed goods and services in the marketplace.

That's why Whaly users that run marketplace are building dashboards to report on their margin level per offer and product to catch any issue and identify opportunities.

#### Satisfaction score tracking

Whaly users are tracking their users satisfaction score to ensure that the overall satisfaction is going good and break it down by product / feature when they scale so that they can know what to improve and priorise in their product roadmaps.

#### Feature usage

Whaly users are building tools to understand the deployment of each feature in the customer base and check its usage rate.

It’s useful when getting product feedbacks to weight them with the actual usage of the feature to priorise the feedback.

When building the roadmap, it’s also a key information to check if the feature is being as used as your are expecting. Additional work to better present the feature or make it self discoverable might be needed and planned in the product roadmap to make it a success.

There is no point in building features and missing the final steps to make it used by a lot of users, don't be the Product Manager that launch features and product but doesn't focus on making them adopted.


# Sales

#### BDR campaign preparation when reaching out to prospects in the same region

For companies that sell to business that have a strong local presence, such as stores or industrial sites those businesses have a strong local network. They know very well the other businesses that are near them (same city, street or industrial zone).

Hence, when preparing a local BDR campaign, it's important to select the testimonials of customers that are near the campaign target to improve the efficiency of the testimonial.

That's why people are building maps of their current customers and share it with their BDR team so that they can select the best customer to cite in their pitch during their campaigns.

**Sales tour preparation**

For some sales cycle, organising a sales tour that takes prospects to current customers location to show them in situ how the solution is working can be game changer to close deals.

In order to efficiently organise such tours, the sales team need to have an up to date view of current customers, of their engagement level and satisfaction on a map. This way, the sales team can easily identify, for a given prospect, the best customer (e.g. close and happy) to visit during the tour.

#### Weekly sales review

The weekly sales review is the key ritual for the sales team to communicate its common goals, current progress and talk about what is happening. Sales teams that are doing it with a dedicated dashboards find it easier to communicate on the topics that matters and keep everyone engaged.

#### Pipeline conversion rate analysis

Most Whaly users are tracking their conversion rate per stage in time to be sure that their Sales process is becoming more and more effective in time.

As the Pipeline performance is always changing (new marketing budgets, angles, new go-to-market, new employees) bottlenecks can be created very fast and the faster the corrective actions are taken (sales coaching, change of pitch / demo, adjustment of the sales process, new hires to do, etc.) the better the overall pipeline throughput will be.

Whaly users monitoring their conversion rate are reporting that they can identify issues in under a week using their dashboards while it would have taken them months before. It's helping them to reach their Sales quota.

#### Pipeline health monitoring

We all love to see a big booking volume in our pipeline stages, but reality is that a lot of opportunities are slipping away because we forgot to engage them enough.

Whaly users that monitor the Deal Age of their deals in their stages can act more quickly to prevent them from going stale and are increasing their conversion rate.

They are bringing more revenue thanks to this analysis as their pipeline is getting healthier. They can ping the Sales rep that are letting their Deal untouched more easily.


# Strategy

#### Customer Segmentation analysis

Whaly customers are segmenting their customer base (ex. VIP / Regular / Low tier) and then check all their key metrics for each segment:

* LTV
* Acquisition cost
* Current portion of the revenue vs the total revenue
* ...

Those strategic analysis is giving them a good set of indicators to decide to focus on a specific segment that show great potential or to de-priorise/drop customers segments that bring low revenue.

Whaly users have used such analysis to change their growth strategy focus on high margin customers segments and drop the customers that were slowing them down.

#### MRR report and cohort expansion

For SaaS companies, building their MRR report is giving them a better vision over their most important metric which is tied to their revenue and valuation.

Having a cohort view let Whaly users checks that they know how to “expand” into existing customers which is a key element of their growth.


# Communication

#### Weekly team meetings

During weekly team meetings, team leaders using Whaly likes to present a dashboard as their communication support. This dashboard help them discuss the following topics with their team:

* Which team member is on top and should to be celebrated
* In the opposite, which team member could get a little help as they are lagging behind
* Is the team level of work increasing or decreasing
* What is the impact of the team work

This is something very popular among Sales team for example.

#### Daily/Weekly digest on Slack

Every week (or even day!) someone on the team is getting data from dashboards and build the current “narrative” of the team and push it on Slack with the attached screenshot of the main KPIs mentioned. For example, it can be giving the number of calls done so far in the week and encouraging people to keep pushing as the team is on track with its objectives.

This is giving a good “async” pulse on where the team is at and keep everyone engaged, even when they couldn’t attend a weekly meeting.

#### TVs on the wall

A lot of Whaly users reported that they have Whaly dashboards on TVs screens around their offices.

Putting rotating dashboards on the room TVs is a great way to push for dashboards awareness and to communicate the metrics to everyone in the team.

It helps awareness of what the company is keeping track of and give an easy way for everyone to always have a good view of where the metrics are at.

It’s also a good way to set-up a data culture as any new employee understand in the first glance that the company run on top of its data.

#### Notion/confluence pages for each dashboard

Whaly users that relies a lot on their knowledge management system such as Notion for their internal communication are creating a “Dashboard gallery” section with direct embeds to share dashboards with anyone having access to Notion in their team.

As the dashboards are embeds natively integrated with Notion, people can change filters and update the dashboards metrics directly from Notion without having to switch between tools.

The Notion page can contains additional information such as who is in charge of the dashboard, associated objectives and give some more context of how it’s being used.

#### Newsletter

Whaly users are sending newsletter to their entire team periodically (monthly / quarterly) to give an hindsight of what are the latest progress.

Whaly users that do so are extracting some metrics from dashboards in order to include them in their newsletter to be as data oriented as possible.

It’s a good way to communicate company metrics to people that are not accessing the dashboards directly.

**Dashboard integration in your CRM**

Some audience such as the Sales people are heavy users of their CRMs (Hubspot, Salesforce) so in order to deploy analysis to them, Whaly users are embedding Dashboards and Charts directly in the CRM interface.

This way, the analysis done with Whaly can be distributed in the daily workflow of the Sales audience without any friction and it improves the adoption rate!

**Dashboards integration in your Product / App for your customer facing reports**

Whaly users that built strong internal reports naturally want to share them with their customers. When they have developped an interface/product on which their customers are connecting, they are integrating Whaly dashboards directly inside their applications to distribute their dashboards to their customers.

As the dashboards are already existing in Whaly, doing it is saving precious engineering time from your tech team and is lowering your time to market on building your reporting features.

As Whaly dashboards are flexible and lightweight to configure, it's giving you a good way to prototype your dashboards in no time and iterate like crazy instead of having a slow iteration speed due to your engineering constraints.&#x20;


# Tips

#### ChatGPT

Whaly users are reporting the usage of ChatGPT to help builders to generate SQL queries to build models faster in the workbench.

Some users even built a tool to feed the schema of the tables into their ChatGPT request so that ChatGPT answers are more accurate.

#### Data coaching

Whaly customers with a small data team (<3 people) are taking a “Data Mentor” that give 1h per week and find it a great way to speed up the team learning and deployment of the use cases. Those mentors are helping on the tech side as they have a good knowledge of the existing tooling (dbt, SQL, …) as well as the effective processes and organisation to run a data team efficiently and maximise the impact of the Data team.

#### Objectives tracking

Many Whaly users are importing team and individual objectives on their charts to show the completion rate of their goal. This is helpful to set:

* Daily/Weekly cadence objectives
* Quarterly strategic initiative


# Customer care


# How to build a 360° customer dashboard

Do you have a lot of customer data spread across your internal tools? Follow this guide to discover Whaly's best practices to centralise all this data in a single dashboard.

**Objective:** Understanding your customer's activity throughout your entire business can be a difficult task, as  data is always spread across different tools such as a CRM, an analytics solution, a support software, and so on.

As a data analyst working with Business Developers, Customer Success Managers and Support Specialists, you probably need to answer these questions :&#x20;

* What is the total revenue of our customer **Acme Corp**?&#x20;
* What usage **Acme Corp** had on our software?
* How many conversations did **Acme Corp** had with our support team?&#x20;
* ...

This implementation guide will give you the keys to build a single interactive dashboard that will answer these questions for each one of your customers. You will also learn how to structure your explorations in order to make the most of it.

## A bit of background

In this guide, we will work as a data analyst for :office: Jaffle Soft, a SaaS company. We will build a dashboard used by our team to follow metrics about:

* **Hubspot**, your CRM software
* **Intercom**, your support tool
* **Segment**, your analytics solution

Our dashboard will be able to filter its metrics on our customers and will show the following charts:&#x20;

* The lifetime value by customer
* The number of conversations opened by customer
* The number of login by customer

## High level plan

1. Creating our explorations and charts
   1. &#x20;Hubspot
   2. Segment & Intercom
2. Filtering our dashboard
   1. Adding a customer filter
   2. Adding a date Filter
   3. Using our dashboard

## 1. Creating our explorations and charts

When we build a dashboard, the first thing we need to do is build the explorations. When working with disparate tables and objects, the rule of thumb is:

{% hint style="info" %}
Create one exploration for each table that is used as a metric in a dashboard
{% endhint %}

This rule helps us make sure that we always have the most precise (and complete) data we want to measure at the topmost part of your exploration.&#x20;

Then, we will need to find on a "CustomerID" field on all the explorations that we'll build and declare it as a Dimension. If your systems don't already share a common CustomerID for each customer, you can use the email address to start with.&#x20;

In our example, we'll work with the user email that is shared accross Hubspot, Intercom and Segment.

### 1. 1. Focus on Hubspot

We will start with the first chart : **Total customer revenue by customer.** Breaking down this sentence we can deduce the total amount spend is the metric we will measure, and customer is the dimension we will need to filter on.&#x20;

In Hubspot, two data tables are of interest :&#x20;

* `Deals`, which contains the amount of our won deals&#x20;
* `Contacts` which contains our user emails, used as the CustomerID

Let's start our explorations with the Deals table :&#x20;

* Create a new metric, a cumulative sum of the column "amount".
* Add related data from the `Contacts` table
* Add `date` and `email` as dimensions

![](/files/DmKFkWfL7hJzZ2lY2i37)

Now create a timeserie chart using:&#x20;

* Number of Hubspot deals as a metric
* Deal date as time field

And add this timeserie to a new report.

![](/files/7MdtMvVRlEurS4QuvRMr)

### 1.-2. Segment & Intercom

Let's use Intercom data to create our chart, **the number of conversations opened by customer** :

* Add `date` and `user_email` as dimensions.
* Create a metric, measuring the `Number of Intercom Conversations`
* Use the `date` dimension as time.

Add this metric our report :&#x20;

![](/files/h7CvFY5Uytv7d1pXgPDx)

Now lets do the same thing for Segment in order to calculate **the number of login by customer** :

![](/files/oR1wHVCzBLDirQPinwU1)

## 2. Filtering our dashboard

Our dashboard is almost done, but it currently shows data for all of our users, and without the possibility to select a date range.

### 2.2. Adding a customer filter

In order to filter by user email, things are also pretty easy. On the dashboard, click on edit, add a filter and select `email` under `Hubspot - Contacts`. Then, in the "Tiles to update" tab, select the `email` field from Intercom and Segment based explorations.&#x20;

![](/files/sxbS0bBU4YHdnaVIEgIt)

### 2.1. Adding a date filter

Since all our metrics have set time field, adding a Date filter is straightforward. On the dashboard,  click on edit, add a filter and select "Default time field". That's it, all our charts will now be based on the range selected in the filter.

### 2.3 Using our dashboard

![](/files/djWmRHHaJuDWeGe5LULt)

That's it: our dashboard shows metrics for our whole customer base, and also allows us to view our data customer per customer :tada:


# Finance


# Modeling your recurring revenue

**Objectives**:&#x20;

Calculating your recurring revenue (MRR or ARR) is vital for every SaaS and subscription based businesses. It helps understanding your past and current revenue as well as projecting your upcoming revenue. When talking with investors, you will also be asked to share this information, the more detailed the better. Keeping track of your recurring revenue will allow you to monitor your company growth.&#x20;

If you follow this article, you will be able to build the following dashboard:

![](/files/PsdXRcoLnF03mGPLeZtG)

## High level plan

* Gathering our required data sources
* Importing our data and creating a SQL Model
* Writing down the SQL Query (BigQuery)
  * SQL for simplified MRR calculation
  * SQL for advanced MRR calculation

### Gathering our required data sources

Every business is unique, and the way your business handles subscriptions may be slightly different than what this article assumes. Here, we will work with a really simple data source :&#x20;

![](/files/uGVOxlZNctcIPUKUrAn6)

As you can see our subscription dataset is very clean, as we have:&#x20;

* One line per subscription, with the monthly amount, the subscription start date and the subscription end date.
* Our subscriptions begin the first day of the month and ends the last day of the month.
* A customer can only have one active subscription during a given month.
* At the end of his subscription, a customer can cancel (churn), downgrade, upgrade or renew his subscription.

The main problem with the current form of this dataset is that it will not play well with visualisation software, i.e. you will not be able to build this kind on charts :&#x20;

![](/files/401tAsS8k8Gz6AyUJtPV)

At the end of this article, we will have modeled our date into an analytics-ready dataset, meaning that we will have one line per subscription per active month. We will also take things one step further in order to identify churn, new revenue, renewals, upgrades and downgrades, as well as calculate MRR changes.

### Importing our data and creating a SQL Model

#### Importing our data into Whaly

{% hint style="info" %}
If you do not have suitable data you can download our example dataset by [clicking here](https://docs.google.com/spreadsheets/d/1jkC63lSUKdVN_vBN87sW553x2lpFZt-d69x_ccHjM-M/edit?usp=sharing).
{% endhint %}

Let's import our data into Whaly, you can use the source of your choice in order to do so. If you are just playing around and experimenting, we recommend you use either our Google Sheet connector:&#x20;

{% embed url="<https://docs.whaly.io/sources/source-catalog/google-sheets>" %}

#### &#x20;Creating a SQL Model

In order to model our current subscriptions data, we will need to create a SQL Model. This can easily done in Whaly.

You can create a new source, select the option `From warehouse` and name it `SQL`. Inside this source create a SQL Model named `Simplified SQL`.

If you have not created any models until now, it is easily done by following our [documentation on SQL Models](https://docs.whaly.io/data-management/workbench/model-data/sql-models).

### Writing down the SQL Query (BigQuery)

We will see two methods in order to model our MRR:

* A simplified method for SQL intermediates users.
* An advanced method for SQL advanced to expert users.

During these steps by steps we will rely on some specific BigQuery commands in order to manipulate dates. These steps should be adapted if used with an other warehouse.


# SQL for simplified MRR calculation

## SQL for simplified MRR calculation

*Required SQL knowledge:* WITH clause, MIN and MAX functions, INNER JOINS, BigQuery arrays manipulation.

In order to model our revenue, we will write a query which will execute the following step:&#x20;

1. Retrieve all subscriptions
2. Calculate oldest and most recent subscriptions dates
3. Generate a list of all the month between our oldest and most recent dates
4. Join our months with our subscriptions

In the end, our modeled dataset will look like this:&#x20;

![](/files/W1Zk5o4B6IlWz1XqImlb)

### Retrieve all subscriptions

Let's start by querying all of our subscriptions (you may need to update the subscription table in the from command):&#x20;

```sql
-- all subscriptions
with subscriptions as (
  select
    subscription_id,
    customer_id,
    monthly_amount,
    start_date,
    end_date,
  from
    ${TABLES["Subscriptions"]["subscriptions"]}
)
select * from subscriptions
```

Run this query, you should see the content of your subscription table as the result.

### Calculate oldest and most recent subscriptions dates

Then we will need to calculate our  oldest start date and most recent end date. Let's do it using the `min` and `max` functions:

```sql
...
-- min and max subscriptions dates
date_limits as (
  select
    min(start_date) as min_date,
    max(end_date) as max_date
  from
    subscriptions
)
select * date_limits
```

Run this query, you should see the min and max subscriptions dates.

### Generate a list of all the month between our oldest and most recent dates

Now we will generate the list of months between our `min_date` and our `max_date`:

```sql
...
-- array of month between min and max subscriptions dates
months_array as (
  select
    GENERATE_DATE_ARRAY(
      CAST(min_date as DATE),
      CAST(max_date as DATE),
      INTERVAL 1 month
    ) as arr
  from
    date_limits
),
-- list of month between min and max subscriptions dates
months as (
  select
    CAST(month as TIMESTAMP) as month
  from
    months_array,
    UNNEST(months_array.arr) as month
)
select * from months
```

Run this query, you should see the list of months between our `min_date` and `max_date`.

### Join our months with our subscriptions

Let's write the final step, which will use an inner join between our `subscriptions` and our `months` tables in order to generate one line per active subscription month.

The entire SQL query should now be as following:

```sql
-- all subscriptions
with subscriptions as (
  select
    subscription_id,
    customer_id,
    monthly_amount,
    start_date,
    end_date,
  from
    ${TABLES["Subscriptions"]["subscriptions"]}
),
-- min and max subscriptions dates
date_limits as (
  select
    min(start_date) as min_date,
    max(end_date) as max_date
  from
    subscriptions
),
-- array of month between min and max subscriptions dates
months_array as (
  select
    GENERATE_DATE_ARRAY(
      CAST(min_date as DATE),
      CAST(max_date as DATE),
      INTERVAL 1 month
    ) as arr
  from
    date_limits
),
-- list of month between min and max subscriptions dates
months as (
  select
    CAST(month as TIMESTAMP) as month
  from
    months_array,
    UNNEST(months_array.arr) as month
),
-- months joined with active subscriptions months
subscriptions_months as (
  select
    months.month,
    subscriptions.customer_id,
    subscriptions.monthly_amount as mrr,
    subscriptions.start_date,
    subscriptions.end_date
  from
    subscriptions
    inner join months on months.month >= subscriptions.start_date
    and months.month < subscriptions.end_date
)
select
  *
from
  subscriptions_months
```

Run the query: we now have one line per active subscription month :tada:

Save the query (you can use a combination of `month` and `customer_id` as primary key).&#x20;

### Build the exploration and the dashboard

By creating an exploration on top on this dataset, we will be able to build the following dashboard:

![Simplified MRR dashboard](/files/KFPtgXsc1yCHBlrbVDuQ)


# SQL for advanced MRR calculation

## SQL for advanced MRR calculation

*Required SQL knowledge:* WITH clause, MIN, MAX LEAST and COALESCE functions, Window functions, INNER JOINS, CASE statements, BigQuery arrays manipulation, subqueries.

The main differences with the simplified version are:

* Adding inactive months for a customers, between his active subscriptions
* Identifying MRR change for a customer
* Categorising our MRR change between new, renewal, upgrade, downgrade and churn

In order to model our revenue, we will write a query which will execute the following step:&#x20;

1. Retrieve all subscriptions and create a list of all the month between our oldest and most recent subscription dates
2. Calculate our customer revenue by month
3. Create our customer churn months

In the end, our modeled dataset will look like this:&#x20;

![](/files/kC59f5pHQKPPuEJvS7tv)

### Retrieve all subscriptions and create a list of all the month between our oldest and most recent subscription dates

This step is the beginning of our SQL for simplified MRR calculate article, which is:&#x20;

```sql
-- all subscriptions
with subscriptions as (
  select 
    subscription_id, 
    customer_id, 
    monthly_amount, 
    start_date, 
    end_date, 
  from 
    ${TABLES["Subscriptions"]["subscriptions"]}
), 
-- min and max subscriptions dates
date_limits AS (
  SELECT 
    MIN(start_date) AS min_date, 
    MAX(end_date) as max_date 
  FROM 
    subscriptions
), 
-- array of month between min and max subscriptions dates
months_array AS (
  SELECT 
    GENERATE_DATE_ARRAY(
      CAST(min_date AS DATE), 
      CAST(max_date AS DATE), 
      INTERVAL 1 month
    ) AS arr 
  FROM 
    date_limits
), 
-- list of months between min and max subscriptions dates
months as (
  SELECT 
    CAST (month as TIMESTAMP) as month 
  FROM 
    months_array, 
    UNNEST(months_array.arr) AS month
),
...
```

### Calculate our customer revenue by month&#x20;

Now for each of our customers we will:

1. Calculate for each customer is first and last active months.
2. Create one row per month between his first and last month.
3. Join our subscriptions with our customer months and set the MRR to 0 for months when a customer subscription is inactive.
4. Identify for each customer the `first_active_month`, `last_active_month`, and for each customer month we add a flag `is_first_month` and `is_last_month`.&#x20;

```sql
...
-- determine when a given customer had its first and last (or most recent) month
customers as (
  select 
    customer_id, 
    min(start_date) as date_month_start, 
    max(end_date) as date_month_end 
  from 
    subscriptions 
  group by 
    1
), 
-- create one record per month between a customer's first and last month
customer_months as (
  select 
    customers.customer_id, 
    months.month 
  from 
    customers 
    inner join months on months.month >= customers.date_month_start 
    and months.month < customers.date_month_end
), 
-- join the account-month spine to MRR base model, pulling through most recent dates
-- and plan info for month rows that have no invoices (i.e. churns)
joined as (
  select 
    customer_months.month, 
    customer_months.customer_id, 
    coalesce(subscriptions.monthly_amount, 0) as mrr 
  from 
    customer_months 
    left join subscriptions on customer_months.customer_id = subscriptions.customer_id 
    and customer_months.month >= subscriptions.start_date 
    and customer_months.month < subscriptions.end_date
), 
customer_revenue_by_month as (
  select 
    *, 
    first_active_month = month as is_first_month, 
    last_active_month = month as is_last_month, 
  from 
    (
      select 
        *, 
        mrr > 0 as is_active, 
        min(case when mrr > 0 then month end) over (partition by customer_id) as first_active_month, 
        max(case when mrr > 0 then month end) over (partition by customer_id) as last_active_month, 
      from 
        joined
    )
),
...
```

### Create our customer churn months&#x20;

We add a new month after our customer last month in order to create one churn month per customer

```sql
...
customer_churn_month as (
  select 
    cast (date_add(cast (month as date), interval 1 month) as timestamp) as month, 
    customer_id, 
    0 as mrr, 
    false as is_active, 
    first_active_month, 
    last_active_month, 
    false as is_first_month, 
    false as is_last_month 
  from 
    customer_revenue_by_month 
  where 
    is_last_month
), 
...
```

### Calculate MRR change

Now we will:

1. Merge our `customer_revenue_by_month` table with our `customer_churn_month` table
2. Get our customer prior month MRR and calculate our current month MRR change

```sql
...
unioned as (
  select 
    * 
  from 
    customer_revenue_by_month 
  union all 
  select 
    * 
  from 
    customer_churn_month
), 
-- get prior month MRR and calculate MRR change
mrr_with_changes as (
  select 
    *, 
    mrr - previous_month_mrr as mrr_change
  from 
    (
      select 
        *, 
        coalesce(
            lag(is_active) over (partition by customer_id order by month),
            false
        ) as previous_month_is_active,
        coalesce(
            lag(mrr) over (partition by customer_id order by month),
            0
        ) as previous_month_mrr,
      from 
        unioned
    )
),
...
```

### Classify our MRR change into categories

Now we have all the data we need to categorize our MRR change. We can easily identify :&#x20;

* New MRR
* Churned MRR
* Upgrade MRR
* Downgrade MRR
* Reactivation MRR

```sql
...
-- classify months as new, churn, reactivation, upgrade, downgrade (or null)
mrr_with_changes_categories as (
  select 
    *, 
    case
      when is_first_month then 'new'
      when not(is_active) and previous_month_is_active then 'churn'
      when is_active and not(previous_month_is_active) then 'reactivation'
      when mrr_change > 0 then 'upgrade'
      when mrr_change < 0 then 'downgrade'
    end as change_category, 
    least(mrr, previous_month_mrr) as renewal_amount 
  from 
    mrr_with_changes
) 
...
```

### Assembling the full query

Now we can assemble all the blocks into our full query:

```sql
-- all subscriptions
with subscriptions as (
  select 
    subscription_id, 
    customer_id, 
    monthly_amount, 
    start_date, 
    end_date, 
  from 
    ${TABLES["Subscriptions"]["subscriptions"]}
), 
-- min and max subscriptions dates
date_limits AS (
  SELECT 
    MIN(start_date) AS min_date, 
    MAX(end_date) as max_date 
  FROM 
    subscriptions
), 
-- array of month between min and max subscriptions dates
months_array AS (
  SELECT 
    GENERATE_DATE_ARRAY(
      CAST(min_date AS DATE), 
      CAST(max_date AS DATE), 
      INTERVAL 1 month
    ) AS arr 
  FROM 
    date_limits
), 
-- list of months between min and max subscriptions dates
months as (
  SELECT 
    CAST (month as TIMESTAMP) as month 
  FROM 
    months_array, 
    UNNEST(months_array.arr) AS month
), 
-- determine when a given customer had its first and last (or most recent) month
customers as (
  select 
    customer_id, 
    min(start_date) as date_month_start, 
    max(end_date) as date_month_end 
  from 
    subscriptions 
  group by 
    1
), 
-- create one record per month between a customer's first and last month
customer_months as (
  select 
    customers.customer_id, 
    months.month 
  from 
    customers 
    inner join months on months.month >= customers.date_month_start 
    and months.month < customers.date_month_end
), 
-- join the account-month spine to MRR base model, pulling through most recent dates
-- and plan info for month rows that have no invoices (i.e. churns)
joined as (
  select 
    customer_months.month, 
    customer_months.customer_id, 
    coalesce(subscriptions.monthly_amount, 0) as mrr 
  from 
    customer_months 
    left join subscriptions on customer_months.customer_id = subscriptions.customer_id 
    and customer_months.month >= subscriptions.start_date 
    and customer_months.month < subscriptions.end_date
), 
customer_revenue_by_month as (
  select 
    *, 
    first_active_month = month as is_first_month, 
    last_active_month = month as is_last_month, 
  from 
    (
      select 
        *, 
        mrr > 0 as is_active, 
        min(case when mrr > 0 then month end) over (partition by customer_id) as first_active_month, 
        max(case when mrr > 0 then month end) over (partition by customer_id) as last_active_month, 
      from 
        joined
    )
), 
-- row for month *after* last month of activity
customer_churn_month as (
  select 
    cast (date_add(cast (month as date), interval 1 month) as timestamp) as month, 
    customer_id, 
    0 as mrr, 
    false as is_active, 
    first_active_month, 
    last_active_month, 
    false as is_first_month, 
    false as is_last_month 
  from 
    customer_revenue_by_month 
  where 
    is_last_month
), 
unioned as (
  select 
    * 
  from 
    customer_revenue_by_month 
  union all 
  select 
    * 
  from 
    customer_churn_month
), 
-- get prior month MRR and calculate MRR change
mrr_with_changes as (
  select 
    *, 
    mrr - previous_month_mrr as mrr_change
  from 
    (
      select 
        *, 
        coalesce(
            lag(is_active) over (partition by customer_id order by month),
            false
        ) as previous_month_is_active,
        coalesce(
            lag(mrr) over (partition by customer_id order by month),
            0
        ) as previous_month_mrr,
      from 
        unioned
    )
), 
-- classify months as new, churn, reactivation, upgrade, downgrade (or null)
mrr_with_changes_categories as (
  select 
    *, 
    case
      when is_first_month then 'new'
      when not(is_active) and previous_month_is_active then 'churn'
      when is_active and not(previous_month_is_active) then 'reactivation'
      when mrr_change > 0 then 'upgrade'
      when mrr_change < 0 then 'downgrade'
    end as change_category, 
    least(mrr, previous_month_mrr) as renewal_amount 
  from 
    mrr_with_changes
) 
select 
  month, 
  customer_id, 
  mrr, 
  mrr_change, 
  change_category 
from 
  mrr_with_changes_categories
```

Run the query and save it (you can use a combination of month and customer\_id as primary key).

### Build the exploration and the dashboard

By creating an exploration on top on this dataset, we will be able to build the following dashboard:

![](/files/uSOKKN3AxnpZP7yH4QFI)


# Marketing


# Track your entire Marketing Funnel

## Objective

User Acquisition is one of the hardest challenge that a company faces. It's really important to get the proper level of reporting in order to take the proper marketing decisions and identify the best acquisition lever and their unit economics.

Most generally, Business wants to bridge their Marketing efforts (Ads, Partnerships, Referrals) with the associated Sales impacts (revenue, MRR, etc.).

In order to track such a wide funnel, many sources must be blend together, this post will help you to understand how to track your:

* Marketing Spend
* New Marketing Qualified Leads
* New Sales Qualified Leads
* New Customers
* New Revenue
* MQL -> SQL Conversion rate
* SQL -> Customer conversion rate
* Return on Ad Spent (ROAS)

and see it breakdown per:

* Date
* Acquisition Channel (Paid Ads, Social Ads, Referral, Partnerships, Organic, etc.)
* Acquisition Source (Google Ads, Facebook Ads, ...)
* Campaign (Campaign #1, Campaign #2, etc.)

## High level plan

1. We'll model individual events, ex. "Marketing Spent" / "MQL Created" / "SQL Created" / "Customer Signed"
2. We'll consolidate those events into a "Funnel table" to get a complete view
3. We'll create an Exploration and create our metrics to track our Conversion rate, etc.
4. We'll build our Dashboard to share with our stakeholders and make better decisions!

## Pre-requisites

This guides assume that you have imported all the data sources of your Marketing Funnel into your Data Warehouse. Whaly offers you a number of connectors that is making this task really easy. More specifically, you'll need:

a. Your Ads sources (Google Ads, Facebook Ads, LinkedIn Ads, a Google Sheets linked to Zapier or Supermetrics for other sources)

b. Your CRM (Hubspot, Salesforce, Pipedrive)

c. (Optionally) The financial tool in which you track your revenue (Stripe, Brex, Quickbooks, Qonto, etc.)

## Event models and Funnel tables

In order to track your Marketing Funnel, we'll use the "Event Models - Funnel Tables" paradigm. This pattern is very powerful as it will be very easy to add new Data Sources and define Events as your marketing stack evolves and your Funnel becomes more complex.

It means that we'll create:

#### Event Models

Each time that something occurs in your Marketing Funnel, we'll create an "Event". Each event has:

* an **Event Date**
* an **Event Name** ("Marketing Spend" / "MQL Created" / "Customer Created") - *This taxonomy should be customised based on your Marketing Funnel + Sales Pipeline processes.*
* Optional attributes. Main ones are:
  * **Channel**: The name of the Marketing Channel that this event should be linked to. Ex. Paid Ads / Social Ads / Referral
  * **Source**: The name of the marketing source related to the event. Ex. Google Ads, Facebook Ads, ...
  * **Campaign**: The name of the campaign related  to the campaign.
  * **Object IDs**: If there is any related object to the event, a column to hold the IDs of the related object should be added (ex. a Facebook Campaign, a Salesforce Opportunity, a Stripe Customer). Those will be used to combine the Events with the associated objects  to get insights later.
  * **Revenue**: If there is any revenue related to the event.
  * **Spend**: If there is any spent related to the event.

{% hint style="info" %}
For revenue and spend columns, it's better to suffix those columns name with the currency in which they are tracked. Ex `revenue_usd / spend_eur` / revenue\_eur\_cents.

Otherwise, you might have discrepancy later when combining the events.
{% endhint %}

#### Funnel tables

Once we have properly defined and create each of the "E**vent Models**", we'll create "**Funnel Tables**" that will consolidate those Events into a single table.

Those **Funnel Tables** can either import all your Events Models or only selected ones, depending on the reporting that you need to do.

It's often recommended to create a complete Funnel table with all the events, and more specific Funnel tables to zoom in the top or the bottom of your Funnel.

{% hint style="info" %}
Generating Events from the different steps of your Marketing Funnel is a very great idea as those could be shared in the future to others teams of your Company such as your Data Scientist that will be able to turn them into recommendations to boost the performance of your Marketing Campaigns!

This is called a [Data Mesh](https://martinfowler.com/articles/data-monolith-to-mesh.html) and [Domain Events](https://martinfowler.com/eaaDev/DomainEvent.html).
{% endhint %}

## 1. Creating our Event Models

For this guide, we'll consider that we have a very simple acquisition funnel:

* Paid Ads are running on Google Ads and Facebook Ads
* When out customers clicks on the Ads, they arrive in our website and once they fill a contact form, a MQL contact is created in the CRM
* After a Sales have called the Lead, they can classify them as being "Sales Qualified" and send them a Quote
* If the Lead accepts, he is flagged as "Customer" in our CRM and the amount billed is written as well

Hence, we'll model the following events:

1. Marketing Spend, for both Facebook and Google Ads platform
2. MQL Created
3. SQL Created

### Marketing Spend model

#### For Google Ads

Let's imagine that we have a Table coming in our Data Warehouse called "**Google Ads - Campaign Stats**" with the following structure:

{% hint style="info" %}
The schema proposed below if the one output by Whaly Google Ads connector, so you can reuse the SQL query below directly if you're a Whaly customer!
{% endhint %}

```
+------------+---------+-----------------+-------------+-------+
| date       | device  | ad_network_type | campaign_id | cost  |
+------------+---------+-----------------+-------------+-------+
| 2022-01-01 | MOBILE  | SEARCH          | 123         | 8.42  |
+------------+---------+-----------------+-------------+-------+
| 2022-01-01 | MOBILE  | CONTENT         | 123         | 10.25 |
+------------+---------+-----------------+-------------+-------+
| 2022-01-01 | DESKTOP | SEARCH          | 456         | 54.21 |
+------------+---------+-----------------+-------------+-------+
| 2022-01-02 | DESKTOP | SEARCH          | 456         | 48.21 |
+------------+---------+-----------------+-------------+-------+
```

We'll create a SQL Model called "Marketing Spend - Google Ads", this model will be in charge of:

* Removed things that are Ads specific or uninteresting for our Marketing Funnel (at least at this stage), like the `device`, the `ad_network_type`
* Keep only a Daily campaign spend

&#x20;with the following query:

```sql
WITH ad_spend_info AS ( -- Reading only what we need from the DataWarehouse
    SELECT 
        date,
        campaign_id,
        cost
    FROM 
        -- When copy pasting into Whaly Workbench, please erase this value 
        -- and rewrite it to select the proper table in the auto complete
        "Google Ads"."Campaign Stats" 
), daily_spend AS ( -- Needed step to remove the device & ad_network_type granularity
    SELECT 
        date,
        campaign_id,
        sum(cost) as cost
    FROM ad_spend_info
    GROUP BY 1, 2
), event_info AS ( -- Enrich Google Ads data to create a proper Event
    SELECT 
        "Marketing Spend" as event_name,
        date as event_date,
        "Search Ads" as channel,
        "Google Ads" as source,
        campaign_id,
        cost as spend
    FROM daily_spend
)
SELECT * FROM event_info
```

We now have a proper event table that is looking like:

```
+------------+-----------------+------------+------------+-------------+-------+
| event_date | event_name      | channel    | source     | campaign_id | spend |
+------------+-----------------+------------+------------+-------------+-------+
| 2022-01-01 | Marketing Spend | Search Ads | Google Ads | 123         | 18.67 |
+------------+-----------------+------------+------------+-------------+-------+
| 2022-01-01 | Marketing Spend | Search Ads | Google Ads | 456         | 54.21 |
+------------+-----------------+------------+------------+-------------+-------+
| 2022-01-02 | Marketing Spend | Search Ads | Google Ads | 456         | 48.21 |
+------------+-----------------+------------+------------+-------------+-------+
```

This event table is now ready to be consumed, let's go to Facebook Ads!

#### For Facebook Ads

Let's imagine that we have a Table coming in our Data Warehouse called "**Facebook Ads - Marketing Spend**" with the following structure:

```
+------------+-------------+-----------+--------+
| date       | campaign_id | ad_id     | spend  |
+------------+-------------+-----------+--------+
| 2022-01-01 | 102212210   | 154214523 | 54.21  |
+------------+-------------+-----------+--------+
| 2022-01-01 | 441215442   | 451212154 | 41.25  |
+------------+-------------+-----------+--------+
| 2022-01-01 | 441215442   | 512100212 | 84.21  |
+------------+-------------+-----------+--------+
| 2022-01-02 | 102212210   | 154214523 | 214.21 |
+------------+-------------+-----------+--------+
```

Like for Google Ads, our model is quite easy to write:

```sql
WITH ad_spend_info AS ( -- Reading only what we need from the DataWarehouse
    SELECT 
        date,
        campaign_id,
        spend
    FROM
        -- When copy pasting into Whaly Workbench, please erase this value 
        -- and rewrite it to select the proper table in the auto complete 
        "Facebook Ads"."Marketing Spent"
), daily_spend AS ( -- Needed step to remove the ad_id granularity
    SELECT 
        date,
        campaign_id,
        sum(spend) as spend
    FROM ad_spend_info
    GROUP BY 1, 2
), event_info AS ( -- Enrich Facebook Ads data to create a proper Event
    SELECT 
        "Marketing Spend" as event_name,
        date as event_date,
        "Social Ads" as channel,
        "Facebook Ads" as source,
        campaign_id,
        spend
    FROM daily_spend
)
SELECT * FROM event_info
```

Which results in:

```
+------------+-----------------+------------+--------------+-------------+--------+
| event_date | event_name      | channel    | source       | campaign_id | spend  |
+------------+-----------------+------------+--------------+-------------+--------+
| 2022-01-01 | Marketing Spend | Social Ads | Facebook Ads | 102212210   | 54.21  |
+------------+-----------------+------------+--------------+-------------+--------+
| 2022-01-01 | Marketing Spend | Social Ads | Facebook Ads | 441215442   | 126.46 |
+------------+-----------------+------------+--------------+-------------+--------+
| 2022-01-02 | Marketing Spend | Social Ads | Facebook Ads | 102212210   | 214.21 |
+------------+-----------------+------------+--------------+-------------+--------+
```

### MQL (=Marketing Qualified Lead) Created Event

Now, let's look into our CRM data to see how we can build the "MQL Created" Event Model. Let's imaging that you are using Hubspot, and that each new Lead is created as a Contact in Hubspot.

Let's have a look at the Hubspot data:

```
+------------+---------------------+--------------------------+-------------------------------------+-------------------------------------+------------------------------+
| contact_id | property_createdate | property_date_quote_sent | property_hs_analytics_source_data_1 | property_hs_analytics_source_data_2 | property_hs_analytics_source |
+------------+---------------------+--------------------------+-------------------------------------+-------------------------------------+------------------------------+
| 1255412354 | 2022-01-01          | 2022-01-06               | Facebook                            | 102212210                           | PAID_SOCIAL                  |
+------------+---------------------+--------------------------+-------------------------------------+-------------------------------------+------------------------------+
| 8442125214 | 2022-01-02          |                          | 441215442                           | SuperKeyword                        | PAID_SEARCH                  |
+------------+---------------------+--------------------------+-------------------------------------+-------------------------------------+------------------------------+
| 8774154775 | 2022-01-10          | 2022-01-25               | IMPORT                              |                                     | OFFLINE                      |
+------------+---------------------+--------------------------+-------------------------------------+-------------------------------------+------------------------------+
```

{% hint style="info" %}
When having an Hubspot tracker on your Website that is used to create the contact when they submit a form, a lot of properties are automatically tracked by Hubspot.

The full documentation can be found [here](https://knowledge.hubspot.com/contacts/understand-source-properties) and [here](https://knowledge.hubspot.com/reports/understand-hubspots-traffic-sources-in-the-traffic-analytics-tool#definitions-and-drill-down-data-of-each-source).
{% endhint %}

{% hint style="warning" %}
If you are using another CRM or if you have a custom way to create contacts into your CRM, get in touch with your CRM admin to see how you track the Marketing origin of your Leads.

By using UTMs properly both in your Marketing tools and in your Contact creation logic, you can track the origin of each of your Lead.

[See this article for more information.](https://www.gravitatedesign.com/blog/utm-codes-crm-tools-4-steps-better-tracking/)
{% endhint %}

Now, we'll create a Model to:

a. Properly map the values and columns from Hubspot to the taxonomy used in the Marketing Spend events

b. Map the proper date to the Event Date -> when tracking new MQL, it is common to use the contact creation date as the event date

```sql
WITH contact_info AS ( -- Reading only what we need from the DataWarehouse
    SELECT 
        property_createdate as contact_creation_date,
        contact_id,
        property_hs_analytics_source as hs_origin_source,
        property_hs_analytics_source_data_1 as hs_origin_data1,
        property_hs_analytics_source_data_2  as hs_origin_data2
    FROM 
        -- When copy pasting into Whaly Workbench, please erase this value 
        -- and rewrite it to select the proper table in the auto complete
        "Hubspot"."Contact"
), cleaned_contact AS ( 
-- Needed step to map Hubspot generated value with our events taxonomy for source and channel
    SELECT 
        contact_creation_date,
        contact_id,
        CASE
            WHEN hs_origin_source = "PAID_SOCIAL" THEN "Social Ads"
            WHEN hs_origin_source = "PAID_SEARCH" THEN "Search Ads"
            ELSE hs_origin_source
        END as channel,
        CASE
            WHEN hs_origin_source = "PAID_SOCIAL" AND hs_origin_data1 = "Facebook" THEN "Facebook Ads"
            WHEN hs_origin_source = "PAID_SEARCH" THEN "Google Ads"
            ELSE "unknown"
        END as source
    FROM contact_info
), event_info AS ( 
-- Enrich Hubspot data to create a proper Event
    SELECT 
        contact_creation_date as event_date,
        "MQL Created" as event_name,
        channel,
        source,
        contact_id
    FROM cleaned_contact
)
SELECT * FROM event_info
```

Which results in:

```
+------------+-------------+------------+--------------+------------+
| event_date | event_name  | channel    | source       | contact_id |
+------------+-------------+------------+--------------+------------+
| 2022-01-01 | MQL Created | Social Ads | Facebook Ads | 1255412354 |
+------------+-------------+------------+--------------+------------+
| 2022-01-02 | MQL Created | Search Ads | Google Ads   | 8442125214 |
+------------+-------------+------------+--------------+------------+
| 2022-01-10 | MQL Created | OFFLINE    | unknown      | 8774154775 |
+------------+-------------+------------+--------------+------------+
```

### SQL (=Sales Qualified Lead) Created Event

in our example, to stay simple, we'll say that a SQL is tracked in our CRM when a contact has a "Date Quote Sent" set.&#x20;

In reality, SQL are often tracked as Deals / Opportunity in the CRM, so your SQL Event Model might be based on the Deal / Opportunity object, but the logic stay the same.

If we keep the same Hubspot table as above, our Model definition would be:

```sql
WITH contact_info AS ( -- Reading only what we need from the DataWarehouse
    SELECT 
        property_date_quote_sent as contact_quote_sent_date,
        contact_id,
        property_hs_analytics_source as hs_origin_source,
        property_hs_analytics_source_data_1 as hs_origin_data1,
        property_hs_analytics_source_data_2  as hs_origin_data2
    FROM 
        -- When copy pasting into Whaly Workbench, please erase this value 
        -- and rewrite it to select the proper table in the auto complete
        "Hubspot"."Contact"
    -- This is where we only keep contact that were sent a Quote for the SQL definition
    WHERE property_date_quote_sent IS NOT NULL 
), cleaned_contact AS ( 
-- Needed step to map Hubspot generated value with our events taxonomy for source and channel
    SELECT 
        contact_quote_sent_date,
        contact_id,
        CASE
            WHEN hs_origin_source = "PAID_SOCIAL" THEN "Social Ads"
            WHEN hs_origin_source = "PAID_SEARCH" THEN "Search Ads"
            ELSE hs_origin_source
        END as channel,
        CASE
            WHEN hs_origin_source = "PAID_SOCIAL" AND hs_origin_data1 = "Facebook" THEN "Facebook Ads"
            WHEN hs_origin_source = "PAID_SEARCH" THEN "Google Ads"
            ELSE "unknown"
        END as source
    FROM contact_info
), event_info AS ( 
-- Enrich Hubspot data to create a proper Event
    SELECT 
        contact_quote_sent_date as event_date,
        "SQL Created" as event_name,
        channel,
        source,
        contact_id
    FROM cleaned_contact
)
SELECT * FROM event_info
```

Which results in:

```
+------------+-------------+------------+--------------+------------+
| event_date | event_name  | channel    | source       | contact_id |
+------------+-------------+------------+--------------+------------+
| 2022-01-06 | SQL Created | Social Ads | Facebook Ads | 1255412354 |
+------------+-------------+------------+--------------+------------+
| 2022-01-25 | SQL Created | OFFLINE    | unknown      | 8774154775 |
+------------+-------------+------------+--------------+------------+
```

{% hint style="info" %}
As you can see, the logic to convert the Hubspot "origin" columns into our Event taxonomy is duplicated in both the MQL and SQL models.

As this mapping will have to change in the future and as it's hard to deal with code duplication, a good thing would be to create a "Contact With Marketing Origin" model containing the mapping logic once and use it in both MQL Created and SQL Created Models configuration.&#x20;
{% endhint %}

{% hint style="success" %}
We'll stop here the definition of our Events, but the same logic and principles can be used to create other events of the bottom of your funnel:

* Demo done
* Contract Signed
* Upsell done
* etc.

It all depends of how your Sales Funnel is structured!
{% endhint %}

## 2. Build our "Funnel Table"

Now that we have our 4 Events:

* Marketing Spend (Google Ads)
* Marketing Spend (Facebook Ads)
* MQL Created
* SQL Created

We'll create a "Funnel table" that will import all those events into a consolidated timeline to do reporting and calculate conversion rates and MQL / SQL acquisition costs.

This Funnel table is once agin a Model defined as:

<pre class="language-sql"><code class="lang-sql">-- || Top of the funnel || --
-- Google Ads
SELECT
    event_date,
    event_name,
    channel,
    source,
    campaign_id,
    spend,
    'n/a' as contact_id
FROM 
    -- When copy pasting into Whaly Workbench, please erase this value 
    -- and rewrite it to select the proper table in the auto complete
    "Marketing Spend Event - Google Ads"
UNION ALL
-- Facebook Ads
SELECT
    event_date,
    event_name,
    channel,
    source,
    campaign_id,
    spend,
    -- We add fake values for the columns specific to the MQL/SQL Created events
    'n/a' as contact_id
FROM 
<strong>    -- When copy pasting into Whaly Workbench, please erase this value 
</strong>    -- and rewrite it to select the proper table in the auto complete
    "Marketing Spend Event - Facebook Ads"
UNION ALL
-- MQL Created
SELECT
    event_date,
    event_name,
    channel,
    source,
    contact_id,
    -- We add fake values into the columns that are specific to the Marketing Spend events
    'n/a' as campaign_id,
    0 as spend
FROM 
    -- When copy pasting into Whaly Workbench, please erase this value 
    -- and rewrite it to select the proper table in the auto complete
    "MQL Created"
UNION ALL
SELECT
    event_date,
    event_name,
    channel,
    source,
    contact_id,
    -- We add fake values into the columns that are specific to the Marketing Spend events
    'n/a' as campaign_id,
    0 as spend
FROM 
    -- When copy pasting into Whaly Workbench, please erase this value 
    -- and rewrite it to select the proper table in the auto complete
    "SQL Created"
-- || Bottom of the funnel || --
</code></pre>

Which results in:

```
+------------+-----------------+------------+--------------+-------------+--------+------------+
| event_date | event_name      | channel    | source       | campaign_id | spend  | contact_id |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-01 | Marketing Spend | Search Ads | Google Ads   | 123         | 18.67  | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-01 | Marketing Spend | Search Ads | Google Ads   | 456         | 54.21  | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-02 | Marketing Spend | Search Ads | Google Ads   | 456         | 48.21  | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-01 | Marketing Spend | Social Ads | Facebook Ads | 102212210   | 54.21  | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-01 | Marketing Spend | Social Ads | Facebook Ads | 441215442   | 126.46 | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-02 | Marketing Spend | Social Ads | Facebook Ads | 102212210   | 214.21 | n/a        |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-01 | MQL Created     | Social Ads | Facebook Ads | n/a         | 0      | 1255412354 |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-02 | MQL Created     | Search Ads | Google Ads   | n/a         | 0      | 8442125214 |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-10 | MQL Created     | OFFLINE    | unknown      | n/a         | 0      | 8774154775 |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-06 | SQL Created     | Social Ads | Facebook Ads | n/a         | 0      | 1255412354 |
+------------+-----------------+------------+--------------+-------------+--------+------------+
| 2022-01-25 | SQL Created     | OFFLINE    | unknown      | n/a         | 0      | 8774154775 |
+------------+-----------------+------------+--------------+-------------+--------+------------+
```

This single table consolidate all our Marketing Funnel events into a single view, with a consolidated taxonomy 🎉

Now that we did the "hard" part, we only have to do the "nice" job to create an Exploration on top of this table!

## 3. Create an Exploration

Now that we have our single table, we can create an Exploration with the following configuration:

Dimensions:

* event\_date as Date
* channel as Channel
* source as Source

Metrics:

* SUM(spend) | If event\_name = "Marketing Spend" as Spend
* COUNT | If event\_name = "MQL Created" as MQLs
* COUNT | If event\_name = "SQL Created" as SQLs

Calculated Metrics:

* SQLs / MQLs as "MQLs -> SQLs conversion rate"
* Spend / MQLs as "CAC MQLs"
* Spend / SQLs as "CAC SQLs"

## 4. Create a dashboard

Now you can chart your CACs, Conversion rate and funnel breakdown by Channel / Sources.

## 5. Next steps

This guide only covers a simple marketing funnel, however, there can be many differents direction to take from there:

* You can add other Events in the Bottom of your funnel to have a better view of where you lose Customers and Revenue per Channel / Source
* You can add events that have a "revenue" attributes (ex. Contract Signed) and create a new "revenue" column in your Funnel table. This will unlock the ROAS metric.
* If you have the Campaign IDs stored in your CRM when Leads are created, you can expose it in the funnel and get a "per Campaign" view of your Funnel
* You can even go one step further and build this funnel at the "Ad Group" / "Ad" level by adding the proper attribute to all the events on the chain (you probably have to tweak a bit the way your campaigns are tracked in your CRM when Leads are created to get this level of details)


# Calculate your Customer Acquisition Cost

**Objective**: Getting new customers is hard and require a lot of Marketing and Sales efforts. Being sure that those costs are kept in control and that your acquisition strategy is sustainable is a matter of life and death of your company.&#x20;

This core metric will be looked at by your future investors and stakeholders and should be improved until it reaches a level aligned with your business model. It is a good reporting metric for your Sales & Marketing teams.&#x20;

&#x20;This guide is targeting Facebook Ads, Google Ads and Hubspot CRM, but the principles can be used with other acquisition sources and CRM tools.

Required sources:

a. Marketing sources such as Google Ads, Facebook Ads, LinkedIn Ads or Airtable / Google Sheets (for offline marketing costs)

b. Your CRM or any data source containing your customers and the date they came from the Marketing channel

## High level plan

**a. We'll make sure that each new customers can be linked to a specific Day of acquisition**&#x20;

**b. We'll use a "Day" table to bridge your acquisition tools cost reports with your CRM performance report**

**c. We'll create an exploration that will link together all the Marketing and CRM tables**

**d. We'll create calculated metrics to track your Customer Acquisition Cost, ROAS and other key marketing metrics**

{% hint style="info" %}
In this guide, in order to bridge the Marketing tools (Google Ads, Facebook Ads, etc.) and the Sales tools (Hubspot, Salesforce, Pipedrive), we will use the time as being the link between those system.&#x20;

We do this because in most organisations there is no "Common ID" between the Spent reports coming from the Marketing tools and the CRM tools.

If you're having such IDs in your data setups (ex. Campaign Ids), you can use those to bridge your Marketing data with your Sales data.
{% endhint %}

### **a. We'll make sure that each new customers can be linked to a specific Day of acquisition**&#x20;

Firstly, you need to define, within your CRM, on which day each of your customers were acquired thanks to a Marketing tool. The easiest way of doing it is by using the CRM Contact "Creation Date" if your CRM is only used by your Sales team.

If you have a more complicated acquisition strategy, you can also decide to chose the date when your Contact became "Sales Qualified Leads (SQL)".

Once you have identified the date column you wish to use as the "acquisition date" in your tables, you need to round it up to the Day granularity to be sure it map properly with your Marketing Spend reports.

You can do that by creating a new formula field in the Workbench with:

```
COHORT(property_createdate; "day")
```

![](/files/13iHRbWTkWP42JN2GVFv)

**b. We'll use a "Day" table to bridge your acquisition tools cost reports with your CRM performance report**

We'll now create Relationships between the "Day" table and both the Marketing tools and the CRM data.

{% hint style="info" %}
Whaly includes a "Day" table in your Workbench by default to help you combine multiple data sources on time
{% endhint %}

In the "**Day**" object, add one **`Has Many`** relationship per Table on the different Day columns, as follows:

![](/files/m6ySJMoi0Y3OhGnPvRtp)

### **c. We'll create an exploration that will link together all the Marketing and CRM tables**

You can now create an exploration that starts from the Day table:

Click on the + icon and on "Add Related Data" to add the different tables needed for the Customer Acquisition Cost calculation. You should obtain:

![](/files/1YAdpuE4BXFZTl8SpxPr)

### **d. We'll create calculated metrics to track your Customer Acquisition Cost, ROAS and other key marketing metrics**

On the Day table, click on the + icon to create Calculated Metric "Marketing Spent":

![](/files/LeySN3YOmStAu3lgXvMT)

On the Day table, click on the + icon to create Calculated Metric "Customer Acquisition Cost":

![](/files/NiU1MMJzCzOb5IjIVdHG)

**You should now have:**

![](/files/ncjcmeTjvZw9HJag4lyn)

And now, you can plot your Customer Acquisition Cost metric in time or calculate it over a period of time to track your Marketing performance 🎉


# Create a partner dashboard

**Objective**

In every partnership, it is important to share, be it in life, in love or in business. For example, if your company outsources part of its lead acquisition process to external partners, it is important for them to have an overview of how successful you are with converting their leads into deals.

In this guide, you will learn how to structure and configure a partner dashboard that enables you to securely share this information back with your partners.&#x20;

### High level plan

* Understanding our partner dashboard
* Making our dashboard partner-ready
  * Adding a partner filter to our dashboard
  * Adding a sharing link
  * Hiding the partner filter from the report

### Understanding our partner dashboard

For this guide, we will assume that we have already built our Partner Dashboard, which gives us information about:

* Our leads conversion funnel
  * Number of new leads received
  * Number of qualified leads
  * Number of converted leads
* Our partners based generated revenue&#x20;
* Our partners commission&#x20;

You can take a look at our example dashboard below:&#x20;

![](/files/GeU4RWDJtvLv7LCBvvyE)

### Making our dashboard partner ready

#### Adding a partner filter to our dashboard

To make our dashboard partner ready, we will firstly need to be able to breakdown our view per partner. This steps essentially consists in adding a "Partner" filter to our dashboard, and will allow us to restrict the leads to the partner they are generated from.&#x20;

Let's get to work. On Whaly, open your partner dashboard, then:&#x20;

* click on Edit
* click on Add a filter
* select you partner identifier in the "Fields to filter from" list (in this example we will use `partner_name`) and give your filter a name
* in the "Tiles to update" tab, verify that the filter is applied to all the tiles you need

![](/files/p9XNyj58KfRknP6glx6K)

### Adding a sharing link&#x20;

We will use the sharing link feature to create a pre-filtered view of our dashboard that we will be able to send to our partners.

In order to do so:&#x20;

* Click on share
* Click on new sharing link
* Give it a name (we will use "Partner: Duff"), a password if you need to, and select set partner filter value to your partner's name.&#x20;

![](/files/VFSeNOPY55rNHPao6u35)

### Hiding the partner filter from the report

Great, now we have a sharing link. But before sending it, we will probably want to prevent our partner to filter on our others partners. To do so, we will hide the partner filter from the dashboard:&#x20;

* Click on edit
* Click on the partner filter settings
* Check "Hide your filter from the report" and save

![](/files/UUoSM0rbPhsUoAGYWD3r)

That's it, now we can send our sharing links to our partners. They will be able to view the dashboard to gain insights of how the partnership is performing :tada:


# Sales


# Analyze the impact of your Sales velocity on your closing rate

**Objective**: The sales momentum don't last as prospects can find alternatives to your product/services pretty fast. Hence, it's important to not stall your Opportunities into a stage of your pipeline, otherwise it can negatively impact your close rate.

However, not all Sales Pipeline are born equal, and all stages are not as time sensitive from the other ones. In order to identify the stages that are the more sensitive to delays, to focus on being reactive during those, it's nice to make an analysis on your historical Opportunities data.

This guide is targeting Hubspot CRM, but the principles can be used with other CRM data.

## **High level plan:**

**a. We'll get the time passed in each stage of your Deal pipeline by each of your deals**

**b. We'll create "buckets" to classify the time spent by each deal in the stages that you want to analyse (ex. "Less than 1 hour", "Less than 1 day", "Less than 1 Month", "Less than 1 Year")**

**c. We'll expose those buckets as "Dimensions" in an Exploration**

**d. We'll create a metric "Close rate" so that we can track your close rate across the different buckets**

**e. We'll create some charts to get insights 🎉**

### **a. Get the time passed in each stage for each deal**

Hubspot is giving, for each deal, the time passed in each stage in a Deal property `property_hs_time_in_XXXXXX` with `XXXXXX` being the Stage Id. The column value is the number of milliseconds passed in the stage.

So, to get our analysis done, the first step would be to create a new View on the Hubspot's Deal object and show the columns associated with the Deal Stage that we want to analyse.

{% hint style="info" %}
In order to identify the proper columns, we can have a Look at the \`stage\_id\` column of the Hubspot's Deal Pipeline Stage table.
{% endhint %}

![](/files/UHHY0slTtkYh33a6fAyl)

Additionally, we should filter out any deals that have not a value superior at 0 in those columns as those are the Deals that never went through those Stage (they can be from other Pipeline or they could have been lost before entering the Stages that we analyse).

Also, we should filter deals that have `property_hs_is_closed` set at `true` in order to only keep the Deal that are closed, e.g. those that are either Won or Lost.

![](/files/lwHysCtAaefqjjdKYtCU)

Finally, we should display the `property_hs_is_closed_won` property. We'll need it to calculate the Close ratio metric later.

### **b. Creating bucket of time**

So, we have the number in milliseconds that deal waited in our Stage, which is great but which is not super helpful to get answers.

In order to get a proper understanding of what time does that represent, we'll create a new column to bucket those time into time period that we can understand.

We can do that by creating a new column in our view with this formula:

```
IF(property_hs_time_in_XXXXXX<3600000;"Less than 1 hour"; IF(property_hs_time_in_XXXXXX<7200000;"Between 1h and 2h"; IF(property_hs_time_in_XXXXXX<86400000;"Between 2h and 1 day"; "More than 1 Day")))
```

The values (1h => 360 000milliseconds, 2h => 7200 000ms, ....) and the labels can be tweaked to match the bucket definition that you want to get for your analysis.

![](/files/0jB3N8NGheJx0Sh1WfuY)

![](/files/0jB3N8NGheJx0Sh1WfuY)

### **c.  Expose those buckets as "Dimensions" in an Exploration**

We should now create a new Exploration from our View created in the previous step. We should create a dimension from the formula column created in the previous step.

![](/files/DsPm6h3Ie5Vja4ZmfJST)

**d. We'll create a metric "Close rate" so that we can track your close rate across the different buckets**

In order to create a Close rate metric, we'll firstly 2 metrics: "Number of Closed Deal" and a "`Number of Closed Won Deal`" metric.

![](/files/iwLsSCCsmEkKYMa4irXr)![](/files/Ejx8e86lk54kp82s1H1P)

Then, we create a calculated metric with the formula: `Number of Closed Won Deal / Number of Closed Deal` and we cast it as percent.

![](/files/w7jlMEvrxyH6rxgNlJtS)

**e. We'll create some charts to get insights 🎉**

We can now plot the Close rate per time bucket and get our insights!

![](/files/7Kz2n5EeRXQH9VFlci3H)

### f. Bonus point: Adding a time component

We can add the close date as a Time dimension to see a breakdown of those closing ratio based on the Close time.

This is done by creating a new column in the workbench with the formula:DA

```
DATETIME_FORMAT(property_closedate;"%b %g")
```

And using this new column as the "Close date - Month" in a pivoted chart:

![](/files/XfNQFnKxlbnkzZgM8dJP)


# Create a sales performance dashboard

**Objective**: Closely following sales performance metrics, such as time to close, conversions rates and average order amount is vital for any business. It is mandatory to have this information when working with investors. It also helps you understand when to accelerate on sales as you notice your metrics are getting better.

In this guide, we will learn how to build a report that will:&#x20;

* Display our average order amount and total revenue
* Track our sales velocity over time
* Monitor our sales pipeline conversions rates

This guide is targeting Hubspot CRM, but the principles can be used with other CRM data.

Here is what we will build :&#x20;

![](/files/Y33BuKQKgJGj4f8uzoud)

## High level plan:&#x20;

1. Creating our Deal exploration
2. Creating our "new revenue" metric
3. Creating our "average order amount" metric
4. Creating our sales velocity chart
5. Creating our deal conversion funnel
6. Creating our report

### Creating our Deal exploration

Let's start with the easy stuff and create a new exploration. We will start our exploration from our Deal table as we want to see 100% of our deals in our sales dashboard.&#x20;

Let's create the exploration and add the column `property_createdate` as a dimension.

### Creating our "new revenue" metric

In order to display our new revenue, we will want to count the total amount for the deals won. It's easy to create such a metric:&#x20;

* Create a new metric
* Set it to `Sum` of the column `property_amount`
* In the advanced settings, set a filter on `property_dealstage` equals `closedwon`
* Set a suffix to €

### Creating our "average order amount" metric

To display the average order amount, we will will recreate the same metric as for total revenue but we will use the aggregation "average" instead of sum:&#x20;

* Create a new metric
* Set it to be `Average` of the column `property_amount`
* In the advanced settings, set a filter on `property_dealstage` equals `closedwon`
* Don't forget the suffix and the name :nerd:

Now you can use these two metrics on our dashboard:

![](/files/1CXfEVNcHHmZxeFsAoJ5)

### Creating our sales velocity chart

Hubspot provides a column named `property_days_to_close` on the deal table which contains the number of days from the deal creation date to the deal closed date. To monitor our sales velocity, we will create a new metric:&#x20;

* Create a new metric
* Set it to average of the column `property_days_to_close`
* In the advanced settings, set a filter on `property_dealstage` equals `closedwon`
* You can add `days` as a suffix in the advanced settings

![](/files/mieT7XCbgyZfgjuz72ca)

### Creating our deal conversion funnel&#x20;

In order to closely monitor our sales pipeline, we will create a conversion funnel on top of our sales process. In this guide our funnel is composed of three steps: Demo -> Trial -> Customer

First, we will create a metric for each of our pipeline steps : number of trial done, number of demo done and number of customer. Let's do it:&#x20;

* Create a new metric
* Set it to count
* In the advanced settings, set a filter to on `property_hs_date_entered_in_xxx` to `is set`, where `xxx` is the corresponding stage id in Hubspot.&#x20;
* Give it a name (for example, number of demo done)

{% hint style="info" %}
The matching between the pipeline stage id and the pipeline label (displayed in hubspot) in available in the Deal pipeline stages table.
{% endhint %}

This allows us to monitor our sales funnel by month:&#x20;

![](/files/cmdxXoTt1WA4H49VqK6J)

As well as displaying our global sales funnel:&#x20;

![](/files/lioA7qGdrfgM1qYJpVVy)

We will also calculate our conversion rate between our pipeline stage. Here we will need to create two calculated metrics:&#x20;

1. Demo conversion rate, which is `number of trial done / number of demo done`
2. Trial conversion rate, which is `number of customer / number of trial done`

This will allow us to monitor our conversion rates in time:&#x20;

![](/files/yXlYE5hLyETBrLuAYYzj)

### Creating our report&#x20;

Now we have all the elements we need to build our sales performance report. Let's just assemble everything and apply some formatting to get our final result :&#x20;

![](/files/jQQP4PymR2BKjbzIipyF)


# Build a target oriented sales dashboard

**Objectives**:&#x20;

As a sales manager, having a target oriented reporting strategy for your team is a driver of success.&#x20;

Objective tracking is a must-have tool that will animate your team and enable your teammate to deliver. It's a good start for starting sales gamification!

On a higher level, it will give you a good tool to drive your 1-to-1 meetings with your Sales rep and give you an easy way to identify which teammate requires coaching and on which topic.

If you follow this article, you will be able to build the following dashboard:

![](/files/T3soHeZJelz1mFcw1Il1)

## High level plan

* 🚛  Importing data into Whaly
  * Importing CRM data
  * Importing target data
* 🔗  Creating the relationships between the datasets
* 📆 Creating the exploration from Days
* 📈 Creating the charts
* ✨ Creating the dashboard

### 🚛  Importing data into Whaly

{% hint style="info" %}
On this article we'll use data from Airtable but the same pattern can be applied to any CRM such as [Hubspot](https://docs.whaly.io/sources/source-catalog/sales/hubspot), [Salesforce](https://docs.whaly.io/sources/source-catalog/sales/salesforce) and [Pipedrive](https://docs.whaly.io/sources/source-catalog/sales/pipedrive) 🤗
{% endhint %}

#### Importing CRM data

To get started, the first thing we are going to do is import our CRM data into Whaly. Our CRM data is composed of two tables.

One "deal" table containing information about our deals:&#x20;

![Our CRM "deal" table](/files/yVSdkyZLEiHpKUN25kbZ)

One "deal stage history" table containing information about our deals movements throughout our sales pipelines:&#x20;

![Our CRM "deal stage history" table](/files/lXBfKStPF2tz9GIIUuum)

#### Importing target data

The second type of data we are going to import is the targets. We will create monthly targets for our salespersons and structure our data with the following columns:

* **owner\_id**: the owner to whom the target applies
* **type**: the current objective (revenue, number of call done, ...)&#x20;
* **date**: the current objective date&#x20;
* **target**: the current target amount

{% hint style="info" %}
Targets should be written in a system on which you can easily input new data. We recommend to use either [Google Sheets](https://docs.whaly.io/sources/source-catalog/no-code/google-sheets) or [Airtable](https://docs.whaly.io/sources/source-catalog/no-code/airtable) for this task.
{% endhint %}

Let's take have a look at our example target database:&#x20;

![](/files/o3AKv7EXhCNHFXoZjZ28)

### 🔗 Creating the relationships between the datasets

Before creating our relationships, we need to ensure that our data will match. In our days table, the date is a timestamp at the beginning of the day (ex: `June 27 2022, 00:00:00`). In our deal stage history table, our timestamps have hours, minutes and seconds (ex: `February 17 2020, 17:39:02`). We will need to round our dates in our deal stage history table in order to have matching data for the relationship. Let's do it:

1. Open the workbench, go the your deal stage history view
2. Click on add a column, select formula, and use the cohort formula `day` as the type in order to round the date to the current day:

![](/files/coX8Lvy7xGmAE1LdWjKp)

As our targets and deals are going to be linked using our Days table, we will need to create relationships between:&#x20;

1. Days *has many* Targets&#x20;
2. Days *has many* Deal Stage History
3. Deal *has many* Deal Stage History

Let's get into the workbench, on the day table and create the following relationships:

![](/files/pKOjVD3gAwP5any9LFVa)

We will also need to create the relationship between our deals and deals stage history (this is optional is you are using Whaly native CRM connectors that auto create such relationships):&#x20;

![](/files/3GvAxayih69qGI0UMXCf)

### :telescope: Retrieve the missing columns in the deal stage history table

It's possible that your deal stage history table, that we will use our exploration, is missing some columns, such as our deal amount and our owner id. Fortunately we have this information in our deal table, so it's easy to bring them in the deal stage history table.&#x20;

Let's do it :&#x20;

1. Open the workbench, go the your deal stage history view
2. Click on add a column, select lookup, and fill the information as required:

![](/files/SvNmqJgFF7RfaACE0Sk5)

Now let's do the same thing for our amount column:

![](/files/sNiuSghq2CGEwnEK4fn7)

Now that we have all our data, we can start building our exploration.

### 📆  Creating the exploration from Days

Let's get to our workspace and create a new exploration, starting from our Days table. We do this in order to be able to add our "Deal Stage history" and "Targets" tables as related data.&#x20;

Let's do it:

1. Create a new exploration from `Days`
2. On the Days table:
   1. add `Date` as a dimension
   2. add `Deal stage history` as a related data
   3. add `Targets` as a related data
3. Remove all the metrics created automatically&#x20;

You should now have an exploration that looks like the following:

![](/files/5crdipa5z2sFR5KmxlwY)

Now we will create our metrics for targets and current progress.

Let's get started with the bookings target:&#x20;

1. Click on `Add a metric` under the `targets` table:&#x20;
2. Create a sum of the column `target`
3. Filter on all rows matching your desired `owner_id` and target type `revenue`
4. Give your custom metric a name, for example: `Target bookings (owner #0)`
5. Add a currency suffix to ensure our charts will look good
6. Create the metric

![](/files/Egy5qmHbFL9UowwA9FSq)

Now let's do the same for the current bookings of our `Owner #0`:

* Click on `Add a metric` under the `deal stage history` table:&#x20;
* Create a sum of the column `amount`
* Filter on all rows matching your desired `owner_id` and deal stage `closedwon`
* Give your custom metric a name, for example: `Bookings (owner #0)`
* Add a currency suffix to ensure our charts will look good
* Create the metric

{% hint style="info" %}
If the columns amount and owner\_id are missing from the deal stage history table you can easily add them using a lookup column in the workbench. See our guide here: <https://docs.whaly.io/data-management/workbench#2.-lookup>
{% endhint %}

![](/files/stKiXdqrU78xUBPhrTYQ)

We just have to repeat these steps for each of our owners and metrics that we want to follow. For example, with 2 owners and with revenue, Nb call done, Nb demo done, Nb trials done, we should build the following exploration:&#x20;

![](/files/6o1bpN6Pv380tpOxzHeF)

### 📆  Creating the charts

In order to create the charts, we will:

1. select the `Metric` chart type
2. add both our current KPI and target to our query builder, for example Bookings `(owner #0)` and `Target bookings (owner #0)`
3. add our `Date` dimension as the time field,
4. select a relevant time range
5. set `Gauge` as metric type
6. run the query

![](/files/4XKk61l1t7IP866Q3lmA)

### ✨ Creating the dashboard

We can repeat the previous step as many times as necessary in order to build our dashboard. For our example, with 4 KPIs and two different salespersons we can build the following report:&#x20;

![](/files/kPxn9JH4UDqNW1H7INQi)

And voilà, that's it :tada:


# SQL Fanout

If you're a SQL user, some of the first SQL concepts you probably learned about were joins and aggregate functions (such as `COUNT` and `SUM`). One thing that is not always taught is how these two concepts can interact and sometimes produce incorrect results. In this article we'll discuss what to look out for, the concept of a "fanout," and why it matters to SQL writers.

This is one of the reason why there is an old wisdom that "JOINs are tricky to get right", even for experience SQL writer.

## Starting with a simple join

Let's start off with a simple example, where we'll join together a couple of tables. Our first table will show our customers' names and the number of visits each customer has made to our e-commerce website:\
&#x20;

**customer**

| customer\_id | first\_name | last\_name     | visits |
| ------------ | ----------- | -------------- | ------ |
| 1            | Jean        | de la Fontaine | 2      |
| 2            | Émile       | Zola           | 2      |
| 3            | René        | Descartes      | 4      |

\
Our second table will include all the orders that those customers have placed. You can see that each order is linked to the customer who placed it by the customer's ID.\
&#x20;

**order**

| order\_id | amount | customer\_id |
| --------- | ------ | ------------ |
| 1         | 25.00  | 1            |
| 2         | 50.00  | 1            |
| 3         | 75.00  | 2            |
| 4         | 100.00 | 3            |

\
Joining these tables together in SQL would be pretty simple:

```
SELECT
    *
FROM
    customer
LEFT JOIN order
USING (customer_id)
```

\
The result of that query would be this table:

| customer\_id | first\_name | last\_name     | visits | order\_id | amount |
| ------------ | ----------- | -------------- | ------ | --------- | ------ |
| 1            | Jean        | de la Fontaine | 2      | 1         | 25.00  |
| 1            | Jean        | de la Fontaine | 2      | 2         | 50.00  |
| 2            | Émile       | Zola           | 2      | 3         | 75.00  |
| 3            | René        | Descartes      | 4      | 4         | 100.00 |

## Aggregate functions gone bad

\
Now that we have a joined table, we need to be careful about how we use aggregate functions like `COUNT` and `SUM`.\
&#x20;

### Aggregate functions on a single table

\
Let's consider the customer table all by itself again. If we want to know the total number of customers, we can execute a simple query like this:

```sql
SELECT
    COUNT(*) as count
FROM   customer
```

SQL will count up the rows in the table as follows:\
&#x20;

**customer**

| `count`        | customer\_id | first\_name | last\_name     | visits |
| -------------- | ------------ | ----------- | -------------- | ------ |
| + 1            | 1            | Jean        | de la Fontaine | 2      |
| + 1            | 2            | Émile       | Zola           | 2      |
| + 1            | 3            | René        | Descartes      | 4      |
| **Results ⤵️** |              |             |                |        |
| `count`        | 3 (✅)        |             |                |        |

\
We'll get a count of 3, which is correct 👍

Or, if we want to know the total number of customer visits, we can execute another straightforward query like this:

```sql
SELECT
    SUM(visits) as sum
FROM
    customer
```

SQL will add up the number of visits in the table as follows:

**customer**

| customer\_id   | visits | sum | first\_name | last\_name     |
| -------------- | ------ | --- | ----------- | -------------- |
| 1              | 2      | + 2 | Jean        | de la Fontaine |
| 2              | 2      | + 2 | Émile       | Zola           |
| 3              | 4      | + 4 | René        | Descartes      |
| **Results ⤵️** |        |     |             |                |
| `sum`          | 8 (✅)  |     |             |                |

\
We'll get a result of 8, which is also correct 👍\
&#x20;

### Aggregate functions on the joined table

\
So far, so good. However, if we try to use the same aggregate functions on either of our joined tables, we'll start to see incorrect results.

Running a basic count on the joined table, we will no longer get the correct number of customers:

```sql
SELECT
    COUNT(*) as count
FROM
    customer
LEFT JOIN order
USING (customer_id)
```

SQL will count up the rows in the table as follows:

<table data-header-hidden><thead><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr><td><pre><code>count
</code></pre></td><td>customer_id</td><td>first_name</td><td>last_name</td><td>visits</td><td>order_id</td><td>amount</td><td>customer_id</td></tr><tr><td>+ 1</td><td>1</td><td>Jean</td><td>de la Fontaine</td><td>2</td><td>1</td><td>25.00</td><td>1</td></tr><tr><td>+ 1</td><td>1</td><td>Jean</td><td>de la Fontaine</td><td>2</td><td>2</td><td>50.00</td><td>1</td></tr><tr><td>+ 1</td><td>2</td><td>Émile</td><td>Zola</td><td>2</td><td>3</td><td>75.00</td><td>2</td></tr><tr><td>+ 1</td><td>3</td><td>René</td><td>Descartes</td><td>4</td><td>4</td><td>100.00</td><td>3</td></tr><tr><td><strong>Results ⤵️</strong></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td><code>count</code></td><td>4 (❌)</td><td></td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table>

\
We'll get a result of 4, even though there are really only 3 customers. You can see that Jean is counted twice.

Similarly, if we try to sum the number of visits, we will no longer get the correct result:

```sql
SELECT
    SUM(visits) as sum
FROM
    customer
LEFT JOIN order
USING (customer_id)
```

SQL will add up the number of visits in the table as follows:

<table><thead><tr><th width="176">customer_id</th><th>visits</th><th>sum</th><th>first_name</th><th>last_name</th><th>order_id</th><th>amount</th><th>customer_id</th></tr></thead><tbody><tr><td>1</td><td>2</td><td>+ 2</td><td>Jean</td><td>de la Fontaine</td><td>1</td><td>25.00</td><td>1</td></tr><tr><td>1</td><td>2</td><td>+ 2</td><td>Jean</td><td>de la Fontaine</td><td>2</td><td>50.00</td><td>1</td></tr><tr><td>2</td><td>2</td><td>+ 2</td><td>Émile</td><td>Zola</td><td>3</td><td>75.00</td><td>2</td></tr><tr><td>3</td><td>4</td><td>+ 4</td><td>René</td><td>Descartes</td><td>4</td><td>100.00</td><td>3</td></tr><tr><td><strong>Results ⤵️</strong></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td><code>SUM</code></td><td>10 (❌)</td><td></td><td></td><td> </td><td></td><td></td><td></td></tr></tbody></table>

\
We'll get a result of 10, even though there are only 8 visits. Jean's 2 visits are added twice.\
&#x20;

## A fanout happened while you weren't looking

\
In the example we've been looking at, the primary table (customer) had only three rows. The "primary table" is the table that is in the `FROM` clause of our SQL queries. After the join, we now have 4 rows. Since the joined table has more rows than the primary table, we say that a *fanout* has occurred.

To avoid a fanout, we can write the join in the opposite order. So, instead of this:

```sql
SELECT *
FROM customer
LEFT JOIN order
USING (customer_id)
```

We would do this:

```sql
SELECT *
FROM order
LEFT JOIN customer
USING (customer_id)
```

Now our joined table looks like this:

| order\_id | amount | customer\_id | first\_name | last\_name     | visits |
| --------- | ------ | ------------ | ----------- | -------------- | ------ |
| 1         | 25.00  | 1            | Jean        | de la Fontaine | 2      |
| 2         | 50.00  | 1            | Jean        | de la Fontaine | 2      |
| 3         | 75.00  | 2            | Émile       | Zola           | 2      |
| 4         | 100.00 | 3            | René        | Descartes      | 4      |

\
The eagle-eyed observer will recognize that this is exactly like our other joined table, except for the order of the columns. That is true, and it's why most writers of SQL think of our two different joins as exactly the same.

However, when we do the join in this order, we do not have a fanout. Our original table (order) had four rows, and our joined table also has four rows, so there has been no fanout.&#x20;

{% hint style="info" %}
Herein lies a key point: To help avoid fanouts, begin your joins with the most granular table.
{% endhint %}

### Fanouts, schmanouts, who cares?

\
In both the fanout and the non-fanout cases, we still need to worry about the accuracy of aggregate functions. However, there is a subtle difference in the type of problems we're going to see:

* **No fanout** 😨 — You can trust aggregate functions on your primary table but not necessarily on your joined tables.
* **Fanout 😱** — You cannot necessarily trust aggregate functions on either your primary table or your joined tables.

To drive home this point, let's take a look at our previous examples.

### Fanout example

\
If you'll recall from the previous example, the following join resulted in a fanout, because while the customer table only had three rows, the joined table had four rows.

```sql
SELECT *
FROM customer
LEFT JOIN order
USING (customer_id)
```

| customer\_id | first\_name | last\_name     | visits | order\_id | amount |
| ------------ | ----------- | -------------- | ------ | --------- | ------ |
| 1            | Jean        | de la Fontaine | 2      | 1         | 25.00  |
| 1            | Jean        | de la Fontaine | 2      | 2         | 50.00  |
| 2            | Émile       | Zola           | 2      | 3         | 75.00  |
| 3            | René        | Descartes      | 4      | 4         | 100.00 |

\
Since we are in a fanout situation, we cannot trust that aggregate functions will work on the primary table (customer). As we saw previously, `SUM(visits)` will give us a value of 10, even though only 8 visits have actually occurred.\
&#x20;

### No fanout example

\
When we reversed the join, we did not get a fanout, **because the order table had four rows, and the joined table also had four rows.**

```sql
SELECT *
FROM order
LEFT JOIN customer
USING (customer_id)
```

| order\_id | amount | customer\_id | first\_name | last\_name     | visits |
| --------- | ------ | ------------ | ----------- | -------------- | ------ |
| 1         | 25.00  | 1            | Jean        | de la Fontaine | 2      |
| 2         | 50.00  | 1            | Jean        | de la Fontaine | 2      |
| 3         | 75.00  | 2            | Émile       | Zola           | 2      |
| 4         | 100.00 | 3            | René        | Descartes      | 4      |

\
Without any fanout, we can trust that aggregate functions will work on the primary table (order).&#x20;

For example, `SUM(amount)` will give us a value of 250.00, which is the correct amount of money collected 👍

However, we can't trust any aggregation done on the secondary table (customer), such as `SUM(visits)` = 10 (❌)\
&#x20;

## Two friendly joins, one frenemy join

\
If we want to avoid fanouts, it's important that we understand the three different types of joins.

### One-to-one (friendly)

If one row of your primary table only ever matches up with one and only one row of your joined table, you have a one-to-one join. This type of join will not result in a fanout, and aggregate functions will be accurate no matter where you use them.

**Example**: Suppose you have a person table and a DNA table. Since only one person can be matched with one DNA record and one DNA record with one person, this is a one-to-one join.

### Many-to-one / Belongs to (friendly)

If many rows of your primary table match up with the same row in your joined table, you have a many-to-one join. This type of join will also not result in a fanout, and aggregate functions will at least be accurate on the primary table.

**Example**: Suppose you have a order table and a customer table. Since many order can be made from a single customer, you have a many-to-one relationship.

### One-to-many (frenemy)

If one row of your primary table can match up with multiple rows in your joined table, you have a one-to-many join. This type of join can result in a fanout, and aggregate functions are not necessarily accurate anywhere.

**Example**: Suppose you have a customer table and an order table. Since one customer can have more than one order, this is a one-to-many join.

## A fanout witch hunt

### Understand your join type

The first, and preferred, method to check for a fanout is to understand the type of join that is occurring. One-to-one and many-to-one joins won't ever result in a fanout. However, if you know you are in a one-to-many situation, then there will always be the risk of a fanout. Even if a fanout has not already occurred, it will be a risk in the future if new rows are added.

### Count rows before and after the join

The second method you can use to check for a fanout is to query a `COUNT` before and after the join. The queries would look like this:

```
SELECT COUNT(*)
FROM   my_primary_table

SELECT    COUNT(*)
FROM      my_primary_table
LEFT JOIN my_joined_table
USING (my_join_column)
```

If the count increases between the two queries, we know that a fanout has occurred. Since we're looking for an increase, it's important that the second query use a `LEFT JOIN`. We don't want to artificially decrease the number of rows being reported just because a row in the primary table doesn't have a corresponding row in the joined table.

Unfortunately, this method cannot tell you if there is a risk of a future fanout. To know that, you need to understand the type of join that is occurring. This is lies in how defined is your business models and what is the functional relationships between your tables.

## Whaly to the rescue

\
If you're one of the lucky folks who use Whaly, we got you covered.

Thanks to:

* the type of the relationships (Has Many / Belongs To / One to One) that you defined when creating your models
* &#x20;to the Primary Keys that can help deduplicate the records in your tables

Whenever Whaly needs to generate a SQL query that will contains a fan-out, our SQL engine generator will avoid it by generating dynamics sub-queries that are using the DISTINCT keywords to avoid getting false results for your aggregates calculations.

This way, any JOINs that are generated through Explorations and Related Data configured inside it will be safe 🤗

## Quick summary

\
To summarize everything we've just covered:

* Aggregate functions like `SUM` and `COUNT` can misbehave if used against joined tables.
* If a join has a one-to-one relationship, aggregate functions will work just fine.
* If a join has a many-to-one relationship, aggregate functions will work on the primary table but might not work on the joined tables.
* If a join has a one-to-many relationship, aggregate functions may not work anywhere.
* If you're using Whaly and joining tables in your Explore, you don't need to think about any of that again 🤗 🐳


# Backup your data using BigQuery

**Objective:** in this guide we will learn how to configure BigQuery in order to create daily data backups. This might prove useful if you want to keep an history of your tools data, like your CRM or finance software.

**Prerequisite**: in oder to follow this guide, you will need to have an admin access to the google.

## Backup a table using BigQuery :&#x20;

### 1. Enable the BigQuery data transfer API&#x20;

The first thing we will need to do is enable the BigQuery data transfer API. This allows to schedule queries on a regular basis.

In order to activate the service:

1. Head over to the API page <https://console.cloud.google.com/apis/api/bigquerydatatransfer.googleapis.com/metrics>
2. Click on "Enable API"

{% hint style="info" %}
There might be some propagation delay before this service is seen as activated for Google BigQuery
{% endhint %}

### 2. Create a new dataset in BigQuery

Now we will need to create a new dataset in BigQuery:&#x20;

1. Open the BigQuery Console
2. Create a new dataset in your desired project&#x20;
   1. Give it a name, for example if we want to backup our `jaffle_shop` dataset, we will name it `jaffle_shop_backup`
   2. Set the region to the same one as your initial table
   3. Click on create
3. Create a table in the new dataset
   1. Give it a name, for example if we want to backup our `customer` table, we will name it `customer_history`
   2. Click on create

![](/files/MSaqI6t7RLnfK0EJtseR)

### 3. Write the backup query

Let's write the query that we will use to backup our table:

* In the BigQuery console, open the table you want to backup
* Click on "Query"
* You should now see a Query looking like the following&#x20;

```sql
SELECT FROM "whaly-temp-migration-guide.jaffle_shop.customer"LIMIT 1000
```

* Edit it in order to remove the limit, select every columns in our table and add a new column named `snapshot_ts` that will be populated with the backup timestamp. The final query should look like

```sql
SELECT *, @run_time as snapshot_ts FROM "whaly-temp-migration-guide.jaffle_shop.customer"
```

### 4. Schedule the backup

Now we will add our query to the scheduler:

* Click on Schedule -> Create a new scheduled query
* Give it a name
* Set the schedule according to your needs
* Set the dataset and table to the ones created during step 2
* Set Append to table under Destination table write preference: this will tell BigQuery to create a full backup of the table each day
* Click on save 💾

### 5. Verify the query execution

Now that our query is scheduled, we should check that it's running correctly:&#x20;

* Open the schedule query panel from the left menu bar: <https://console.cloud.google.com/bigquery/scheduled-queries?project=whaly-temp-migration-guide>
* Check that your query is listed in the summary table
* If you see any error, follow google debugging info to fix them

![](/files/tjgqPd40CNUJv0mUc4Gt)

That's it :tada:


# Embedding reports in Salesforce

This guide aims at explaining how to embed a report in salesforce on the home page or on a specific object page.

In order to embed Whaly reports in Salesforce, you need to:

* Be an developer on your Salesforce org
* Have at least a report created in your Whaly instance

## Setting up your Salesforce org

First of all you need to create the necessary component in your salesforce org to embed Whaly. In order to speed up the process we have created a git repository with all the code required to embed Whaly.&#x20;

{% embed url="<https://github.com/whalyapp/salesforce-embed>" %}

## Adding your app to your home or object page

In order to add your newly created app to your home page or record page, you should navigate to the page you wish to embed your report on and click on settings and edit page as shown below.

![Embeding Whaly](/files/rY1Sxkl3NP0cUq3g1U5D)

Once you are on the edition menu you can drag and drop the WhalyEmbed app and enter your credentials as show below.

![Page builder](/files/UnNu5K8TSrVKdQY2VRGO)

## Forwarding context to your report

By default the app that we created on salesforce pass three filters

* userId: Contains the connected user ID from Salesforce
* userEmail: Contains the connected user email from Salesforce
* recordId: When displaying your report on a record page you will get the current recordId otherwise you should expect it to be null

Therefore on Whaly side you can set up filters with a api name to either userId, userEmail, or objectId to take advantage of those prebuilt features.

## To go further

You can read our documentation on how our embedding mechanism works there:

{% embed url="<https://docs.whaly.io/data-management/reports/embed-a-report>" %}

Learn how to deploy a lightning app

<https://developer.salesforce.com/docs/atlas.en-us.236.0.apexcode.meta/apexcode/apex_debug_test_deploy.htm>

[<br>](<https://developer.salesforce.com/docs/atlas.en-us.236.0.apexcode.meta/apexcode/apex_debug_test_deploy.htm&#xA;>)


# Useful SQL operations

In the section you will find examples of use sql queries that are used in data projects


# Flattening categories

## Input

When dealing with categories it is common to encounter data that may be under such format :&#x20;

| id | toppings              |
| -- | --------------------- |
| 1  | salad;tomatoes;onions |
| 2  | cheese;bacon          |
| 3  | null                  |

## Output

Ideally, the structure we would like to have to enable data analysis on such data format would be the following :&#x20;

| id | toppings |
| -- | -------- |
| 1  | salad    |
| 1  | tomatoes |
| 1  | onions   |
| 2  | cheese   |
| 2  | bacon    |

\
Example query
-------------

Let's see an example in order to achieve this result :&#x20;

{% tabs %}
{% tab title="BigQuery" %}

```sql
-- this is our fake database
WITH
  database AS (
    SELECT
      1 AS id,
      "salad;tomatoes;onions" AS toppings
    UNION ALL
    SELECT
      2 AS id,
      "cheese;bacon" AS toppings
    UNION ALL
    SELECT
      3 AS id,
      null AS toppings
  )
  
-- this is the output of the model
-- 1. we start by selecting our two columns "id" and "toppings"

-- 2. then we split the toppings using the ; delimiter character
--    this will generatate an array
--    https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split

-- 3. Then we unnest the array, and flatten it using unnest and cross join
--    this will give us the expected output format
--    https://cloud.google.com/bigquery/docs/reference/standard-sql/arrays#flattening_arrays
SELECT
  id,
  toppings
FROM
  database
  CROSS JOIN UNNEST(SPLIT(toppings, ";")) as toppings
```

{% endtab %}

{% tab title="Snowflake" %}

```sql
with
  database as (
    select
      1 as id,
      'salad;tomatoes;onions' as toppings
    union all
    select
      2 as id,
      'cheese;bacon' as toppings
    union all
    select
      3 as id,
      null as toppings
  )
select d.id, f.value::string as toppings
from database d,
lateral flatten
(input=>split(d.toppings, ';')) f
```

{% endtab %}
{% endtabs %}


