Product sales over time
Recreates WooCommerce Analytics' Products and Variations reports (items sold, net sales, orders) at line grain, with the same sales equation as sales_over_time.
- Grain
- day x store x order x line
- Built from
- 6Raw tables
- 6Staging
- 1Core
- 1Analytics
- Reads from WooCommerce
- wp_optionswp_postmetawp_postswp_wc_order_product_lookupwp_wc_order_statswp_woocommerce_order_items
- Ships as
- Weld and dbt SQL, open source on GitHub
1-- core.woocommerce.product_sales_over_time
2-- Recreates WooCommerce Analytics' Products and Variations reports (items sold,
3-- net sales, orders) at line grain, with the same sales equation as
4-- sales_over_time.
5--
6-- Grain: day x store x order x line. A line is one row of wc_order_product_lookup:
7-- a product line on an order (row_type SALE) or on a refund (row_type RETURN).
8-- order_id is always the ORIGINAL order, so a refunded line and the line it
9-- reverses share an order_id but not a line id.
10--
11-- Same report date and status rules as sales_over_time (read from the store's
12-- analytics settings, applied through wc_order_stats), so the two agree on which
13-- orders count and on which day.
14--
15-- Product lines only. Shipping lines, fees and order-level refund amounts that are
16-- not tied to a product are not in the lookup table, so net_sales here sums to
17-- less than sales_over_time.net_sales whenever a store charges fees or refunds a
18-- lump sum. Discounts do tie exactly: sales_over_time takes its coupons from these
19-- same lines.
20--
21-- Orders per product: COUNT(DISTINCT order_id) over SALE rows, which is the
22-- Products report's "Orders" column. Not a column here, because a distinct count
23-- does not survive being summed across days.
24--
25-- Names: product_name is the current catalog name; line_item_name is the name as
26-- it read on the order and survives the product being renamed or deleted.
27--
28-- Depends on: staging.woocommerce.order_product_lookup, .order_stats,
29-- .order_items, .product, .product_sku, .store_settings
30
31WITH settings AS (
32 SELECT
33 woocommerce_store,
34 currency,
35 COALESCE(analytics_date_type, 'date_paid') AS date_type,
36 ARRAY_CONCAT(
37 ['wc-auto-draft', 'wc-trash'],
38 COALESCE(excluded_statuses, ['wc-pending', 'wc-failed', 'wc-cancelled'])
39 ) AS excluded_statuses
40 FROM {{staging.woocommerce.store_settings}}This model and the 7 it depends on. Deploy them together; refs like {{staging.…}} resolve to the matching folders in Weld, and the dbt view uses source() and ref().
How it counts. And where it stops.
Same report date and status rules as sales_over_time (read from the store's analytics settings, applied through wc_order_stats), so the two agree on which orders count and on which day. Product lines only. Shipping lines, fees and order-level refund amounts that are not tied to a product are not in the lookup table, so net_sales here sums to less than sales_over_time.net_sales whenever a store charges fees or refunds a lump sum. Discounts do tie exactly: sales_over_time takes its coupons from these same lines. Orders per product: COUNT(DISTINCT order_id) over SALE rows, which is the Products report's "Orders" column. Not a column here, because a distinct count does not survive being summed across days. Names: product_name is the current catalog name; line_item_name is the name as it read on the order and survives the product being renamed or deleted.
Raw tables it reads
- raw.woocommerce.wp_options
- raw.woocommerce.wp_postmeta
- raw.woocommerce.wp_posts
- raw.woocommerce.wp_wc_order_product_lookup
- raw.woocommerce.wp_wc_order_stats
- raw.woocommerce.wp_woocommerce_order_items
More WooCommerce models
Put WooCommerce in front
of your agents.
Connect WooCommerce, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.