Sales Over Time
Recreates Shopify's "Total sales over time" on Shopify's own event grain, so returns land on the refund date and order edits on the edit date. Returns the full sales equation plus parity variants, the returns detail block, gift-card columns, and location, sales-channel, country and fulfilment dimensions.
- Grain
- day x store x order x location x channel x row type
- Built from
- 8Raw tables
- 8Staging
- 1Core
- 1Analytics
- Reads from Shopify
- locationorderorder_agreementorder_agreement_saleorder_lineorder_line_refundorder_refundshop
- Ships as
- Weld and dbt SQL, open source on GitHub
1-- shopify_sales_over_time - recreates Shopify's "Total sales over time"
2--
3-- Grain: day x store x order x location x channel x row type. Keeping row type
4-- in the grain means a day where an order both sells and refunds produces two
5-- rows, so the SALE and RETURN sides stay independently auditable.
6--
7-- Multi-store safe: order IDs are only unique WITHIN a store, so shopify_store is
8-- part of every join key and of the grain. Drop it from one join and rows fan out
9-- across storefronts silently.
10--
11-- Event-based: reads Shopify's order agreement log, so every financial change
12-- lands on the day it happened - returns on the refund date, order edits on the
13-- edit date. History does not restate itself when an old order is refunded.
14--
15-- Currency: shop money, plus the presentment amounts the customer actually paid.
16-- Timezone: read from the shop record, so nothing needs hardcoding.
17--
18-- Depends on: staging.shopify.order, .order_agreement, .order_agreement_sale,
19-- .order_line, .order_refund, .order_line_refund, .shop, .location
20
21WITH shop AS (
22 -- Per store: currency and timezone both vary between storefronts.
23 SELECT
24 shopify_store,
25 ANY_VALUE(currency) AS shop_currency,
26 COALESCE(ANY_VALUE(iana_timezone), 'UTC') AS report_timezone
27 FROM {{staging.shopify.shop}}
28 GROUP BY shopify_store
29),
30
31locations AS (
32 SELECT shopify_store, location_id, location_name, location_country_code
33 FROM {{staging.shopify.location}}
34),
35
36orders AS (
37 SELECT
38 o.shopify_store,
39 o.order_id,
40 o.order_name,This model and the 9 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.
Rebuilds Shopify's flagship sales report from the order agreement log rather than order_line. order_line is a current-state table: it says what an order looks like now and cannot express when its value changed, so summing it nets returns back to the original order date, hides order edits, and silently restates history. In practice that drifts 2-20% from the admin. Reading the agreement log instead attributes every financial change to the day it actually happened. The model returns one row per order per day per row type, so a day where an order both sells and refunds produces two rows and the SALE and RETURN sides stay independently auditable - aggregate to day in your BI tool. Alongside the sales equation it ships the columns that make reconciliation possible: parity variants for net sales and units, because Shopify's admin UI and its CSV export do not agree; a returns detail block with reversal_type separating cancellations from genuine returns; gift-card columns kept out of the sales equation as deferred revenue; and dimensions that report what Shopify stores rather than interpreting it: location_name is the order's own location and stays NULL when there is none, refund_location_name is kept separate because a refund is often processed somewhere the order was not, sales_channel is Shopify's own source_name, and fulfilment status distinguishes digital-only orders from genuinely unfulfilled ones. Timezone is read from the shop record, so nothing needs hardcoding.
Raw tables it reads
- raw.shopify.location
- raw.shopify.order
- raw.shopify.order_agreement
- raw.shopify.order_agreement_sale
- raw.shopify.order_line
- raw.shopify.order_line_refund
- raw.shopify.order_refund
- raw.shopify.shop
More Shopify models
Put Shopify in front
of your agents.
Connect Shopify, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.