Skip to main content
net_return_cost = refund_amount + gift_card_amount - upsell_amount. A positive value represents a net cost to the brand. All three components are coalesced to zero when null.
is_child_order_return = TRUE (EXC- order names) does not mean the return has an exchange outcome — use has_exchange instead. Multiple returns per order are common; when counting Return Orders, use COUNT(DISTINCT sm_order_key) rather than counting rows.

When to use this table

Use fct_returns when you need Loop Returns data at the return-event grain. It is best for return outcomes, return timing, refund/store-credit/upsell economics, and order-level return counts after deduplicating by sm_order_key. Common questions this table can answer:
  • What share of valid orders had at least one return?
  • How much net return cost is tied to each channel or campaign?
  • Are customers choosing refunds, exchanges, store credit, or upsells?
  • How long after purchase do returns usually start?
For return analysis tied to known Shopify orders, start with:
WHERE is_order_matched = TRUE
Use COUNT(DISTINCT sm_order_key) for returned-order counts. Use COUNT(*) only when you intentionally want return-event counts.

Common joins

  • Join to obt_orders on sm_order_key when you need the full valid-order denominator for return-rate calculations.
  • Join to obt_order_lines on sm_order_key for product-level analysis of orders that had returns.
  • Join to dim_customers on sm_customer_key for customer-level return behavior.
For ready-to-run templates, see the Orders & Revenue SQL Query Library.
version: 2

models:
  - name: fct_returns
    description: >
      Loop Returns fact enriched with Shopify order context from dim_orders.
      Grain: one row per Loop return (sm_store_id + return_id).
      Requires Loop Returns integration.
      Join: left join dim_orders on shopify_order_id = order_id, sm_store_id, and source_system = Shopify.
      Critical filter: is_order_matched = TRUE to scope to returns tied to known Shopify orders;
      days_to_return and return_week_bucket are null when unmatched.
      Key joins: dim_orders via sm_order_key (many:1); dim_customers via sm_customer_key (many:1).
    columns:
      - name: sm_store_id
        description: >
          SourceMedium store identifier. Unique per tenant.

      - name: sm_return_key
        description: >
          Surrogate key for the return event (sm_store_id + return_id). Unique per row.

      - name: return_id
        description: >
          Loop Returns internal return id.

      - name: shopify_order_id
        description: >
          Shopify order id; join key to dim_orders.order_id for Shopify rows.

      - name: sm_order_key
        description: >
          Order key from dim_orders when the return matched a Shopify order.
          Null when is_order_matched = FALSE.

      - name: sm_customer_key
        description: >
          Customer key from dim_orders when matched. Null when is_order_matched = FALSE.

      - name: return_state
        description: >
          Loop return state. Values: open, closed, expired.

      - name: return_outcome
        description: >
          Loop return-level outcome. Values: exchange, refund, upsell, credit, or combined
          (e.g., exchange+refund). Use has_refund / has_exchange / has_upsell / has_credit
          flags for reliable filtering.

      - name: return_type
        description: >
          Loop return type.

      - name: loop_order_name
        description: >
          Loop order display name. Standard returns use a # prefix; exchange child orders use EXC- prefix.

      - name: is_child_order_return
        description: >
          True when loop_order_name starts with EXC-, indicating an exchange child order.
          Not a proxy for has_exchange — use has_exchange to determine exchange outcome.

      - name: customer_email
        description: >
          Email on the return record.

      - name: origin_country_code
        description: >
          Return origin country code.

      - name: return_currency
        description: >
          Currency for return financials.

      - name: return_total
        description: >
          Total return value from Loop.

      - name: return_product_total
        description: >
          Returned product subtotal.

      - name: return_discount_total
        description: >
          Discounts on returned items.

      - name: return_tax_total
        description: >
          Tax on returned items.

      - name: refund_amount
        description: >
          Cash refunded to customer.

      - name: exchange_total
        description: >
          Exchange merchandise total.

      - name: exchange_product_total
        description: >
          Exchange product subtotal.

      - name: upsell_amount
        description: >
          Additional amount collected on the return (upsell).

      - name: gift_card_amount
        description: >
          Store credit or gift card amount issued on the return.

      - name: handling_fee_amount
        description: >
          Handling fee charged on the return.

      - name: net_return_cost
        description: >
          Net cost of the return to the brand: refund_amount + gift_card_amount - upsell_amount
          (all components coalesced to zero). Positive value = net cost to brand.

      - name: order_created_at
        description: >
          Matched Shopify order created_at from dim_orders. Null when is_order_matched = FALSE.

      - name: order_processed_at_local_datetime
        description: >
          Matched order processed datetime in reporting timezone. Null when is_order_matched = FALSE.

      - name: sm_channel
        description: >
          SM channel on the matched order. Null when is_order_matched = FALSE.

      - name: sm_sub_channel
        description: >
          SM sub-channel on the matched order. Null when is_order_matched = FALSE.

      - name: order_name
        description: >
          Shopify order name from dim_orders when matched. Null when is_order_matched = FALSE.

      - name: order_index
        description: >
          Customer order index from dim_orders when matched (1 = first order). Null when is_order_matched = FALSE.

      - name: order_sequence
        description: >
          First vs repeat order sequence from dim_orders when matched.
          Values: 1st_order, repeat_order. Null when is_order_matched = FALSE.

      - name: return_created_at
        description: >
          Timestamp when the customer initiated the return.

      - name: return_updated_at
        description: >
          Timestamp of the last update on the return in Loop.

      - name: days_to_return
        description: >
          Calendar days from order_created_at to return_created_at.
          Null when is_order_matched = FALSE. Non-negative when present.

      - name: return_week_bucket
        description: >
          Bucketed days from order to return initiation.
          Values: 0-7 days, 8-14 days, 15-21 days, 22-30 days, 31-45 days, 46-60 days, 60+ days.
          Null when is_order_matched = FALSE.

      - name: has_refund
        description: >
          True when return_outcome contains "refund" (including exchange+refund).

      - name: has_exchange
        description: >
          True when return_outcome contains "exchange" (including exchange+refund).

      - name: has_upsell
        description: >
          True when return_outcome is exactly "upsell".

      - name: has_credit
        description: >
          True when return_outcome contains "credit".

      - name: is_order_matched
        description: >
          True when a dim_orders row was found for shopify_order_id + sm_store_id + Shopify.
          When false, all order-context fields are null.