Write-up

Technical Write-up

Imports

import duckdb
import os
cwd = os.getcwd()

Olist connects small businesses from all over Brazil to channels without hassle and with a single contract. Those merchants are able to sell their products through the Olist Store and ship them directly to the customers using Olist logistics partners.

Basically, the customer flow is 1. customers browse through products provided by various sellers. 2. If they like them, they’ll order. They can have multiple order_items. 3. Once customers are ready, they’ll head through order_payments process and wait for delivery. 4. After customers confirm that their received their items OR after estimated delivary date, customers will be given prompt to order_reviews.

Staging

Choosing which tables and columns to use and making sure they are casted into the right type

stg_customers = f'''
CREATE OR REPLACE VIEW stg_customers AS
SELECT 
    customer_id::varchar AS customer_id, 
    customer_unique_id::varchar AS customer_unique_id,
    customer_state::varchar AS customer_state
FROM read_csv_auto('../raw_data/olist_customers_dataset.csv')
'''

stg_products = f'''
CREATE OR REPLACE VIEW stg_products AS
SELECT 
    product_id::varchar AS product_id, 
    COALESCE(product_category_name_english, products.product_category_name)::varchar AS category_name
    FROM read_csv_auto('../raw_data/olist_products_dataset.csv') AS products
    LEFT JOIN read_csv_auto('../raw_data/product_category_name_translation.csv') AS category_translation ON products.product_category_name = category_translation.product_category_name
'''

stg_orders = f'''
CREATE OR REPLACE VIEW stg_orders AS
SELECT
    order_id::varchar AS order_id,
    order_status::varchar AS order_status,
    customer_id::varchar AS customer_id,
    order_purchase_timestamp::timestamp AS order_purchase,
    order_estimated_delivery_date::timestamp AS order_estimated_delivery,
    order_delivered_carrier_date::timestamp AS order_delivered_carrier,
    order_delivered_customer_date::timestamp AS order_delivered_customer
FROM read_csv_auto('../raw_data/olist_orders_dataset.csv') AS orders
'''

stg_order_items = f'''
CREATE OR REPLACE VIEW stg_order_items AS
SELECT
    order_id::varchar AS order_id,
    product_id::varchar AS product_id,
    seller_id::varchar AS seller_id,
    price::decimal(10,2) AS price,
    freight_value::decimal(10,2) AS freight_value
FROM read_csv_auto('../raw_data/olist_order_items_dataset.csv') AS order_items
'''

stg_order_payments = f'''
CREATE OR REPLACE VIEW stg_order_payments AS
SELECT
    order_id::varchar AS order_id,
    payment_value::decimal(10,2) AS payment_value,
    payment_installments::smallint AS payment_installments,
    payment_type::varchar AS payment_type
FROM read_csv_auto('../raw_data/olist_order_payments_dataset.csv') AS order_payments
'''

stg_order_reviews = f'''
CREATE OR REPLACE VIEW stg_order_reviews AS
SELECT
    review_id::varchar AS review_id,
    order_id::varchar AS order_id,
    review_score::smallint AS review_score,
    review_answer_timestamp::timestamp AS review_answer
FROM read_csv_auto('../raw_data/olist_order_reviews_dataset.csv') AS order_reviews
'''


if os.path.exists(f"{cwd}/../sql/olist.db"):
    os.remove(f"{cwd}/../sql/olist.db")

con = duckdb.connect(f"{cwd}/../sql/olist.db")
con.execute(open(f"{cwd}/../sql/01_staging.sql").read())
<_duckdb.DuckDBPyConnection at 0x10bf415f0>

Dashboard Sets

A possible dashboard set is product - category satisfaction.

  • The overview, frontpage would be the general health. Which would be, daily checkouts, unique customers, how many items bought, how many sellers involved in checkouts (a bit obscure). How many unique items (if the company care about expanding reach).

  • Customers retention per category. Explains what categories drive purchasing habit from customers (Maybe a bit weird because various categories would have different periods if they have that property)

  • Categories. Find out what categories are dominating, How much each categories contributing in revenue, Reviews per category.

  • Deliveries. Successful deliveries, time to deliver and reviews

Let us check if any of these are feasible with our data

Overview Page

General information about the platform’s health.

overview_sql = f'''
WITH categories AS (
    SELECT 
        product_id, 
        category_name
    FROM stg_products AS products
),
order_summary AS (
    SELECT 
        order_id, 
        COUNT(*) AS item_count, 
        product_id, 
        seller_id, 
        SUM(price) AS price,
        SUM(freight_value) AS freight_value
    FROM stg_order_items
    GROUP BY order_id, product_id, seller_id
)

SELECT 
    DATE(orders.order_purchase) AS order_date,
    orders.order_id, 
    customers.customer_unique_id, 
    orders.order_status, 
    order_summary.product_id, 
    order_summary.item_count,
    order_summary.seller_id,
    order_summary.price,
    order_summary.freight_value,
    COALESCE(categories.category_name, 'Uncategorized') AS category_name
FROM stg_orders AS orders
INNER JOIN order_summary ON orders.order_id = order_summary.order_id
LEFT JOIN categories ON order_summary.product_id = categories.product_id
LEFT JOIN stg_customers AS customers ON orders.customer_id = customers.customer_id
'''

con.sql(f"CREATE OR REPLACE TABLE overview AS {overview_sql}")
con.sql(f"SELECT * FROM overview LIMIT 5")
┌────────────┬──────────────────────────────────┬──────────────────────────────────┬──────────────┬──────────────────────────────────┬────────────┬──────────────────────────────────┬───────────────┬───────────────┬───────────────────────────┐
│ order_date │             order_id             │        customer_unique_id        │ order_status │            product_id            │ item_count │            seller_id             │     price     │ freight_value │       category_name       │
│    date    │             varchar              │             varchar              │   varchar    │             varchar              │   int64    │             varchar              │ decimal(38,2) │ decimal(38,2) │          varchar          │
├────────────┼──────────────────────────────────┼──────────────────────────────────┼──────────────┼──────────────────────────────────┼────────────┼──────────────────────────────────┼───────────────┼───────────────┼───────────────────────────┤
│ 2018-08-15 │ 0028de0ca693a1bb26448916a81105cc │ f94d7c267f1bb6956a0c6c6fef65da8b │ delivered    │ 059344baebbeaa42fa9f2bbe11b1583e │          1 │ 955fee9216a65b617aa5c0531780ce60 │         29.99 │         15.31 │ construction_tools_lights │
│ 2018-05-02 │ 0029f17cf0e7640c5cb6825af681303f │ 83e1722d7fa45e1393a1226cd68c46b1 │ delivered    │ 01c666c82f414c762ad21bffa56e8b49 │          1 │ d3dcf0604eabf0224fbd5948b5e02f69 │         94.90 │         18.54 │ toys                      │
│ 2017-05-18 │ 003324c70b19a16798817b2b3640e721 │ fbe6316a06058c651539cbf59ec5a0ef │ delivered    │ 2b939dc9b176d7fa21594d588815d4a4 │          2 │ dbc22125167c298ef99da25668e1011f │        205.80 │         28.90 │ luggage_accessories       │
│ 2017-08-10 │ 0045e3085f083f0f38d24bb3f22e6593 │ 5eb8cc30e45a67bdb05240e7e735967d │ delivered    │ 9545d45c37449ccbc376de3a04c66e71 │          1 │ 431af27f296bc6519d890aa5a05fdb11 │        116.90 │         13.84 │ health_beauty             │
│ 2018-05-03 │ 0079bca8e89bd52fdb87408e4f3fb94d │ 9bc0afc85b2f40c797f96f7911397708 │ delivered    │ 19421075ae0b585f2dc13ff149e2119d │          1 │ 4c2b230173bb36f9b240f2b8ac11786e │         49.90 │          7.39 │ sports_leisure            │
└────────────┴──────────────────────────────────┴──────────────────────────────────┴──────────────┴──────────────────────────────────┴────────────┴──────────────────────────────────┴───────────────┴───────────────┴───────────────────────────┘

Sanity checks:

Payment total from overview derived from order_items, matches order_payments

con.sql(f'''
WITH payment_table AS (
    SELECT order_id, ROUND(SUM(payment_value), 2)::DECIMAL(10,2) AS payment_total, MAX(payment_installments) AS payment_installments
    FROM stg_order_payments
    GROUP BY order_id
)

SELECT 
    ROUND(MEDIAN(payment_table.payment_total/overview_agg.payment_total), 2)::DECIMAL(10,2) AS error_ratio_median, 
    payment_installments, 
    COUNT(*) AS count
FROM (
    SELECT order_id, SUM(price+freight_value) AS payment_total
    FROM overview
    GROUP BY order_id
    ) AS overview_agg
LEFT JOIN payment_table ON overview_agg.order_id = payment_table.order_id
WHERE overview_agg.payment_total != payment_table.payment_total AND ABS(overview_agg.payment_total - payment_table.payment_total) > 0.02
GROUP BY payment_installments
ORDER BY payment_installments 
''')
┌────────────────────┬──────────────────────┬───────┐
│ error_ratio_median │ payment_installments │ count │
│   decimal(10,2)    │        int16         │ int64 │
├────────────────────┼──────────────────────┼───────┤
│               0.97 │                    1 │    20 │
│               1.03 │                    2 │    12 │
│               1.05 │                    3 │    35 │
│               1.06 │                    4 │    37 │
│               1.07 │                    5 │    32 │
│               1.08 │                    6 │    31 │
│               1.13 │                    7 │    18 │
│               1.13 │                    8 │    16 │
│               1.13 │                    9 │     7 │
│               1.13 │                   10 │    35 │
│               1.10 │                   11 │     4 │
│               1.16 │                   12 │    14 │
│               1.24 │                   13 │     1 │
│               1.24 │                   15 │     2 │
│               1.24 │                   20 │     1 │
│               1.25 │                   21 │     1 │
│               1.29 │                   24 │     1 │
└────────────────────┴──────────────────────┴───────┘
  17 rows                                 3 columns

Most likely credit card interest that causes discrepencies. Normally, we would decide that total from order_items should be the one used since bank interest is none of our concern. However, for one installments, the median is 0.97, which should not happen. There are 20 discrepencies for 1 installments, let us take a look first before deciding anything

con.sql(f'''
WITH payment_table AS (
    SELECT order_id, ROUND(SUM(payment_value), 2)::DECIMAL(10,2) AS payment_total, MAX(payment_installments) AS payment_installments,
    ARRAY_AGG(payment_type) AS payment_type
    FROM stg_order_payments
    GROUP BY order_id
),
categories AS (
    SELECT product_id, category_name
    FROM stg_products AS products
)

SELECT 
    payment_type, overview_agg.payment_total, payment_table.payment_total
FROM (
    SELECT order_id, SUM(price+freight_value) AS payment_total
    FROM overview
    GROUP BY order_id
    ) AS overview_agg
LEFT JOIN payment_table ON overview_agg.order_id = payment_table.order_id
WHERE overview_agg.payment_total != payment_table.payment_total AND ABS(overview_agg.payment_total - payment_table.payment_total) > 0.02
    AND payment_installments = 1
ORDER BY payment_installments 
''')
┌────────────────────────────┬───────────────┬───────────────┐
│        payment_type        │ payment_total │ payment_total │
│         varchar[]          │ decimal(38,2) │ decimal(10,2) │
├────────────────────────────┼───────────────┼───────────────┤
│ [credit_card, credit_card] │         28.75 │         30.30 │
│ [voucher, credit_card]     │         94.59 │         94.95 │
│ [voucher, credit_card]     │         55.80 │         57.60 │
│ [credit_card]              │         17.38 │         16.38 │
│ [credit_card]              │         37.37 │         34.38 │
│ [debit_card]               │         33.43 │         30.83 │
│ [debit_card]               │        333.92 │        318.97 │
│ [credit_card]              │        115.65 │        105.26 │
│ [debit_card]               │         47.31 │         45.71 │
│ [debit_card]               │        350.41 │        333.91 │
│ [credit_card, voucher]     │         44.09 │         45.42 │
│ [debit_card]               │         59.00 │         56.50 │
│ [boleto]                   │       1972.00 │       1972.03 │
│ [credit_card, voucher]     │         50.10 │         50.70 │
│ [credit_card]              │         17.77 │         18.38 │
│ [credit_card]              │         35.14 │         25.14 │
│ [boleto]                   │        224.14 │        224.17 │
│ [debit_card]               │        231.92 │        222.02 │
│ [debit_card]               │        161.47 │        157.57 │
│ [boleto]                   │        524.32 │        524.28 │
└────────────────────────────┴───────────────┴───────────────┘
  20 rows                                          3 columns

So we have three payment type that can cause discrepencies and we have no clue what causes it for the first installments. Nevertheless, we determined that the total payments from order_payments table are affected by external causes that does not directly affect our revenue. Hence, we will be using total payments derived from price + freight_value.

It is unfortunate that we do not have a good sanity check in this, but with the limited information that we have, this is all we can do.

NULL values

con.sql(f'''
SUMMARIZE overview
'''
)
┌────────────────────┬───────────────┬──────────────────────────────────┬──────────────────────────────────┬───────────────┬────────────────────────────┬────────────────────┬────────────┬────────────┬────────────┬────────┬─────────────────┐
│    column_name     │  column_type  │               min                │               max                │ approx_unique │            avg             │        std         │    q25     │    q50     │    q75     │ count  │ null_percentage │
│      varchar       │    varchar    │             varchar              │             varchar              │     int64     │          varchar           │      varchar       │  varchar   │  varchar   │  varchar   │ int64  │  decimal(9,2)   │
├────────────────────┼───────────────┼──────────────────────────────────┼──────────────────────────────────┼───────────────┼────────────────────────────┼────────────────────┼────────────┼────────────┼────────────┼────────┼─────────────────┤
│ order_date         │ DATE          │ 2016-09-04                       │ 2018-09-03                       │           727 │ 2017-12-31 07:14:06.109836 │ NULL               │ 2017-09-13 │ 2018-01-19 │ 2018-05-05 │ 102425 │            0.00 │
│ order_id           │ VARCHAR       │ 00010242fe8c5a6d1ba2dd792cb16214 │ fffe41c64501cc87c801fd61db3f6244 │         87618 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
│ customer_unique_id │ VARCHAR       │ 0000366f3b9a7992bf8c76cfdf3221e2 │ ffffd2657e2aad2907e67c3e9daecbeb │         93296 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
│ order_status       │ VARCHAR       │ approved                         │ unavailable                      │             7 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
│ product_id         │ VARCHAR       │ 00066f42aeeb9f3007548bb9d3f33c38 │ fffe9eeff12fcbd74a2f2b007dde0c58 │         24292 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
│ item_count         │ BIGINT        │ 1                                │ 20                               │            18 │ 1.0998291432755676         │ 0.4569262566527153 │ 1          │ 1          │ 1          │ 102425 │            0.00 │
│ seller_id          │ VARCHAR       │ 0015a82c2db000af6aaaf3ae2ecb0532 │ ffff564a4f9085cd26170f4732393726 │          3886 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
│ price              │ DECIMAL(38,2) │ 0.85                             │ 13440.00                         │          7557 │ 132.69849841347326         │ 205.30884670665242 │ 45         │ 82         │ 147        │ 102425 │            0.00 │
│ freight_value      │ DECIMAL(38,2) │ 0.00                             │ 1794.96                          │          7602 │ 21.98593644129851          │ 20.464658702056713 │ 13         │ 17         │ 23         │ 102425 │            0.00 │
│ category_name      │ VARCHAR       │ Uncategorized                    │ watches_gifts                    │            76 │ NULL                       │ NULL               │ NULL       │ NULL       │ NULL       │ 102425 │            0.00 │
└────────────────────┴───────────────┴──────────────────────────────────┴──────────────────────────────────┴───────────────┴────────────────────────────┴────────────────────┴────────────┴────────────┴────────────┴────────┴─────────────────┘
  10 rows                                                                                                                                                                                                                           12 columns

Some nulls in category_name

con.sql(f'''
SELECT *
FROM stg_products
WHERE product_id = '4914f8796af2ecd359fd8f44b9b92339'
'''
)
┌──────────────────────────────────┬───────────────┐
│            product_id            │ category_name │
│             varchar              │    varchar    │
├──────────────────────────────────┼───────────────┤
│ 4914f8796af2ecd359fd8f44b9b92339 │ NULL          │
└──────────────────────────────────┴───────────────┘

Nothing we can do about this.

We are in the clear for overview now.

Customers retention

First, we have to confirm whether there are repeat orders from the same customer. Then, we need to see how many are there any how spaced out are they. The first problem that arised is that we have no first purchase timestamp or first sign up timestamp, which discards the option to have a cohort analysis.

con.sql(f'''
SELECT customers.customer_unique_id, COUNT(DISTINCT orders.customer_id) AS order_count
FROM stg_customers AS customers
RIGHT JOIN stg_orders AS orders ON customers.customer_id = orders.customer_id
GROUP BY customer_unique_id
HAVING order_count > 1
ORDER BY order_count DESC
''')
┌──────────────────────────────────┬─────────────┐
│        customer_unique_id        │ order_count │
│             varchar              │    int64    │
├──────────────────────────────────┼─────────────┤
│ 8d50f5eadf50201ccdcedfb9e2ac8455 │          17 │
│ 3e43e6105506432c953e165fb2acf44c │           9 │
│ 1b6c7548a2a1f9037c1fd3ddfed95f33 │           7 │
│ ca77025e7201e3b30c44b472ff346268 │           7 │
│ 6469f99c1f9dfae7733b25662e7f1782 │           7 │
│ 63cfc61cee11cbe306bff5857d00bfe4 │           6 │
│ f0e310a6839dce9de1638e0fe5ab282a │           6 │
│ dc813062e0fc23409cd255f7f53c7074 │           6 │
│ de34b16117594161a6a89c50b289d35a │           6 │
│ 47c1a3033b8b77b3ab6e109eb4d5fdf3 │           6 │
│                ·                 │           · │
│                ·                 │           · │
│                ·                 │           · │
│ f172c33b146e90b95ee12499eb6632cb │           2 │
│ 8bc85b1061383d0dcd4e995a726da763 │           2 │
│ acc374bd9024806a83121b1d2565db2f │           2 │
│ e68856e8450afe7390b02d1b2bcefb29 │           2 │
│ 36be413fe0f4a74eee7b764e758730ed │           2 │
│ 09ab36a420b5c87c5c9772bdb494f2e3 │           2 │
│ 72421b30e46c895e99e8560a48a33fb6 │           2 │
│ 73f97c6a5af65a71194cc2265ae82501 │           2 │
│ 73de7b7b976fc6b75283b05f67fd6b5b │           2 │
│ d5f79f616536f08c3946ee6ac810a43a │           2 │
└──────────────────────────────────┴─────────────┘
  2997 rows (20 shown)                 2 columns

~3000 out of ~99500 customers have multiple orders. Not a good amount. But, let’s see the timeline

# cust_uniq_id | order count | mean_gap | median_gap | min_gap | max_gap | 
con.sql(f'''
WITH multiple_orders_count AS (
    SELECT customers.customer_unique_id, COUNT(DISTINCT orders.customer_id) AS order_count
    FROM stg_customers AS customers
    RIGHT JOIN stg_orders AS orders ON customers.customer_id = orders.customer_id
    GROUP BY customer_unique_id
    HAVING order_count > 1
),
next_order_table AS (
    SELECT 
        customers.customer_unique_id, 
        customers.customer_id,
        LEAD(order_purchase::date, 1) OVER (PARTITION BY customers.customer_unique_id ORDER BY order_purchase) - order_purchase::date AS next_order_days
    FROM multiple_orders_count
    INNER JOIN stg_customers AS customers ON multiple_orders_count.customer_unique_id = customers.customer_unique_id
    LEFT JOIN stg_orders AS orders ON customers.customer_id = orders.customer_id
    QUALIFY next_order_days IS NOT NULL
)

SELECT 
    customer_unique_id,
    MEAN(next_order_days) AS mean_next_order,
    MEDIAN(next_order_days) AS median_next_order,
    MIN(next_order_days) AS min_next_order,
    MAX(next_order_days) AS max_next_order,
    COUNT(*)  AS order_count_after_first
FROM next_order_table
GROUP BY customer_unique_id
HAVING order_count_after_first > 1
''')
┌──────────────────────────────────┬────────────────────┬───────────────────┬────────────────┬────────────────┬─────────────────────────┐
│        customer_unique_id        │  mean_next_order   │ median_next_order │ min_next_order │ max_next_order │ order_count_after_first │
│             varchar              │       double       │      double       │     int64      │     int64      │          int64          │
├──────────────────────────────────┼────────────────────┼───────────────────┼────────────────┼────────────────┼─────────────────────────┤
│ 06a52782a04f0086d16b9c22d0e29438 │                0.0 │               0.0 │              0 │              0 │                       2 │
│ 18e3d276253780b44b5b7bf83f6785ec │              104.5 │             104.5 │              7 │            202 │                       2 │
│ 1b6e96ed99cb8d135efe220d761bbd67 │              255.5 │             255.5 │            213 │            298 │                       2 │
│ 310647380793836bfa5b7b6b3f518423 │               91.5 │              91.5 │             54 │            129 │                       2 │
│ 46958f708ade6b6a88b2e5aca1e66de5 │                0.0 │               0.0 │              0 │              0 │                       2 │
│ 4dcabe94c61f202327839a734955b0cf │                1.0 │               1.0 │              0 │              2 │                       2 │
│ 4facc2e6fbc2bffab2fea92d2b4aa7e4 │ 140.33333333333334 │              20.0 │              4 │            397 │                       3 │
│ 50cd97d731f3f5e3f321673c2671a23d │               48.5 │              48.5 │              0 │             97 │                       2 │
│ 5b09c620db60411b0be6f1817c6cbe71 │               75.5 │              75.5 │              0 │            151 │                       2 │
│ 6419a1be8feac26ec793667b71cbaeb4 │              102.5 │             102.5 │             56 │            149 │                       2 │
│                ·                 │                 ·  │                ·  │              · │              · │                       · │
│                ·                 │                 ·  │                ·  │              · │              · │                       · │
│                ·                 │                 ·  │                ·  │              · │              · │                       · │
│ 86df00dc5fd68f4dd5d5945ca19f3ed6 │               63.5 │              63.5 │             51 │             76 │                       2 │
│ 94a19681bf6faa3c6ce3e2df1c2d97cb │               38.5 │              38.5 │              2 │             75 │                       2 │
│ 9aae8213ff9bc04cc3ce48b85f43a173 │                0.0 │               0.0 │              0 │              0 │                       2 │
│ acea6bd29b8c1e3c6a8b266a8fb4475e │  82.33333333333333 │              73.0 │             22 │            152 │                       3 │
│ b9badb100ff8ecc16a403111209e3a06 │              241.0 │             241.0 │            137 │            345 │                       2 │
│ bf6656ef50e6fd00e7517b01d615b4f3 │               89.0 │              89.0 │             86 │             92 │                       2 │
│ cd11a0920c498edb1da9ecd2d31aa599 │               51.5 │              51.5 │              0 │            103 │                       2 │
│ d387ea85dc301a91740e31360d355686 │               62.5 │              62.5 │             62 │             63 │                       2 │
│ e13e8b789e5a8e6fe1445f924a4ed4f6 │                0.0 │               0.0 │              0 │              0 │                       2 │
│ e2492e4188991b6276a4a62a287a5451 │               34.0 │              34.0 │              5 │             63 │                       2 │
└──────────────────────────────────┴────────────────────┴───────────────────┴────────────────┴────────────────┴─────────────────────────┘
  252 rows (20 shown)                                                                                                         6 columns

Out of ~3000 customers with multiple orders, only 252 customers that have more than 2 orders. Because there are so few data, the statistics explaining next order in days looked erratic. Wtih all the problems due discovered due to lack of data, we will be dropping this page completely.

Categories

Explaining performances of categories. We want to see how revenue, late deliveries, reviews, and count are spread amongst categories. Unfortunately, for reviews and late deliveries, it is per order, not per item.

con.sql(
    "SELECT * FROM stg_order_reviews"
)
┌──────────────────────────────────┬──────────────────────────────────┬──────────────┬─────────────────────┐
│            review_id             │             order_id             │ review_score │    review_answer    │
│             varchar              │             varchar              │    int16     │      timestamp      │
├──────────────────────────────────┼──────────────────────────────────┼──────────────┼─────────────────────┤
│ 7bc2406110b926393aa56f80a40eba40 │ 73fc7af87114b39712e6da79b0a377eb │            4 │ 2018-01-18 21:46:59 │
│ 80e641a11e56f04c1ad469d5645fdfde │ a548910a1c6147796b98fdf73dbeba33 │            5 │ 2018-03-11 03:05:13 │
│ 228ce5500dc1d8e020d8d1322874b6f0 │ f9e4b658b201a9f2ecdecbb34bed034b │            5 │ 2018-02-18 14:36:24 │
│ e64fb393e7b32834bb789ff8bb30750e │ 658677c97b385a9be170737859d3511b │            5 │ 2017-04-21 22:02:06 │
│ f7c4243c7fe1938f181bec41a392bdeb │ 8e6bfb81e283fa7e4f11123a3fb894f1 │            5 │ 2018-03-02 10:26:53 │
│ 15197aa66ff4d0650b5434f1b46cda19 │ b18dcdf73be66366873cd26c5724d1dc │            1 │ 2018-04-16 00:39:37 │
│ 07f9bee5d1b850860defd761afa7ff16 │ e48aa0d2dcec3a2e87348811bcfdf22b │            5 │ 2017-07-18 19:30:34 │
│ 7c6400515c67679fbee952a7525281ef │ c31a859e34e3adac22f376954e19b39d │            5 │ 2018-08-14 21:36:06 │
│ a3f6f7f6f433de0aefbb97da197c554c │ 9c214ac970e84273583ab523dfafd09b │            5 │ 2017-05-18 12:05:37 │
│ 8670d52e15e00043ae7de4c01cc2fe06 │ b9bf720beb4ab3728760088589c62129 │            4 │ 2018-05-23 16:45:47 │
│                ·                 │                ·                 │            · │          ·          │
│                ·                 │                ·                 │            · │          ·          │
│                ·                 │                ·                 │            · │          ·          │
│ f8ac2ca757c6702a8f7b9f82a3361b54 │ b77e196ee314322dcec6124fa3e38b6b │            1 │ 2018-09-02 16:27:56 │
│ c5b14992eb69dc68e3e855cd95f0ed57 │ 7b374a3cd8b325fd49e6789ccfa88639 │            5 │ 2018-05-21 18:16:28 │
│ c210b4ed36c4ce629a8ce851be0d5828 │ de1c6db658e84e0ba847b2c1bc857bb5 │            1 │ 2018-04-17 22:55:46 │
│ 110160bcffa1c9bef8b1868aba109cf1 │ 930fff097a9427c8a9a70559aa36fa76 │            5 │ 2018-09-03 00:11:20 │
│ 3dd6dd8bd30dd50a2b15328239cfcd91 │ acfa87544c19d596707d25cf2f7dab61 │            1 │ 2018-08-27 03:32:14 │
│ 6a1edd1c07b9ceb1a031a9e3d0a0c69a │ 4ac735cd851ba30e7b280f7e045544a0 │            5 │ 2017-09-30 09:51:15 │
│ a2e0d982abe0a650156ec056ca4d1b85 │ a4244008bc97ae9cdc3807237fa83229 │            5 │ 2018-07-07 15:50:37 │
│ c5ab0934968065d986a65f00567d4c54 │ 8d74231228bdbc2e89fc0125abfc1c87 │            3 │ 2017-08-12 03:30:12 │
│ db5a5956ff1b3b42cea50a82a54bf781 │ 1644056a29868cbba3251a81fae0e1f8 │            2 │ 2017-05-24 13:02:42 │
│ 78fa666f7808563cf40c8eac105316bf │ 2a19516124bb3d5f4d0d07555104e322 │            4 │ 2017-03-19 21:48:51 │
└──────────────────────────────────┴──────────────────────────────────┴──────────────┴─────────────────────┘
  ? rows (>9999 rows, 20 shown)                                                                  4 columns
# order_id | category_name | order_status | item_count | payment_total | delivery_days | is_late | delay_days | review_score | is_single_category
categories_sql = f'''
WITH order_summary AS (
    SELECT 
        order_id,
        product_id,
        COUNT(*) AS item_count,
        SUM(price) AS price,
        SUM(freight_value) AS freight_value 
    FROM stg_order_items AS order_items
    GROUP BY order_id, product_id
),
category_table AS (
    SELECT products.product_id, category_name
    FROM stg_products AS products
),
order_categorized AS (
    SELECT 
        order_id,
        COALESCE(category_name, 'Uncategorized') AS category_name,
        SUM(item_count) AS item_count, 
        SUM(price) AS price,
        SUM(freight_value) AS freight_value
    FROM order_summary
    LEFT JOIN category_table ON order_summary.product_id = category_table.product_id
    GROUP BY order_id, category_name
),
single_category AS (
    SELECT order_id, 
        CASE
            WHEN ARRAY_LENGTH(ARRAY_AGG(DISTINCT category_name)) > 1 THEN FALSE
            ELSE TRUE
        END AS is_single_category
    FROM order_categorized
    GROUP BY order_id
),
reviews AS (
    SELECT order_id, review_score
    FROM stg_order_reviews
    QUALIFY ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY review_answer DESC) = 1
)

SELECT 
    orders.order_id,
    category_name, 
    order_status,
    item_count,
    price,
    freight_value,
    COALESCE(order_delivered_customer::date - order_purchase::date, NULL)  AS delivery_days,
    order_delivered_customer::date - order_estimated_delivery::date AS delay_days,
    CASE
        WHEN delay_days > 0 THEN TRUE
        WHEN delay_days <= 0 THEN FALSE
        ELSE NULL
    END AS is_late,
    review_score,
    is_single_category
FROM stg_orders AS orders
LEFT JOIN order_categorized ON orders.order_id = order_categorized.order_id
LEFT JOIN single_category ON orders.order_id = single_category.order_id
LEFT JOIN reviews AS reviews ON orders.order_id = reviews.order_id
'''

con.sql(f"CREATE OR REPLACE TABLE category AS {categories_sql}")
con.sql(f"SELECT * FROM category")
┌──────────────────────────────────┬───────────────────────────┬──────────────┬────────────┬───────────────┬───────────────┬───────────────┬────────────┬─────────┬──────────────┬────────────────────┐
│             order_id             │       category_name       │ order_status │ item_count │     price     │ freight_value │ delivery_days │ delay_days │ is_late │ review_score │ is_single_category │
│             varchar              │          varchar          │   varchar    │   int128   │ decimal(38,2) │ decimal(38,2) │     int64     │   int64    │ boolean │    int16     │      boolean       │
├──────────────────────────────────┼───────────────────────────┼──────────────┼────────────┼───────────────┼───────────────┼───────────────┼────────────┼─────────┼──────────────┼────────────────────┤
│ bb05bd3bbacf1e3c6026b43b44a6631c │ sports_leisure            │ delivered    │          1 │         22.30 │          8.88 │             8 │        -10 │ false   │            5 │ true               │
│ bb9a1bc465b8456a8d36e06370273b58 │ health_beauty             │ delivered    │          1 │         61.99 │         60.49 │            27 │          4 │ true    │            4 │ true               │
│ bbfb7c72fb40d3623c225c5b1bf1e989 │ watches_gifts             │ delivered    │          1 │         49.00 │          8.72 │             8 │        -12 │ false   │            4 │ true               │
│ be86cd9f96e674ebdbb586454780a1ea │ computers_accessories     │ delivered    │          1 │         52.00 │         15.11 │             8 │        -29 │ false   │            3 │ true               │
│ c32dd42a4b8a42c008777be9de862c10 │ telephony                 │ delivered    │          1 │         26.99 │         15.65 │            13 │        -14 │ false   │            4 │ true               │
│ c32fc9ccb03292ac2e7a2c2f027394ff │ watches_gifts             │ delivered    │          1 │         49.00 │         15.44 │             7 │        -21 │ false   │            4 │ true               │
│ c575d4ac51a449488b640e19092ab952 │ consoles_games            │ delivered    │          1 │         32.50 │         11.85 │             9 │        -10 │ false   │            5 │ true               │
│ c5f43352377291361dad9a7366ae1c89 │ telephony                 │ delivered    │          1 │         24.99 │         16.79 │           135 │        109 │ true    │            5 │ true               │
│ c6df71a18bb2d9e7d88ee3e4d7080982 │ telephony                 │ delivered    │          1 │         11.97 │          7.39 │            13 │         -5 │ false   │            4 │ true               │
│ c6f02030f6bc633b8073c50178eb2833 │ construction_tools_lights │ delivered    │          1 │        169.99 │         23.99 │             8 │        -21 │ false   │            5 │ true               │
│                ·                 │       ·                   │     ·        │          · │           ·   │           ·   │             · │          · │   ·     │            · │  ·                 │
│                ·                 │       ·                   │     ·        │          · │           ·   │           ·   │             · │          · │   ·     │            · │  ·                 │
│                ·                 │       ·                   │     ·        │          · │           ·   │           ·   │             · │          · │   ·     │            · │  ·                 │
│ 8d585a78c58bc6ee2c28323cfd8ce6c7 │ bed_bath_table            │ delivered    │          1 │         99.90 │         21.19 │            24 │         -2 │ false   │            5 │ true               │
│ 9562a9a598a619a1726397eb5246e623 │ housewares                │ delivered    │          1 │         26.40 │         15.29 │             7 │        -17 │ false   │            5 │ true               │
│ 97b6ab675f38dce08fc58f7e5ddfa1a7 │ baby                      │ delivered    │          1 │        240.00 │         35.48 │            42 │         -1 │ false   │            3 │ true               │
│ 99e1d586758dee362e01030739d3d87a │ bed_bath_table            │ delivered    │          1 │         56.99 │         22.98 │            19 │         -3 │ false   │            4 │ true               │
│ 9a3966c23190dbdbaabed08e8429c006 │ Uncategorized             │ delivered    │          1 │       3980.00 │         62.74 │             5 │        -50 │ false   │            5 │ true               │
│ 9add7e9338f3b8cac642ce405bb64655 │ furniture_decor           │ delivered    │          1 │        399.99 │        193.78 │            18 │        -12 │ false   │            5 │ true               │
│ a08c01d5f51d5678f97d00ad2f228f8f │ housewares                │ delivered    │          1 │         75.00 │         13.08 │             2 │        -16 │ false   │            5 │ true               │
│ a1119e009d1d91655405307f4c095818 │ drinks                    │ delivered    │          1 │         58.00 │          7.67 │            11 │          2 │ true    │            1 │ true               │
│ a1eca0e86d2739a6adeb18015e4bc249 │ baby                      │ delivered    │          1 │         50.00 │         16.11 │            18 │        -10 │ false   │            5 │ true               │
│ a4277113bcf10ed364a0182042fc8155 │ housewares                │ delivered    │          1 │         44.90 │         23.28 │             9 │        -16 │ false   │            5 │ true               │
└──────────────────────────────────┴───────────────────────────┴──────────────┴────────────┴───────────────┴───────────────┴───────────────┴────────────┴─────────┴──────────────┴────────────────────┘
  ? rows (>9999 rows, 20 shown)                                                                                                                                                            11 columns

Sanity checks

NULL values

con.sql(f'''
SUMMARIZE category
'''
)
┌────────────────────┬───────────────┬──────────────────────────────────┬──────────────────────────────────┬───────────────┬─────────────────────┬────────────────────┬─────────┬─────────┬─────────┬────────┬─────────────────┐
│    column_name     │  column_type  │               min                │               max                │ approx_unique │         avg         │        std         │   q25   │   q50   │   q75   │ count  │ null_percentage │
│      varchar       │    varchar    │             varchar              │             varchar              │     int64     │       varchar       │      varchar       │ varchar │ varchar │ varchar │ int64  │  decimal(9,2)   │
├────────────────────┼───────────────┼──────────────────────────────────┼──────────────────────────────────┼───────────────┼─────────────────────┼────────────────────┼─────────┼─────────┼─────────┼────────┼─────────────────┤
│ order_id           │ VARCHAR       │ 00010242fe8c5a6d1ba2dd792cb16214 │ fffe41c64501cc87c801fd61db3f6244 │         87618 │ NULL                │ NULL               │ NULL    │ NULL    │ NULL    │ 100245 │            0.00 │
│ category_name      │ VARCHAR       │ Uncategorized                    │ watches_gifts                    │            76 │ NULL                │ NULL               │ NULL    │ NULL    │ NULL    │ 100245 │            0.77 │
│ order_status       │ VARCHAR       │ approved                         │ unavailable                      │             9 │ NULL                │ NULL               │ NULL    │ NULL    │ NULL    │ 100245 │            0.00 │
│ item_count         │ HUGEINT       │ 1                                │ 21                               │            18 │ 1.1325022619885392  │ 0.522268687362423  │ 1       │ 1       │ 1       │ 100245 │            0.77 │
│ price              │ DECIMAL(38,2) │ 0.85                             │ 13440.00                         │          7753 │ 136.64063235146276  │ 209.5685056184807  │ 46      │ 86      │ 150     │ 100245 │            0.77 │
│ freight_value      │ DECIMAL(38,2) │ 0.00                             │ 1794.96                          │          7635 │ 22.639082537448477  │ 21.430904326478046 │ 14      │ 17      │ 24      │ 100245 │            0.77 │
│ delivery_days      │ BIGINT        │ 0                                │ 210                              │           155 │ 12.473287072731946  │ 9.536871750189059  │ 7       │ 10      │ 16      │ 100245 │            2.96 │
│ delay_days         │ BIGINT        │ -147                             │ 188                              │           191 │ -11.913060909791827 │ 10.18037246428974  │ -17     │ -12     │ -7      │ 100245 │            2.96 │
│ is_late            │ BOOLEAN       │ false                            │ true                             │             2 │ NULL                │ NULL               │ NULL    │ NULL    │ NULL    │ 100245 │            2.96 │
│ review_score       │ SMALLINT      │ 1                                │ 5                                │             5 │ 4.078032793477496   │ 1.3539790874408633 │ 4       │ 5       │ 5       │ 100245 │            0.77 │
│ is_single_category │ BOOLEAN       │ false                            │ true                             │             2 │ NULL                │ NULL               │ NULL    │ NULL    │ NULL    │ 100245 │            0.77 │
└────────────────────┴───────────────┴──────────────────────────────────┴──────────────────────────────────┴───────────────┴─────────────────────┴────────────────────┴─────────┴─────────┴─────────┴────────┴─────────────────┘
  11 rows                                                                                                                                                                                                           12 columns

We have 0.77 null percentage for (category_name, item_count, payment_total, review_score, is_single_category) and 2.96 null percentage (delivery_days, delay_days, is_late).

con.sql(f'''
SELECT order_status, COUNT(*) AS null_count
FROM category
LEFT JOIN stg_order_items AS order_items ON category.order_id = order_items.order_id
WHERE is_single_category IS NULL
GROUP BY order_status
'''
)
┌──────────────┬────────────┐
│ order_status │ null_count │
│   varchar    │   int64    │
├──────────────┼────────────┤
│ shipped      │          1 │
│ invoiced     │          2 │
│ created      │          5 │
│ unavailable  │        603 │
│ canceled     │        164 │
└──────────────┴────────────┘

There are no nulls in delivered orders. These nulls are caused by missing product_id. Hence, we will left it as is since we do not have anything to fill it with.

Deliveries

It seems that most of the data required are already in the categories table. However, we will be adding a couple more columns.

# order_id | category_name | order_status | item_count | payment_total | delivery_days | is_late | delay_days | review_score | is_single_category
categories_sql = f'''
WITH order_summary AS (
    SELECT 
        order_id,
        product_id,
        COUNT(*) AS item_count,
        SUM(price) AS price,
        SUM(freight_value) AS freight_value
    FROM stg_order_items AS order_items
    GROUP BY order_id, product_id
),
category_table AS (
    SELECT products.product_id, category_name
    FROM stg_products AS products
),
order_categorized AS (
    SELECT 
        order_id,
        COALESCE(category_name, 'Uncategorized') AS category_name,
        SUM(item_count) AS item_count, 
        SUM(price) AS price,
        SUM(freight_value) AS freight_value
    FROM order_summary
    LEFT JOIN category_table ON order_summary.product_id = category_table.product_id
    GROUP BY order_id, category_name
),
single_category AS (
    SELECT order_id, 
        CASE
            WHEN ARRAY_LENGTH(ARRAY_AGG(DISTINCT category_name)) > 1 THEN FALSE
            ELSE TRUE
        END AS is_single_category
    FROM order_categorized
    GROUP BY order_id
),
reviews AS (
    SELECT order_id, review_score
    FROM stg_order_reviews
    QUALIFY ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY review_answer DESC) = 1
)

SELECT 
    orders.order_id,
    category_name, 
    order_status,
    item_count,
    price,
    freight_value,
    COALESCE(order_delivered_carrier::date - order_purchase::date, NULL)  AS fulfilment_days,
    COALESCE(order_delivered_customer::date - order_delivered_carrier::date, NULL)  AS delivery_days,
    order_delivered_customer::date - order_estimated_delivery::date AS delay_days,
    fulfilment_days + delivery_days AS order_completion_days,
    order_completion_days - delay_days AS estimated_completion_days,
    CASE
        WHEN delay_days > 0 THEN TRUE
        WHEN delay_days <= 0 THEN FALSE
        ELSE NULL
    END AS is_late,
    review_score,
    is_single_category
FROM stg_orders AS orders
LEFT JOIN order_categorized ON orders.order_id = order_categorized.order_id
LEFT JOIN single_category ON orders.order_id = single_category.order_id
LEFT JOIN reviews AS reviews ON orders.order_id = reviews.order_id
'''

con.sql(f"CREATE OR REPLACE TABLE categories_deliveries AS {categories_sql}")
con.sql(f"SELECT * FROM categories_deliveries")
┌──────────────────────────────────┬──────────────────────────┬──────────────┬────────────┬───────────────┬───────────────┬─────────────────┬───────────────┬────────────┬───────────────────────┬───────────────────────────┬─────────┬──────────────┬────────────────────┐
│             order_id             │      category_name       │ order_status │ item_count │     price     │ freight_value │ fulfilment_days │ delivery_days │ delay_days │ order_completion_days │ estimated_completion_days │ is_late │ review_score │ is_single_category │
│             varchar              │         varchar          │   varchar    │   int128   │ decimal(38,2) │ decimal(38,2) │      int64      │     int64     │   int64    │         int64         │           int64           │ boolean │    int16     │      boolean       │
├──────────────────────────────────┼──────────────────────────┼──────────────┼────────────┼───────────────┼───────────────┼─────────────────┼───────────────┼────────────┼───────────────────────┼───────────────────────────┼─────────┼──────────────┼────────────────────┤
│ 26a44b28ac942b09a771479c880882d9 │ garden_tools             │ delivered    │          1 │         59.90 │         17.67 │               3 │             7 │        -20 │                    10 │                        30 │ false   │            5 │ true               │
│ 284039cca9a61886953286f5dae55fdf │ cool_stuff               │ delivered    │          1 │        199.00 │         18.00 │               1 │             8 │        -26 │                     9 │                        35 │ false   │            5 │ true               │
│ 29b71dc3837a954158a1d1b30d0605bc │ bed_bath_table           │ delivered    │          1 │        203.00 │         25.03 │               2 │             7 │        -10 │                     9 │                        19 │ false   │            1 │ true               │
│ 2b2e646f2556658e3d06c53ee0465153 │ furniture_living_room    │ delivered    │          1 │        116.94 │          9.58 │               7 │             5 │        -15 │                    12 │                        27 │ false   │            2 │ false              │
│ 2c45c33d2f9cb8ff8b1c86cc28c11c30 │ fashion_bags_accessories │ canceled     │          1 │        135.00 │         18.51 │               5 │            26 │        -29 │                    31 │                        60 │ false   │            1 │ true               │
│ 30647325eed2f0ac046e27a3cb6b76fc │ market_place             │ delivered    │          1 │        339.74 │         41.01 │               1 │            21 │        -17 │                    22 │                        39 │ false   │            5 │ true               │
│ 3204d768f424c527945af498dfdc005f │ health_beauty            │ delivered    │          1 │        122.99 │         10.17 │               1 │             4 │        -18 │                     5 │                        23 │ false   │            4 │ true               │
│ 3227045c7007663566ae3d19aed8cca7 │ home_appliances          │ delivered    │          1 │         97.00 │         13.78 │               4 │             2 │         -3 │                     6 │                         9 │ false   │            5 │ true               │
│ 36df29e4671cdc14371883d209fed7dc │ telephony                │ delivered    │          1 │         17.00 │          8.29 │               1 │             3 │         -9 │                     4 │                        13 │ false   │            4 │ true               │
│ 383e1abcae2216bb7149870157ac31a9 │ musical_instruments      │ delivered    │          1 │         39.90 │         26.89 │               4 │            14 │        -17 │                    18 │                        35 │ false   │            4 │ true               │
│                ·                 │       ·                  │     ·        │          · │           ·   │           ·   │               · │             · │         ·  │                     · │                         · │   ·     │            · │  ·                 │
│                ·                 │       ·                  │     ·        │          · │           ·   │           ·   │               · │             · │         ·  │                     · │                         · │   ·     │            · │  ·                 │
│                ·                 │       ·                  │     ·        │          · │           ·   │           ·   │               · │             · │         ·  │                     · │                         · │   ·     │            · │  ·                 │
│ 3ab4eea6776259ae8dfbabaeffece352 │ health_beauty            │ delivered    │          1 │        370.00 │         17.80 │               2 │             6 │        -43 │                     8 │                        51 │ false   │            4 │ true               │
│ 45b3000bcd10464ac178f32cd783fc83 │ health_beauty            │ delivered    │          1 │        250.00 │         54.11 │               4 │            25 │        -17 │                    29 │                        46 │ false   │            4 │ true               │
│ 4a44bde077699b5d6718fd30e34c0b90 │ auto                     │ delivered    │          1 │        199.90 │         21.89 │              21 │            13 │        -10 │                    34 │                        44 │ false   │            2 │ true               │
│ 4d60e5659a5be3b15be731b5882ecc3e │ furniture_decor          │ delivered    │          1 │         75.00 │          8.82 │               6 │             1 │        -19 │                     7 │                        26 │ false   │            3 │ true               │
│ 521061459524ada27c911dfdc0059314 │ cool_stuff               │ delivered    │          1 │        265.00 │         24.57 │               2 │             2 │         -5 │                     4 │                         9 │ false   │            5 │ true               │
│ 5253f3630960eb737c90b4ea7cf42614 │ sports_leisure           │ delivered    │          1 │         53.90 │          8.72 │               7 │             8 │         -1 │                    15 │                        16 │ false   │            5 │ true               │
│ 52f0252ece9cc6fa57f20b1f309ba1e2 │ housewares               │ delivered    │          1 │         72.00 │         16.75 │               3 │             6 │        -23 │                     9 │                        32 │ false   │            5 │ true               │
│ 56c52cf14c501d31e4d208bf86cecb53 │ auto                     │ delivered    │          2 │        399.80 │         43.78 │               6 │             9 │        -25 │                    15 │                        40 │ false   │            1 │ true               │
│ 570eccb41678dc30af2792b28b86bb45 │ health_beauty            │ delivered    │          1 │        370.00 │         28.85 │               2 │            15 │        -19 │                    17 │                        36 │ false   │            5 │ true               │
│ 58a0927d5f0c99b74be566b3d5ed7c59 │ garden_tools             │ delivered    │          1 │        179.99 │         45.72 │               3 │            12 │        -16 │                    15 │                        31 │ false   │            5 │ true               │
└──────────────────────────────────┴──────────────────────────┴──────────────┴────────────┴───────────────┴───────────────┴─────────────────┴───────────────┴────────────┴───────────────────────┴───────────────────────────┴─────────┴──────────────┴────────────────────┘
  ? rows (>9999 rows, 20 shown)                                                                                                                                                                                                                                 14 columns
con.execute(open(f"{cwd}/../sql/01_staging.sql").read())
con.execute(open(f"{cwd}/../sql/02_marts.sql").read())
<_duckdb.DuckDBPyConnection at 0x10bf415f0>

Validations

In order to know that to validate, we will reiterate what are the tables granularity.

  1. Overview table: (order_id, product_id)

  2. Categories and Deliveries table: (order_id, category_name)

Hence, the tests should be

  • Are all order_id from staging included? (For both) (We know that this is not true, but include it in the check) This tells us whether we missed orders we shouldn’t have.

  • Are the granularities correct? Checks whether the granularities for each table is correct and assert that all of the other columns are grouped correctly.

  • Does the payment_total and item_count matched with staging? (For both) This tells us whether there are any irregularities from the counting and total.

  • Does deliveries statistics and reviews stays constant for each orders? (For categories_deliveries)

con.sql(
    f'''
    WITH grain_overview AS (
        SELECT order_id, product_id 
        FROM overview
        GROUP BY order_id, product_id 
        HAVING COUNT(*) > 1
    ),
    grain_categories AS (
        SELECT order_id, category_name 
        FROM categories_deliveries
        GROUP BY order_id, category_name 
        HAVING COUNT(*) > 1
    ),
    missing_orders AS (
        SELECT order_id 
        FROM stg_orders
        WHERE order_id IN (SELECT order_id FROM stg_order_items)
        AND order_id NOT IN (SELECT order_id FROM overview)
    ),
    revenue_count_overview AS (
        SELECT COALESCE(SUM(price + freight_value) = (SELECT SUM(price + freight_value) FROM stg_order_items), FALSE) AND
        COALESCE(SUM(item_count) = (SELECT COUNT(*) FROM stg_order_items), FALSE) AS pass_check
        FROM overview
    ),
    revenue_count_categories AS (
        SELECT COALESCE(SUM(price + freight_value) = (SELECT SUM(price + freight_value) FROM stg_order_items), FALSE) AND
        COALESCE(SUM(item_count) = (SELECT COUNT(*) FROM stg_order_items), FALSE) AS pass_check
        FROM categories_deliveries
    ),
    deliveries_statistics AS (
        SELECT order_id 
        FROM categories_deliveries 
        GROUP BY order_id
        HAVING COUNT(DISTINCT review_score) > 1
            OR COUNT(DISTINCT delay_days) > 1
            OR COUNT(DISTINCT order_status) > 1
            OR COUNT(DISTINCT is_single_category) > 1
    )

        SELECT
            'grain_overview' AS check_name,
            COUNT(order_id) = 0 AS pass_check
        FROM grain_overview
        UNION ALL
        SELECT
            'grain_categories' AS check_name,
            COUNT(order_id) = 0 AS pass_check
        FROM grain_categories
        UNION ALL
        SELECT 
            'unexplained_missing_orders' AS check_name,
            COUNT(*) = 0 AS pass_check
        FROM missing_orders
        UNION ALL
        SELECT 
            'revenue_count_overview' AS check_name,
            pass_check
        FROM revenue_count_overview
        UNION ALL
        SELECT 
            'revenue_count_categories' AS check_name,
            pass_check
        FROM revenue_count_categories
        UNION ALL
        SELECT
            'deliveries_statistics_check' AS check_name,
            COUNT(*) = 0 AS pass_check
        FROM deliveries_statistics
    '''
)
┌─────────────────────────────┬────────────┐
│         check_name          │ pass_check │
│           varchar           │  boolean   │
├─────────────────────────────┼────────────┤
│ grain_overview              │ true       │
│ grain_categories            │ true       │
│ unexplained_missing_orders  │ true       │
│ revenue_count_overview      │ true       │
│ revenue_count_categories    │ true       │
│ deliveries_statistics_check │ true       │
└─────────────────────────────┴────────────┘
con.execute(open(f"{cwd}/../sql/03_checks.sql").read())
<_duckdb.DuckDBPyConnection at 0x10bf415f0>

Export

Normally, this part wouldn’t exist. But, since the project is local, we will be exporting into a new csv so tableau can ingest the data.

con.sql(f'''
SELECT CASE 
    WHEN COUNT(*) > 0 THEN error('The table did not pass validation checks') 
    END
FROM (
    SELECT check_name
    FROM check_results
    WHERE pass_check IS FALSE
);

COPY 
overview 
TO '../exports/overview.csv'
DELIMITER ','
CSV HEADER;

COPY 
categories_deliveries 
TO '../exports/categories_deliveries.csv'
DELIMITER ','
CSV HEADER;
''')
con.execute(open(f"{cwd}/../sql/04_export.sql").read())
<_duckdb.DuckDBPyConnection at 0x10bf415f0>