diff --git a/003_ecommerce/audits/.gitkeep b/003_ecommerce/audits/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/003_ecommerce/audits/assert_positive_order_ids.sql b/003_ecommerce/audits/assert_positive_order_ids.sql new file mode 100644 index 0000000..99f6ae1 --- /dev/null +++ b/003_ecommerce/audits/assert_positive_order_ids.sql @@ -0,0 +1,9 @@ +AUDIT ( + name assert_positive_order_ids +); + +SELECT + * +FROM @this_model +WHERE + item_id < 0 \ No newline at end of file diff --git a/003_ecommerce/config.yaml b/003_ecommerce/config.yaml new file mode 100644 index 0000000..9957cb9 --- /dev/null +++ b/003_ecommerce/config.yaml @@ -0,0 +1,11 @@ +gateways: + local: + connection: + type: duckdb + database: db.db + +default_gateway: local + +model_defaults: + dialect: duckdb + start: 2024-12-15 diff --git a/003_ecommerce/macros/.gitkeep b/003_ecommerce/macros/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/003_ecommerce/macros/__init__.py b/003_ecommerce/macros/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/003_ecommerce/models/.gitkeep b/003_ecommerce/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/003_ecommerce/models/bronze/raw_customer_addresses.sql b/003_ecommerce/models/bronze/raw_customer_addresses.sql new file mode 100644 index 0000000..5904965 --- /dev/null +++ b/003_ecommerce/models/bronze/raw_customer_addresses.sql @@ -0,0 +1,42 @@ +MODEL ( + name bronze.raw_customer_addresses, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [address_id], + tags ['bronze'], + columns ( + address_id INT, + customer_id INT, + address_type TEXT, + street_address TEXT, + city TEXT, + state TEXT, + postal_code TEXT, + country TEXT, + is_default BOOLEAN, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_customer_addresses] +); + +SELECT + id AS address_id, + customer_id, + address_type, + street_address, + city, + state, + postal_code, + country, + is_default, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_customer_addresses +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_customers.sql b/003_ecommerce/models/bronze/raw_customers.sql new file mode 100644 index 0000000..b8d7513 --- /dev/null +++ b/003_ecommerce/models/bronze/raw_customers.sql @@ -0,0 +1,34 @@ +MODEL ( + name bronze.raw_customers, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [customer_id], + tags ['bronze'], + columns ( + customer_id INT, + email TEXT, + first_name TEXT, + last_name TEXT, + phone_number TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_customers] +); + +SELECT + id AS customer_id, + email, + first_name, + last_name, + phone_number, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_customers +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_order_items.sql b/003_ecommerce/models/bronze/raw_order_items.sql new file mode 100644 index 0000000..1e7b990 --- /dev/null +++ b/003_ecommerce/models/bronze/raw_order_items.sql @@ -0,0 +1,32 @@ +MODEL ( + name bronze.raw_order_items, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column order_timestamp + ), + grain [order_item_id], + tags ['bronze'], + columns ( + order_item_id INT, + order_id INT, + product_id INT, + quantity INT, + unit_price DECIMAL(10, 2), + order_timestamp TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_order_items] +); + +SELECT + id AS order_item_id, + order_id, + product_id, + quantity, + unit_price, + created_at AS order_timestamp, + _loaded_at, + _file_name +FROM source_ecommerce.raw_order_items +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_orders.sql b/003_ecommerce/models/bronze/raw_orders.sql new file mode 100644 index 0000000..a70131d --- /dev/null +++ b/003_ecommerce/models/bronze/raw_orders.sql @@ -0,0 +1,30 @@ +MODEL ( + name bronze.raw_orders, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column order_timestamp + ), + grain [order_id], + tags ['bronze'], + columns ( + order_id INT, + user_id INT, + total_amount DECIMAL(10, 2), + status TEXT, + order_timestamp TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_orders] +); + +SELECT + id AS order_id, + user_id, + total_amount, + status, + created_at AS order_timestamp, + _loaded_at, + _file_name +FROM source_ecommerce.raw_orders +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_product_categories.sql b/003_ecommerce/models/bronze/raw_product_categories.sql new file mode 100644 index 0000000..36f4704 --- /dev/null +++ b/003_ecommerce/models/bronze/raw_product_categories.sql @@ -0,0 +1,32 @@ +MODEL ( + name bronze.raw_product_categories, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [category_id], + tags ['bronze'], + columns ( + category_id INT, + category_name TEXT, + parent_category_id INT, + description TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_product_categories] +); + +SELECT + id AS category_id, + name AS category_name, + parent_category_id, + description, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_product_categories +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_products.sql b/003_ecommerce/models/bronze/raw_products.sql new file mode 100644 index 0000000..34d58e2 --- /dev/null +++ b/003_ecommerce/models/bronze/raw_products.sql @@ -0,0 +1,40 @@ +MODEL ( + name bronze.raw_products, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [product_id], + tags ['bronze'], + columns ( + product_id INT, + sku TEXT, + product_name TEXT, + description TEXT, + category_id INT, + supplier_id INT, + unit_price DECIMAL(10, 2), + stock_quantity INT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_products] +); + +SELECT + id AS product_id, + sku, + name AS product_name, + description, + category_id, + supplier_id, + unit_price, + stock_quantity, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_products +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_shipments.sql b/003_ecommerce/models/bronze/raw_shipments.sql new file mode 100644 index 0000000..f91c02c --- /dev/null +++ b/003_ecommerce/models/bronze/raw_shipments.sql @@ -0,0 +1,36 @@ +MODEL ( + name bronze.raw_shipments, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [shipment_id], + tags ['bronze'], + columns ( + shipment_id INT, + order_id INT, + tracking_number TEXT, + status TEXT, + estimated_delivery_date DATE, + actual_delivery_date DATE, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_shipments] +); + +SELECT + id AS shipment_id, + order_id, + tracking_number, + status, + estimated_delivery_date, + actual_delivery_date, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_shipments +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/bronze/raw_suppliers.sql b/003_ecommerce/models/bronze/raw_suppliers.sql new file mode 100644 index 0000000..de938ca --- /dev/null +++ b/003_ecommerce/models/bronze/raw_suppliers.sql @@ -0,0 +1,44 @@ +MODEL ( + name bronze.raw_suppliers, + kind INCREMENTAL_BY_TIME_RANGE ( + time_column updated_at + ), + grain [supplier_id], + tags ['bronze'], + columns ( + supplier_id INT, + company_name TEXT, + contact_name TEXT, + contact_email TEXT, + contact_phone TEXT, + address TEXT, + city TEXT, + state TEXT, + postal_code TEXT, + country TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [source_ecommerce.raw_suppliers] +); + +SELECT + id AS supplier_id, + company_name, + contact_name, + contact_email, + contact_phone, + address, + city, + state, + postal_code, + country, + created_at, + updated_at, + _loaded_at, + _file_name +FROM source_ecommerce.raw_suppliers +WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date \ No newline at end of file diff --git a/003_ecommerce/models/gold/customer_metrics.sql b/003_ecommerce/models/gold/customer_metrics.sql new file mode 100644 index 0000000..385e39e --- /dev/null +++ b/003_ecommerce/models/gold/customer_metrics.sql @@ -0,0 +1,33 @@ +MODEL ( + name gold.customer_metrics, + kind FULL, + grain [customer_id], + tags ['gold'], + references [silver.customers, silver.orders, bronze.raw_customer_addresses] +); + +SELECT + c.customer_id, + c.full_name, + c.email, + COUNT(DISTINCT o.order_id) AS total_orders, + SUM(o.total_amount) AS total_spend, + AVG(o.total_amount) AS average_order_value, + MIN(o.order_timestamp) AS first_order_date, + MAX(o.order_timestamp) AS last_order_date, + DATE_DIFF('DAY', MIN(o.order_timestamp), MAX(o.order_timestamp)) AS customer_lifetime_days, + ca.city, + ca.state, + ca.country +FROM silver.customers AS c +left join bronze.raw_customer_addresses as ca + on c.customer_id = ca.customer_id +LEFT JOIN silver.orders AS o + ON c.customer_id = o.user_id +GROUP BY + 1, + 2, + 3, + 10, + 11, + 12 \ No newline at end of file diff --git a/003_ecommerce/models/gold/daily_sales_metrics.sql b/003_ecommerce/models/gold/daily_sales_metrics.sql new file mode 100644 index 0000000..bef9ce9 --- /dev/null +++ b/003_ecommerce/models/gold/daily_sales_metrics.sql @@ -0,0 +1,42 @@ +MODEL ( + name gold.daily_sales_metrics, + kind FULL, + grain [date], + tags ['gold'], + references [silver.orders, silver.order_items] +); + +WITH daily_orders AS ( + SELECT + order_timestamp::DATE AS date, + COUNT(DISTINCT order_id) AS total_orders, + COUNT(DISTINCT user_id) AS unique_customers, + SUM(total_amount) AS total_revenue, + SUM(CASE WHEN is_completed THEN 1 ELSE 0 END) AS completed_orders + FROM silver.orders + GROUP BY + 1 +), daily_items AS ( + SELECT + order_timestamp::DATE AS date, + COUNT(DISTINCT product_id) AS unique_products_sold, + SUM(quantity) AS total_items_sold, + AVG(unit_price) AS avg_unit_price + FROM silver.order_items + GROUP BY + 1 +) +SELECT + o.date, + o.total_orders, + o.unique_customers, + o.total_revenue, + o.completed_orders, + i.unique_products_sold, + i.total_items_sold, + i.avg_unit_price, + o.total_revenue / NULLIF(o.total_orders, 0) AS avg_order_value, + o.completed_orders::REAL / NULLIF(o.total_orders, 0) AS order_completion_rate +FROM daily_orders AS o +JOIN daily_items AS i + ON o.date = i.date \ No newline at end of file diff --git a/003_ecommerce/models/gold/product_performance_metrics.sql b/003_ecommerce/models/gold/product_performance_metrics.sql new file mode 100644 index 0000000..6696a2a --- /dev/null +++ b/003_ecommerce/models/gold/product_performance_metrics.sql @@ -0,0 +1,34 @@ +MODEL ( + name gold.product_performance_metrics, + kind FULL, + grain [product_id], + tags ['gold'], + references [silver.products, silver.order_items, silver.product_categories] +); + +SELECT + p.product_id, + p.product_name, + c.category_name, + COUNT(DISTINCT oi.order_id) AS number_of_orders, + SUM(oi.quantity) AS units_sold, + SUM(oi.total_item_amount) AS total_revenue, + AVG(oi.unit_price) AS average_selling_price, + p.stock_quantity AS current_stock_level, + CASE + WHEN p.stock_quantity = 0 + THEN 'Out of Stock' + WHEN p.stock_quantity < 10 + THEN 'Low Stock' + ELSE 'In Stock' + END AS inventory_status +FROM silver.products AS p +LEFT JOIN silver.order_items AS oi + ON p.product_id = oi.product_id +LEFT JOIN silver.product_categories AS c + ON p.category_id = c.category_id +GROUP BY + 1, + 2, + 3, + 8 \ No newline at end of file diff --git a/003_ecommerce/models/gold/supplier_performance_metrics.sql b/003_ecommerce/models/gold/supplier_performance_metrics.sql new file mode 100644 index 0000000..7cc216e --- /dev/null +++ b/003_ecommerce/models/gold/supplier_performance_metrics.sql @@ -0,0 +1,28 @@ +MODEL ( + name gold.supplier_performance_metrics, + kind FULL, + grain [supplier_id], + tags ['gold'], + references [silver.suppliers, silver.products, silver.order_items] +); + +SELECT + s.supplier_id, + s.company_name, + COUNT(DISTINCT p.product_id) AS number_of_products, + SUM(p.stock_quantity) AS total_stock_quantity, + COUNT(DISTINCT CASE WHEN p.stock_quantity = 0 THEN p.product_id END) AS out_of_stock_products, + COUNT(DISTINCT oi.order_id) AS number_of_orders, + SUM(oi.quantity) AS total_units_sold, + SUM(oi.total_item_amount) AS total_revenue, + s.country AS supplier_country, + COUNT(DISTINCT p.category_id) AS number_of_categories_supplied +FROM silver.suppliers AS s +LEFT JOIN silver.products AS p + ON s.supplier_id = p.supplier_id +LEFT JOIN silver.order_items AS oi + ON p.product_id = oi.product_id +GROUP BY + 1, + 2, + 9 \ No newline at end of file diff --git a/003_ecommerce/models/raw_customer_addresses.sql b/003_ecommerce/models/raw_customer_addresses.sql new file mode 100644 index 0000000..ca240a1 --- /dev/null +++ b/003_ecommerce/models/raw_customer_addresses.sql @@ -0,0 +1,21 @@ +MODEL ( + name source_ecommerce.raw_customer_addresses, + kind SEED ( + path '../seeds/source_customer_addresses.csv' + ), + columns ( + id INTEGER, + customer_id INTEGER, + address_type TEXT, + street_address TEXT, + city TEXT, + state TEXT, + postal_code TEXT, + country TEXT, + is_default BOOLEAN, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_customers.sql b/003_ecommerce/models/raw_customers.sql new file mode 100644 index 0000000..73548b3 --- /dev/null +++ b/003_ecommerce/models/raw_customers.sql @@ -0,0 +1,17 @@ +MODEL ( + name source_ecommerce.raw_customers, + kind SEED ( + path '../seeds/source_customers.csv' + ), + columns ( + id INTEGER, + email TEXT, + first_name TEXT, + last_name TEXT, + phone_number TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_order_items.sql b/003_ecommerce/models/raw_order_items.sql new file mode 100644 index 0000000..3a2a415 --- /dev/null +++ b/003_ecommerce/models/raw_order_items.sql @@ -0,0 +1,16 @@ +MODEL ( + name source_ecommerce.raw_order_items, + kind SEED ( + path '../seeds/source_order_items.csv' + ), + columns ( + id INTEGER, + order_id INTEGER, + product_id INTEGER, + quantity INTEGER, + unit_price DECIMAL(10,2), + created_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_orders.sql b/003_ecommerce/models/raw_orders.sql new file mode 100644 index 0000000..adadb47 --- /dev/null +++ b/003_ecommerce/models/raw_orders.sql @@ -0,0 +1,15 @@ +MODEL ( + name source_ecommerce.raw_orders, + kind SEED ( + path '../seeds/source_orders.csv' + ), + columns ( + id INTEGER, + user_id INTEGER, + total_amount DECIMAL(10,2), + status TEXT, + created_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_product_categories.sql b/003_ecommerce/models/raw_product_categories.sql new file mode 100644 index 0000000..c8eaddb --- /dev/null +++ b/003_ecommerce/models/raw_product_categories.sql @@ -0,0 +1,16 @@ +MODEL ( + name source_ecommerce.raw_product_categories, + kind SEED ( + path '../seeds/source_product_categories.csv' + ), + columns ( + id INTEGER, + name TEXT, + parent_category_id INTEGER, + description TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_products.sql b/003_ecommerce/models/raw_products.sql new file mode 100644 index 0000000..78adaba --- /dev/null +++ b/003_ecommerce/models/raw_products.sql @@ -0,0 +1,20 @@ +MODEL ( + name source_ecommerce.raw_products, + kind SEED ( + path '../seeds/source_products.csv' + ), + columns ( + id INTEGER, + sku TEXT, + name TEXT, + description TEXT, + category_id INTEGER, + supplier_id INTEGER, + unit_price DECIMAL(10,2), + stock_quantity INTEGER, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_shipments.sql b/003_ecommerce/models/raw_shipments.sql new file mode 100644 index 0000000..bad83d5 --- /dev/null +++ b/003_ecommerce/models/raw_shipments.sql @@ -0,0 +1,18 @@ +MODEL ( + name source_ecommerce.raw_shipments, + kind SEED ( + path '../seeds/source_shipments.csv' + ), + columns ( + id INTEGER, + order_id INTEGER, + tracking_number TEXT, + status TEXT, + estimated_delivery_date DATE, + actual_delivery_date DATE, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/raw_suppliers.sql b/003_ecommerce/models/raw_suppliers.sql new file mode 100644 index 0000000..a09a405 --- /dev/null +++ b/003_ecommerce/models/raw_suppliers.sql @@ -0,0 +1,22 @@ +MODEL ( + name source_ecommerce.raw_suppliers, + kind SEED ( + path '../seeds/source_suppliers.csv' + ), + columns ( + id INTEGER, + company_name TEXT, + contact_name TEXT, + contact_email TEXT, + contact_phone TEXT, + address TEXT, + city TEXT, + state TEXT, + postal_code TEXT, + country TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ) +); diff --git a/003_ecommerce/models/silver/customers.sql b/003_ecommerce/models/silver/customers.sql new file mode 100644 index 0000000..69425bc --- /dev/null +++ b/003_ecommerce/models/silver/customers.sql @@ -0,0 +1,33 @@ +MODEL ( + name silver.customers, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [customer_id] + ), + tags ['silver'], + columns ( + customer_id INT, + email TEXT, + first_name TEXT, + last_name TEXT, + full_name TEXT, + phone_number TEXT, + created_at TIMESTAMP, + updated_at TIMESTAMP, + _loaded_at TIMESTAMP, + _file_name TEXT + ), + references [bronze.raw_customers] +); + +SELECT + customer_id, + email, + first_name, + last_name, + first_name || ' ' || last_name AS full_name, + phone_number, + created_at, + updated_at, + _loaded_at, + _file_name +FROM bronze.raw_customers \ No newline at end of file diff --git a/003_ecommerce/models/silver/order_items.sql b/003_ecommerce/models/silver/order_items.sql new file mode 100644 index 0000000..78091b2 --- /dev/null +++ b/003_ecommerce/models/silver/order_items.sql @@ -0,0 +1,28 @@ +MODEL ( + name silver.order_items, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [order_item_id] + ), + tags ['silver'], + references [bronze.raw_order_items] +); + +WITH latest_order_items AS ( + SELECT + * + FROM bronze.raw_order_items + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY order_item_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + order_item_id, + order_id, + product_id, + quantity, + unit_price, + quantity * unit_price AS total_item_amount, + order_timestamp, + _loaded_at +FROM latest_order_items \ No newline at end of file diff --git a/003_ecommerce/models/silver/orders.sql b/003_ecommerce/models/silver/orders.sql new file mode 100644 index 0000000..23f697b --- /dev/null +++ b/003_ecommerce/models/silver/orders.sql @@ -0,0 +1,27 @@ +MODEL ( + name silver.orders, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [order_id] + ), + tags ['silver'], + references [bronze.raw_orders] +); + +WITH latest_orders AS ( + SELECT + * + FROM bronze.raw_orders + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + order_id, + user_id, + total_amount, + status, + order_timestamp, + CASE WHEN status = 'completed' THEN TRUE ELSE FALSE END AS is_completed, + _loaded_at +FROM latest_orders \ No newline at end of file diff --git a/003_ecommerce/models/silver/product_categories.sql b/003_ecommerce/models/silver/product_categories.sql new file mode 100644 index 0000000..2b96872 --- /dev/null +++ b/003_ecommerce/models/silver/product_categories.sql @@ -0,0 +1,27 @@ +MODEL ( + name silver.product_categories, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [category_id] + ), + tags ['silver'], + references [bronze.raw_product_categories] +); + +WITH latest_categories AS ( + SELECT + * + FROM bronze.raw_product_categories + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + category_id, + category_name, + description, + parent_category_id, + created_at, + updated_at, + _loaded_at +FROM latest_categories \ No newline at end of file diff --git a/003_ecommerce/models/silver/products.sql b/003_ecommerce/models/silver/products.sql new file mode 100644 index 0000000..a5e5e4b --- /dev/null +++ b/003_ecommerce/models/silver/products.sql @@ -0,0 +1,32 @@ +MODEL ( + name silver.products, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [product_id] + ), + tags ['silver'], + references [bronze.raw_products] +); + +WITH latest_products AS ( + SELECT + * + FROM bronze.raw_products + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + product_id, + sku, + product_name, + description, + category_id, + supplier_id, + unit_price, + stock_quantity, + CASE WHEN stock_quantity > 0 THEN TRUE ELSE FALSE END AS is_in_stock, + created_at, + updated_at, + _loaded_at +FROM latest_products \ No newline at end of file diff --git a/003_ecommerce/models/silver/shipments.sql b/003_ecommerce/models/silver/shipments.sql new file mode 100644 index 0000000..86de90e --- /dev/null +++ b/003_ecommerce/models/silver/shipments.sql @@ -0,0 +1,37 @@ +MODEL ( + name silver.shipments, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [shipment_id] + ), + tags ['silver'], + grain [shipment_id], + references [bronze.raw_shipments] +); + +WITH latest_shipments AS ( + SELECT + * + FROM bronze.raw_shipments + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY shipment_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + shipment_id, + order_id, + tracking_number, + status, + estimated_delivery_date, + actual_delivery_date, + DATE_DIFF('DAY', created_at, COALESCE(actual_delivery_date, CURRENT_TIMESTAMP)) AS delivery_days, + CASE + WHEN NOT actual_delivery_date IS NULL + AND actual_delivery_date <= estimated_delivery_date + THEN TRUE + ELSE FALSE + END AS is_on_time, + created_at, + updated_at, + _loaded_at +FROM latest_shipments \ No newline at end of file diff --git a/003_ecommerce/models/silver/suppliers.sql b/003_ecommerce/models/silver/suppliers.sql new file mode 100644 index 0000000..ce9af11 --- /dev/null +++ b/003_ecommerce/models/silver/suppliers.sql @@ -0,0 +1,33 @@ +MODEL ( + name silver.suppliers, + kind INCREMENTAL_BY_UNIQUE_KEY ( + unique_key [supplier_id] + ), + tags ['silver'], + references [bronze.raw_suppliers] +); + +WITH latest_suppliers AS ( + SELECT + * + FROM bronze.raw_suppliers + WHERE + _loaded_at >= @start_date AND _loaded_at < @end_date + QUALIFY + ROW_NUMBER() OVER (PARTITION BY supplier_id ORDER BY _loaded_at DESC) = 1 +) +SELECT + supplier_id, + company_name, + contact_name, + contact_email, + contact_phone, + address, + city, + state, + postal_code, + country, + created_at, + updated_at, + _loaded_at +FROM latest_suppliers \ No newline at end of file diff --git a/003_ecommerce/seeds/.gitkeep b/003_ecommerce/seeds/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/003_ecommerce/seeds/seed_data.csv b/003_ecommerce/seeds/seed_data.csv new file mode 100644 index 0000000..ef6d930 --- /dev/null +++ b/003_ecommerce/seeds/seed_data.csv @@ -0,0 +1,8 @@ +id,item_id,ds +1,2,2020-01-01 +2,1,2020-01-01 +3,3,2020-01-03 +4,1,2020-01-04 +5,1,2020-01-05 +6,1,2020-01-06 +7,1,2020-01-07 diff --git a/003_ecommerce/seeds/source_customer_addresses.csv b/003_ecommerce/seeds/source_customer_addresses.csv new file mode 100644 index 0000000..88edabe --- /dev/null +++ b/003_ecommerce/seeds/source_customer_addresses.csv @@ -0,0 +1,6 @@ +id,customer_id,address_type,street_address,city,state,postal_code,country,is_default,created_at,updated_at,_loaded_at,_file_name +1,1,home,123 Main St,San Francisco,CA,94105,USA,true,2024-01-01 10:00:00,2024-01-01 10:00:00,2024-01-01 10:30:00,addresses_batch_1.csv +2,1,work,456 Market St,San Francisco,CA,94105,USA,false,2024-01-01 10:00:00,2024-01-01 10:00:00,2024-01-01 10:30:00,addresses_batch_1.csv +3,2,home,789 Oak Ave,San Jose,CA,95110,USA,true,2024-01-02 11:00:00,2024-01-02 11:00:00,2024-01-02 11:30:00,addresses_batch_2.csv +4,3,home,321 Pine St,Oakland,CA,94601,USA,true,2024-01-02 12:00:00,2024-01-02 12:00:00,2024-01-02 12:30:00,addresses_batch_2.csv +5,4,home,654 Elm St,Berkeley,CA,94701,USA,true,2024-01-03 09:00:00,2024-01-03 09:00:00,2024-01-03 09:30:00,addresses_batch_3.csv diff --git a/003_ecommerce/seeds/source_customers.csv b/003_ecommerce/seeds/source_customers.csv new file mode 100644 index 0000000..cc91a66 --- /dev/null +++ b/003_ecommerce/seeds/source_customers.csv @@ -0,0 +1,6 @@ +id,email,first_name,last_name,phone_number,created_at,updated_at,_loaded_at,_file_name +1,john.doe@email.com,John,Doe,+1-555-0101,2024-01-01 10:00:00,2024-01-01 10:00:00,2024-01-01 10:30:00,customers_batch_1.csv +2,jane.smith@email.com,Jane,Smith,+1-555-0102,2024-01-01 11:00:00,2024-01-01 11:00:00,2024-01-01 11:30:00,customers_batch_1.csv +3,bob.wilson@email.com,Bob,Wilson,+1-555-0103,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,customers_batch_2.csv +4,alice.brown@email.com,Alice,Brown,+1-555-0104,2024-01-02 10:00:00,2024-01-02 10:00:00,2024-01-02 10:30:00,customers_batch_2.csv +5,charlie.davis@email.com,Charlie,Davis,+1-555-0105,2024-01-03 09:00:00,2024-01-03 09:00:00,2024-01-03 09:30:00,customers_batch_3.csv diff --git a/003_ecommerce/seeds/source_order_items.csv b/003_ecommerce/seeds/source_order_items.csv new file mode 100644 index 0000000..cc56900 --- /dev/null +++ b/003_ecommerce/seeds/source_order_items.csv @@ -0,0 +1,7 @@ +id,order_id,product_id,quantity,unit_price,created_at,_loaded_at,_file_name +1,1,1,2,49.99,2024-01-01 12:00:00,2024-01-01 12:30:00,order_items_batch_1.csv +2,2,2,1,149.99,2024-01-02 13:00:00,2024-01-02 13:30:00,order_items_batch_2.csv +3,3,1,2,49.99,2024-01-02 14:00:00,2024-01-02 14:30:00,order_items_batch_2.csv +4,3,3,1,99.99,2024-01-02 14:00:00,2024-01-02 14:30:00,order_items_batch_2.csv +5,4,4,1,75.50,2024-01-03 10:00:00,2024-01-03 10:30:00,order_items_batch_3.csv +6,5,2,2,149.99,2024-01-03 11:00:00,2024-01-03 11:30:00,order_items_batch_3.csv diff --git a/003_ecommerce/seeds/source_orders.csv b/003_ecommerce/seeds/source_orders.csv new file mode 100644 index 0000000..1316daa --- /dev/null +++ b/003_ecommerce/seeds/source_orders.csv @@ -0,0 +1,6 @@ +id,user_id,total_amount,status,created_at,_loaded_at,_file_name +1,1,99.99,completed,2024-01-01 12:00:00,2024-01-01 12:30:00,orders_batch_1.csv +2,1,149.99,completed,2024-01-02 13:00:00,2024-01-02 13:30:00,orders_batch_2.csv +3,2,199.99,completed,2024-01-02 14:00:00,2024-01-02 14:30:00,orders_batch_2.csv +4,3,75.50,processing,2024-01-03 10:00:00,2024-01-03 10:30:00,orders_batch_3.csv +5,4,299.99,completed,2024-01-03 11:00:00,2024-01-03 11:30:00,orders_batch_3.csv diff --git a/003_ecommerce/seeds/source_product_categories.csv b/003_ecommerce/seeds/source_product_categories.csv new file mode 100644 index 0000000..434c47b --- /dev/null +++ b/003_ecommerce/seeds/source_product_categories.csv @@ -0,0 +1,6 @@ +id,name,parent_category_id,description,created_at,updated_at,_loaded_at,_file_name +1,Apparel,null,Clothing and accessories,2024-01-01 09:00:00,2024-01-01 09:00:00,2024-01-01 09:30:00,categories_batch_1.csv +2,Footwear,null,Shoes and related items,2024-01-01 09:00:00,2024-01-01 09:00:00,2024-01-01 09:30:00,categories_batch_1.csv +3,Exercise Equipment,null,Fitness and workout gear,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,categories_batch_2.csv +4,T-Shirts,1,All types of t-shirts,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,categories_batch_2.csv +5,Hoodies,1,Sweatshirts and hoodies,2024-01-03 09:00:00,2024-01-03 09:00:00,2024-01-03 09:30:00,categories_batch_3.csv diff --git a/003_ecommerce/seeds/source_products.csv b/003_ecommerce/seeds/source_products.csv new file mode 100644 index 0000000..2f59707 --- /dev/null +++ b/003_ecommerce/seeds/source_products.csv @@ -0,0 +1,6 @@ +id,sku,name,description,category_id,supplier_id,unit_price,stock_quantity,created_at,updated_at,_loaded_at,_file_name +1,SKU001,Basic T-Shirt,Comfortable cotton t-shirt,1,1,49.99,100,2024-01-01 09:00:00,2024-01-01 09:00:00,2024-01-01 09:30:00,products_batch_1.csv +2,SKU002,Premium Hoodie,Warm and stylish hoodie,1,1,149.99,50,2024-01-01 09:00:00,2024-01-01 09:00:00,2024-01-01 09:30:00,products_batch_1.csv +3,SKU003,Running Shoes,Lightweight running shoes,2,2,99.99,75,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,products_batch_2.csv +4,SKU004,Yoga Mat,Non-slip exercise mat,3,2,75.50,200,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,products_batch_2.csv +5,SKU005,Dumbbell Set,10lb weight set,3,3,129.99,30,2024-01-03 09:00:00,2024-01-03 09:00:00,2024-01-03 09:30:00,products_batch_3.csv diff --git a/003_ecommerce/seeds/source_shipments.csv b/003_ecommerce/seeds/source_shipments.csv new file mode 100644 index 0000000..1ba21fc --- /dev/null +++ b/003_ecommerce/seeds/source_shipments.csv @@ -0,0 +1,6 @@ +id,order_id,carrier_id,tracking_number,status,shipping_address_id,estimated_delivery_date,actual_delivery_date,created_at,updated_at,_loaded_at,_file_name +1,1,1,TRACK001,delivered,1,2024-01-03,2024-01-03,2024-01-01 12:30:00,2024-01-03 14:00:00,2024-01-03 14:30:00,shipments_batch_1.csv +2,2,1,TRACK002,in_transit,1,2024-01-04,null,2024-01-02 13:30:00,2024-01-02 13:30:00,2024-01-02 14:00:00,shipments_batch_2.csv +3,3,2,TRACK003,delivered,3,2024-01-04,2024-01-04,2024-01-02 14:30:00,2024-01-04 15:00:00,2024-01-04 15:30:00,shipments_batch_2.csv +4,4,2,TRACK004,processing,4,2024-01-05,null,2024-01-03 10:30:00,2024-01-03 10:30:00,2024-01-03 11:00:00,shipments_batch_3.csv +5,5,1,TRACK005,in_transit,5,2024-01-05,null,2024-01-03 11:30:00,2024-01-03 11:30:00,2024-01-03 12:00:00,shipments_batch_3.csv diff --git a/003_ecommerce/seeds/source_suppliers.csv b/003_ecommerce/seeds/source_suppliers.csv new file mode 100644 index 0000000..b6abf27 --- /dev/null +++ b/003_ecommerce/seeds/source_suppliers.csv @@ -0,0 +1,4 @@ +id,company_name,contact_name,contact_email,contact_phone,address,city,state,postal_code,country,created_at,updated_at,_loaded_at,_file_name +1,Fashion Corp,John Manager,john@fashioncorp.com,+1-555-1001,100 Fashion Ave,New York,NY,10001,USA,2024-01-01 09:00:00,2024-01-01 09:00:00,2024-01-01 09:30:00,suppliers_batch_1.csv +2,Sports Gear Inc,Sarah Director,sarah@sportsgear.com,+1-555-1002,200 Athletic Dr,Los Angeles,CA,90001,USA,2024-01-02 09:00:00,2024-01-02 09:00:00,2024-01-02 09:30:00,suppliers_batch_2.csv +3,Fitness Equipment Co,Mike Supplier,mike@fitequip.com,+1-555-1003,300 Fitness Blvd,Chicago,IL,60601,USA,2024-01-03 09:00:00,2024-01-03 09:00:00,2024-01-03 09:30:00,suppliers_batch_3.csv diff --git a/003_ecommerce/test_output.json b/003_ecommerce/test_output.json new file mode 100644 index 0000000..7b0678b --- /dev/null +++ b/003_ecommerce/test_output.json @@ -0,0 +1,258 @@ +[ + { + "model": "gold.customer_metrics", + "joins": [ + { + "type": "left", + "left": "silver.customers", + "right": "bronze.raw_customer_addresses", + "condition": "c.customer_id = ca.customer_id" + }, + { + "type": "LEFT", + "left": "bronze.raw_customer_addresses", + "right": "silver.orders", + "condition": "c.customer_id = o.user_id" + } + ], + "fields": [ + { + "name": "total_orders", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT o.order_id)", + "is_agg": true + }, + { + "name": "total_spend", + "type": "DECIMAL", + "sql": "SUM(o.total_amount)", + "is_agg": true + }, + { + "name": "average_order_value", + "type": "DECIMAL", + "sql": "AVG(o.total_amount)", + "is_agg": true + }, + { + "name": "first_order_date", + "type": "DECIMAL", + "sql": "MIN(o.order_timestamp)", + "is_agg": false + }, + { + "name": "last_order_date", + "type": "DECIMAL", + "sql": "MAX(o.order_timestamp)", + "is_agg": false + }, + { + "name": "customer_lifetime_days", + "type": "DECIMAL", + "sql": "DATEDIFF(MAX(o.order_timestamp), MIN(o.order_timestamp), DAY)", + "is_agg": false + } + ] + }, + { + "model": "gold.product_performance_metrics", + "joins": [ + { + "type": "LEFT", + "left": "silver.products", + "right": "silver.order_items", + "condition": "p.product_id = oi.product_id" + }, + { + "type": "LEFT", + "left": "silver.order_items", + "right": "silver.product_categories", + "condition": "p.category_id = c.category_id" + } + ], + "fields": [ + { + "name": "number_of_orders", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT oi.order_id)", + "is_agg": true + }, + { + "name": "units_sold", + "type": "DECIMAL", + "sql": "SUM(oi.quantity)", + "is_agg": true + }, + { + "name": "total_revenue", + "type": "DECIMAL", + "sql": "SUM(oi.total_item_amount)", + "is_agg": true + }, + { + "name": "average_selling_price", + "type": "DECIMAL", + "sql": "AVG(oi.unit_price)", + "is_agg": true + }, + { + "name": "current_stock_level", + "type": "DECIMAL", + "sql": "p.stock_quantity", + "is_agg": false + }, + { + "name": "inventory_status", + "type": "DECIMAL", + "sql": "CASE\n WHEN p.stock_quantity = 0\n THEN 'Out of Stock'\n WHEN p.stock_quantity < 10\n THEN 'Low Stock'\n ELSE 'In Stock'\nEND", + "is_agg": false + } + ] + }, + { + "model": "gold.supplier_performance_metrics", + "joins": [ + { + "type": "LEFT", + "left": "silver.suppliers", + "right": "silver.products", + "condition": "s.supplier_id = p.supplier_id" + }, + { + "type": "LEFT", + "left": "silver.products", + "right": "silver.order_items", + "condition": "p.product_id = oi.product_id" + } + ], + "fields": [ + { + "name": "number_of_products", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT p.product_id)", + "is_agg": true + }, + { + "name": "total_stock_quantity", + "type": "DECIMAL", + "sql": "SUM(p.stock_quantity)", + "is_agg": true + }, + { + "name": "out_of_stock_products", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT CASE WHEN p.stock_quantity = 0 THEN p.product_id END)", + "is_agg": true + }, + { + "name": "number_of_orders", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT oi.order_id)", + "is_agg": true + }, + { + "name": "total_units_sold", + "type": "DECIMAL", + "sql": "SUM(oi.quantity)", + "is_agg": true + }, + { + "name": "total_revenue", + "type": "DECIMAL", + "sql": "SUM(oi.total_item_amount)", + "is_agg": true + }, + { + "name": "supplier_country", + "type": "DECIMAL", + "sql": "s.country", + "is_agg": false + }, + { + "name": "number_of_categories_supplied", + "type": "DECIMAL", + "sql": "COUNT(DISTINCT p.category_id)", + "is_agg": true + } + ] + }, + { + "model": "gold.daily_sales_metrics", + "joins": [ + { + "type": "INNER", + "left": "silver.orders", + "right": "silver.order_items", + "condition": "o.date = i.date" + } + ], + "fields": [ + { + "name": "silver.orders.date", + "type": "DATE", + "sql": "CAST(order_timestamp AS DATE)", + "is_agg": false + }, + { + "name": "silver.orders.total_orders", + "type": "BIGINT", + "sql": "COUNT(DISTINCT order_id)", + "is_agg": true + }, + { + "name": "silver.orders.unique_customers", + "type": "BIGINT", + "sql": "COUNT(DISTINCT user_id)", + "is_agg": true + }, + { + "name": "silver.orders.total_revenue", + "type": "DECIMAL", + "sql": "SUM(total_amount)", + "is_agg": true + }, + { + "name": "silver.orders.completed_orders", + "type": "DECIMAL", + "sql": "SUM(CASE WHEN is_completed THEN 1 ELSE 0 END)", + "is_agg": true + }, + { + "name": "silver.order_items.date", + "type": "DATE", + "sql": "CAST(order_timestamp AS DATE)", + "is_agg": false + }, + { + "name": "silver.order_items.unique_products_sold", + "type": "BIGINT", + "sql": "COUNT(DISTINCT product_id)", + "is_agg": true + }, + { + "name": "silver.order_items.total_items_sold", + "type": "DECIMAL", + "sql": "SUM(quantity)", + "is_agg": true + }, + { + "name": "silver.order_items.avg_unit_price", + "type": "DOUBLE", + "sql": "AVG(unit_price)", + "is_agg": true + }, + { + "name": "avg_order_value", + "type": "DOUBLE", + "sql": "o.total_revenue / NULLIF(NULLIF(o.total_orders, 0), 0)", + "is_agg": false + }, + { + "name": "order_completion_rate", + "type": "DOUBLE", + "sql": "CAST(o.completed_orders AS FLOAT) / NULLIF(NULLIF(o.total_orders, 0), 0)", + "is_agg": false + } + ] + } +] \ No newline at end of file diff --git a/003_ecommerce/test_output.yaml b/003_ecommerce/test_output.yaml new file mode 100644 index 0000000..9573cf2 --- /dev/null +++ b/003_ecommerce/test_output.yaml @@ -0,0 +1,330 @@ +```yaml +cubes: + - name: silver_customers + sql: SELECT * FROM db.silver.customers + dimensions: + - name: customer_id + sql: customer_id + type: number + primary_key: true + - name: full_name + sql: full_name + type: string + - name: email + sql: email + type: string + joins: + - name: bronze_raw_customer_addresses + relationship: one_to_many + sql: "{CUBE.customer_id} = {bronze_raw_customer_addresses.customer_id}" + - name: silver_orders + relationship: one_to_many + sql: "{CUBE.customer_id} = {silver_orders.user_id}" + + - name: bronze_raw_customer_addresses + sql: SELECT * FROM db.bronze.raw_customer_addresses + dimensions: + - name: customer_id + sql: customer_id + type: number + - name: city + sql: city + type: string + - name: state + sql: state + type: string + - name: country + sql: country + type: string + + - name: silver_orders + sql: SELECT * FROM db.silver.orders + dimensions: + - name: order_id + sql: order_id + type: number + primary_key: true + - name: user_id + sql: user_id + type: number + - name: order_timestamp + sql: order_timestamp + type: time + - name: date + sql: CAST({order_timestamp} AS DATE) + type: time + - name: is_completed + sql: is_completed + type: boolean + - name: total_amount + sql: total_amount + type: number + measures: + - name: total_orders + sql: order_id + type: count_distinct + - name: unique_customers + sql: user_id + type: count_distinct + - name: total_revenue + sql: total_amount + type: sum + - name: total_spend + sql: total_amount + type: sum + - name: average_order_value + sql: total_amount + type: avg + - name: first_order_date + sql: order_timestamp + type: min + - name: last_order_date + sql: order_timestamp + type: max + - name: customer_lifetime_days + sql: "DATEDIFF('day', {first_order_date}, {last_order_date})" + type: number + - name: completed_orders + sql: "CASE WHEN {is_completed} THEN 1 ELSE 0 END" + type: sum + - name: avg_order_value + sql: "{total_revenue} / NULLIF({total_orders}, 0)" + type: number + - name: order_completion_rate + sql: "CAST({completed_orders} AS FLOAT) / NULLIF({total_orders}, 0)" + type: number + joins: + - name: silver_customers + relationship: many_to_one + sql: "{CUBE.user_id} = {silver_customers.customer_id}" + - name: silver_order_items + relationship: one_to_many + sql: "{CUBE.order_id} = {silver_order_items.order_id}" + + - name: silver_order_items + sql: SELECT * FROM db.silver.order_items + dimensions: + - name: order_item_id + sql: order_item_id + type: number + primary_key: true + - name: order_id + sql: order_id + type: number + - name: product_id + sql: product_id + type: number + - name: order_timestamp + sql: order_timestamp + type: time + - name: date + sql: CAST({order_timestamp} AS DATE) + type: time + - name: quantity + sql: quantity + type: number + - name: unit_price + sql: unit_price + type: number + - name: total_item_amount + sql: total_item_amount + type: number + measures: + - name: unique_products_sold + sql: product_id + type: count_distinct + - name: total_items_sold + sql: quantity + type: sum + - name: avg_unit_price + sql: unit_price + type: avg + - name: number_of_orders + sql: order_id + type: count_distinct + - name: units_sold + sql: quantity + type: sum + - name: total_revenue + sql: total_item_amount + type: sum + - name: average_selling_price + sql: unit_price + type: avg + joins: + - name: silver_orders + relationship: many_to_one + sql: "{CUBE.order_id} = {silver_orders.order_id}" + - name: silver_products + relationship: many_to_one + sql: "{CUBE.product_id} = {silver_products.product_id}" + + - name: silver_suppliers + sql: SELECT * FROM db.silver.suppliers + dimensions: + - name: supplier_id + sql: supplier_id + type: number + primary_key: true + - name: company_name + sql: company_name + type: string + - name: supplier_country + sql: country + type: string + joins: + - name: silver_products + relationship: one_to_many + sql: "{CUBE.supplier_id} = {silver_products.supplier_id}" + + - name: silver_products + sql: SELECT * FROM db.silver.products + dimensions: + - name: product_id + sql: product_id + type: number + primary_key: true + - name: supplier_id + sql: supplier_id + type: number + - name: category_id + sql: category_id + type: number + - name: product_name + sql: product_name + type: string + - name: stock_quantity + sql: stock_quantity + type: number + - name: current_stock_level + sql: stock_quantity + type: number + - name: inventory_status + sql: "CASE WHEN {stock_quantity} = 0 THEN 'Out of Stock' + WHEN {stock_quantity} < 10 THEN 'Low Stock' ELSE 'In Stock' END" + type: string + measures: + - name: total_stock_quantity + sql: stock_quantity + type: sum + - name: out_of_stock_products + sql: product_id + type: count_distinct + filters: + - sql: "{stock_quantity} = 0" + - name: number_of_products + sql: product_id + type: count_distinct + - name: number_of_categories_supplied + sql: category_id + type: count_distinct + - name: units_sold + sql: "{silver_order_items.quantity}" + type: sum + - name: total_revenue + sql: "{silver_order_items.total_item_amount}" + type: sum + - name: average_selling_price + sql: "{silver_order_items.unit_price}" + type: avg + - name: number_of_orders + sql: "{silver_order_items.order_id}" + type: count_distinct + joins: + - name: silver_suppliers + relationship: many_to_one + sql: "{CUBE.supplier_id} = {silver_suppliers.supplier_id}" + - name: silver_order_items + relationship: one_to_many + sql: "{CUBE.product_id} = {silver_order_items.product_id}" + - name: silver_product_categories + relationship: many_to_one + sql: "{CUBE.category_id} = {silver_product_categories.category_id}" + + - name: silver_product_categories + sql: SELECT * FROM db.silver.product_categories + dimensions: + - name: category_id + sql: category_id + type: number + primary_key: true + - name: category_name + sql: category_name + type: string + +views: + - name: customer_metrics + cubes: + - join_path: silver_customers + includes: + - customer_id + - full_name + - email + - join_path: silver_customers.bronze_raw_customer_addresses + includes: + - city + - state + - country + - join_path: silver_customers.silver_orders + includes: + - total_orders + - total_spend + - average_order_value + - first_order_date + - last_order_date + - customer_lifetime_days + + - name: daily_sales_metrics + cubes: + - join_path: silver_orders + includes: + - date + - total_orders + - unique_customers + - total_revenue + - completed_orders + - avg_order_value + - order_completion_rate + - join_path: silver_orders.silver_order_items + includes: + - unique_products_sold + - total_items_sold + - avg_unit_price + + - name: supplier_performance_metrics + cubes: + - join_path: silver_suppliers + includes: + - supplier_id + - company_name + - supplier_country + - join_path: silver_suppliers.silver_products + includes: + - number_of_products + - total_stock_quantity + - out_of_stock_products + - number_of_categories_supplied + - join_path: silver_suppliers.silver_products.silver_order_items + includes: + - number_of_orders + - units_sold + - total_revenue + + - name: product_performance_metrics + cubes: + - join_path: silver_products + includes: + - product_id + - product_name + - current_stock_level + - inventory_status + - join_path: silver_products.silver_product_categories + includes: + - category_name + - join_path: silver_products.silver_order_items + includes: + - number_of_orders + - units_sold + - total_revenue + - average_selling_price +``` \ No newline at end of file diff --git a/003_ecommerce/tests/.gitkeep b/003_ecommerce/tests/.gitkeep new file mode 100644 index 0000000..e69de29