Shopify model

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
  1. 8
    Raw tables
  2. 8
    Staging
  3. 1
    Core
  4. 1
    Analytics
Reads from Shopify
locationorderorder_agreementorder_agreement_saleorder_lineorder_line_refundorder_refundshop
Ships as
Weld and dbt SQL, open source on GitHub
core.shopify.sales_over_time
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().

Notes

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

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.