← Back to Metrics

Units Sold

Also known as: Items Purchased, Quantity Sold, Item Quantity, Total Units

MonetizationmetricE-commerce: Cart & Checkout Funnel AnalyticsE-commerce

Units Sold is the total quantity of items purchased across completed transactions in the period, summing the item-level quantity carried on every purchase. Because it measures volume rather than value, it isolates demand from price, discount and order-size effects, which makes it the reference base for sell-through, replenishment planning and per-unit economics.

Business Use Case

1️⃣ Merchandising planners review Units Sold beside Item Revenue by item category and SKU in the weekly trade meeting; SKUs with high unit velocity but thin revenue contribution are demoted from prime product-list and home-page slots in favour of higher-value substitutes.,2️⃣ Revenue Management reads Units Sold split by coupon code and discount depth within 72 hours of a promotion ending and extends, re-prices or kills the offer depending on whether the unit lift covered the margin given away.,3️⃣ Finance reconciles digital Units Sold to shipped quantity from the order-management system at every month-end close; a variance beyond the agreed tolerance opens a tagging investigation before the figure is released into the demand and inventory forecast.,4️⃣ Marketing compares Units Sold by default channel grouping and campaign against spend each month and shifts budget toward channels that deliver unit volume in the categories the merchandising calendar is pushing, rather than isolated high-ticket orders.

Business Criticality

High

Journey / Navigation Area

Checkout

Owning Function

Merchandising

Scope

Event

Formula

SUM(purchase.items[].quantity)

Calculation Notes

1️⃣ The grain is the item line, not the order: every purchase event contributes the sum of the quantity on its item array, so an order containing three of one SKU adds three units while a single-line order adds one. Order count and order value never enter the calculation.,2️⃣ Gross at transaction time: units are booked when the purchase confirms and are never retroactively reduced by returns, refunds, cancellations or chargebacks. Net Units Sold is a separate derivation that subtracts refunded quantity against the original order date; the two are published side by side rather than one silently netted into the other.,3️⃣ Exclusions: internal and test orders identified by the QA order prefix or test payment method, non-merchandise lines (gift wrap, shipping insurance, donations and gift cards, which are always excluded because they are stored-value tender rather than merchandise), bundle parent SKUs whose components are also transmitted, and lines carrying a missing, zero or negative quantity — those are raised as a tagging defect rather than coerced to one.,4️⃣ Transaction de-duplication: purchase is serialised once per transaction ID, so confirmation-page reloads, back navigation and client-side retries must resolve to the first event per transaction ID. Without that guard the full basket quantity is counted twice, and the inflation lands disproportionately on desktop and on slow networks.,5️⃣ Timing: units are attributed to the timestamp of the purchase event in the reporting time zone, never to session start or ship date. The 30-minute session-inactivity timeout, midnight boundary and campaign-change session resets therefore move only session-scoped breakdowns such as channel, landing page or device — the period total is unaffected.,6️⃣ Quantity carries no price component, so units aggregate cleanly across currencies, markets and item categories without an FX step; this is the roll-up to trust when price changes, discount depth or exchange rates distort revenue comparisons year over year.,7️⃣ Pre-orders, backorders and subscription renewals count at purchase confirmation regardless of fulfilment date; a renewal contributes its ordered quantity once per billing transaction, and split shipments of a single order never re-count the same units.

Technical Details

Measure TypeSum
Aggregation WindowDaily, Weekly, Monthly, Quarterly

Events

PlatformEvent
Google Analytics 4purchase
Adobepurchase

Data Mappings

W3 Data Layer
{
  "digitalData": {
    "pageInstanceID": "prod-web-us",
    "page": {
      "pageInfo": {
        "pageID": "checkout-confirmation",
        "pageName": "Order Confirmation",
        "destinationURL": "https://www.example.com/checkout/confirmation",
        "sysEnv": "web",
        "language": "en-US"
      },
      "category": {
        "primaryCategory": "Checkout",
        "subCategory1": "Order Confirmation"
      }
    },
    "transaction": {
      "transactionID": "TXN-2026-4471829",
      "profile": {
        "profileInfo": {
          "profileID": "a7f3c9e1-2b44-4f0d-9c18-5d61be720f33"
        }
      },
      "total": {
        "basePrice": 186.5,
        "voucherCode": "SPRING10",
        "voucherDiscount": 18.65,
        "currency": "USD",
        "taxRate": 0.0825,
        "shipping": 7.95,
        "shippingMethod": "Standard Ground",
        "priceWithTax": 181.7,
        "transactionTotal": 189.65
      },
      "item": [
        {
          "productInfo": {
            "productID": "SKU-TS-4412",
            "productName": "Ribbed Crew T-Shirt",
            "manufacturer": "Northline",
            "color": "Charcoal",
            "size": "M"
          },
          "category": {
            "primaryCategory": "Apparel"
          },
          "quantity": 3,
          "price": {
            "basePrice": 24,
            "currency": "USD",
            "priceWithTax": 24
          }
        },
        {
          "productInfo": {
            "productID": "SKU-SN-9087",
            "productName": "Trail Runner Sneaker",
            "manufacturer": "Ridgeway",
            "color": "Slate",
            "size": "10"
          },
          "category": {
            "primaryCategory": "Footwear"
          },
          "quantity": 1,
          "price": {
            "basePrice": 89.5,
            "currency": "USD",
            "priceWithTax": 89.5
          }
        },
        {
          "productInfo": {
            "productID": "SKU-SO-2210",
            "productName": "Merino Crew Socks",
            "manufacturer": "Northline",
            "color": "Oatmeal",
            "size": "L"
          },
          "category": {
            "primaryCategory": "Accessories"
          },
          "quantity": 2,
          "price": {
            "basePrice": 12.5,
            "currency": "USD",
            "priceWithTax": 12.5
          }
        }
      ],
      "attributes": {
        "checkoutType": "Guest",
        "paymentMethod": "Credit Card",
        "totalUnits": 6,
        "lineItemCount": 3
      }
    },
    "event": [
      {
        "eventInfo": {
          "eventName": "purchase",
          "eventAction": "Order Completed",
          "type": "transaction",
          "timeStamp": "2026-09-17T14:52:08.412Z"
        }
      }
    ]
  }
}
GA4 Data Layer
{
  "event": "purchase",
  "ecommerce": {
    "transaction_id": "TXN-2026-4471829",
    "currency": "USD",
    "value": 167.85,
    "tax": 13.85,
    "shipping": 7.95,
    "coupon": "SPRING10",
    "shipping_tier": "Standard Ground",
    "payment_type": "Credit Card",
    "items": [
      {
        "item_id": "SKU-TS-4412",
        "item_name": "Ribbed Crew T-Shirt",
        "item_brand": "Northline",
        "item_category": "Apparel",
        "item_category2": "Tops",
        "item_variant": "Charcoal / M",
        "coupon": "SPRING10",
        "discount": 2.4,
        "price": 21.6,
        "quantity": 3,
        "index": 1
      },
      {
        "item_id": "SKU-SN-9087",
        "item_name": "Trail Runner Sneaker",
        "item_brand": "Ridgeway",
        "item_category": "Footwear",
        "item_category2": "Running",
        "item_variant": "Slate / 10",
        "coupon": "SPRING10",
        "discount": 8.95,
        "price": 80.55,
        "quantity": 1,
        "index": 2
      },
      {
        "item_id": "SKU-SO-2210",
        "item_name": "Merino Crew Socks",
        "item_brand": "Northline",
        "item_category": "Accessories",
        "item_category2": "Socks",
        "item_variant": "Oatmeal / L",
        "coupon": "SPRING10",
        "discount": 1.25,
        "price": 11.25,
        "quantity": 2,
        "index": 3
      }
    ]
  },
  "checkout_type": "guest",
  "total_units": 6,
  "entry_method": "assisted"
}
Adobe Client Data Layer
{
  "event": "purchase",
  "eventInfo": {
    "eventName": "purchase",
    "eventTimestamp": "2026-09-17T14:52:08.412Z",
    "path": "commerce.order"
  },
  "commerce": {
    "order": {
      "purchaseID": "TXN-2026-4471829",
      "purchaseOrderNumber": "TXN-2026-4471829",
      "currencyCode": "USD",
      "priceTotal": 181.7,
      "taxAmount": 13.85,
      "shippingAmount": 7.95,
      "payments": [
        {
          "paymentType": "credit_card",
          "paymentAmount": 189.65,
          "currencyCode": "USD"
        }
      ],
      "discountAmount": 18.65,
      "couponCode": "SPRING10",
      "checkoutType": "guest"
    },
    "purchases": {
      "value": 1
    },
    "productListItems": [
      {
        "SKU": "SKU-TS-4412",
        "name": "Ribbed Crew T-Shirt",
        "productCategory": "Apparel",
        "productBrand": "Northline",
        "productVariant": "Charcoal / M",
        "priceTotal": 64.8,
        "currencyCode": "USD",
        "quantity": 3
      },
      {
        "SKU": "SKU-SN-9087",
        "name": "Trail Runner Sneaker",
        "productCategory": "Footwear",
        "productBrand": "Ridgeway",
        "productVariant": "Slate / 10",
        "priceTotal": 80.55,
        "currencyCode": "USD",
        "quantity": 1
      },
      {
        "SKU": "SKU-SO-2210",
        "name": "Merino Crew Socks",
        "productCategory": "Accessories",
        "productBrand": "Northline",
        "productVariant": "Oatmeal / L",
        "priceTotal": 22.5,
        "currencyCode": "USD",
        "quantity": 2
      }
    ]
  }
}
XDM Mapping
{
  "eventType": "commerce.purchases",
  "timestamp": "2026-09-17T14:52:08.412Z",
  "commerce": {
    "purchases": {
      "value": 1
    },
    "order": {
      "purchaseID": "TXN-2026-4471829",
      "purchaseOrderNumber": "TXN-2026-4471829",
      "currencyCode": "USD",
      "priceTotal": 181.7,
      "payments": [
        {
          "transactionID": "TXN-2026-4471829",
          "paymentType": "credit_card",
          "paymentAmount": 189.65,
          "currencyCode": "USD"
        }
      ]
    }
  },
  "_tenant": {
    "order": {
      "couponCode": "SPRING10",
      "discountAmount": 18.65
    }
  },
  "productListItems": [
    {
      "SKU": "SKU-TS-4412",
      "name": "Ribbed Crew T-Shirt",
      "productCategory": "Apparel",
      "quantity": 3,
      "priceTotal": 64.8,
      "currencyCode": "USD",
      "_tenant": {
        "productBrand": "Northline",
        "productVariant": "Charcoal / M"
      }
    },
    {
      "SKU": "SKU-SN-9087",
      "name": "Trail Runner Sneaker",
      "productCategory": "Footwear",
      "quantity": 1,
      "priceTotal": 80.55,
      "currencyCode": "USD",
      "_tenant": {
        "productBrand": "Ridgeway",
        "productVariant": "Slate / 10"
      }
    },
    {
      "SKU": "SKU-SO-2210",
      "name": "Merino Crew Socks",
      "productCategory": "Accessories",
      "quantity": 2,
      "priceTotal": 22.5,
      "currencyCode": "USD",
      "_tenant": {
        "productBrand": "Northline",
        "productVariant": "Oatmeal / L"
      }
    }
  ],
  "_experience": {
    "analytics": {
      "customDimensions": {
        "eVars": {
          "eVar12": "guest",
          "eVar18": "Standard Ground"
        }
      },
      "event1to100": {
        "event7": {
          "value": 6
        }
      }
    }
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Metric : Units Sold — gross item quantity on completed purchase transactions
-- Grain  : one row per reporting day x merchandising / channel breakdown
-- Source : GA4 BigQuery export, date-sharded events_* (quantity lives in the items array,
--          event-scoped parameters are pivoted out of event_params with UNNEST / MAX(IF()))
-- Scope  : GROSS units booked at transaction time. Refunds, cancellations and chargebacks are
--          NOT netted here; Net Units Sold is published separately against the original order date.

DECLARE start_date DATE DEFAULT DATE_SUB(CURRENT_DATE('America/Chicago'), INTERVAL 27 DAY);
DECLARE end_date   DATE DEFAULT CURRENT_DATE('America/Chicago');

WITH purchase_events AS (
  SELECT
    PARSE_DATE('%Y%m%d', event_date)                                     AS order_date,
    event_timestamp,
    user_pseudo_id,
    IFNULL(device.category, '(not set)')                                 AS device_category,
    IFNULL(geo.country, '(not set)')                                     AS country,
    -- Session-scoped (last non-direct click) source / medium, not the first-touch user traffic_source record
    LOWER(IFNULL(session_traffic_source_last_click.manual_campaign.medium, '(none)'))   AS traffic_medium,
    LOWER(IFNULL(session_traffic_source_last_click.manual_campaign.source, '(direct)')) AS traffic_source_name,
    -- Event-param pivot: one scalar subquery per parameter key, never two key predicates in a single row filter
    COALESCE(
      (SELECT MAX(IF(p.key = 'transaction_id', p.value.string_value, NULL)) FROM UNNEST(event_params) AS p),
      ecommerce.transaction_id
    )                                                                    AS transaction_id,
    (SELECT MAX(IF(p.key = 'currency',      p.value.string_value, NULL)) FROM UNNEST(event_params) AS p) AS currency,
    (SELECT MAX(IF(p.key = 'coupon',        p.value.string_value, NULL)) FROM UNNEST(event_params) AS p) AS coupon_code,
    (SELECT MAX(IF(p.key = 'shipping_tier', p.value.string_value, NULL)) FROM UNNEST(event_params) AS p) AS shipping_tier,
    (SELECT MAX(IF(p.key = 'payment_type',  p.value.string_value, NULL)) FROM UNNEST(event_params) AS p) AS payment_method,
    (SELECT MAX(IF(p.key = 'checkout_type', p.value.string_value, NULL)) FROM UNNEST(event_params) AS p) AS checkout_type,
    (SELECT MAX(IF(p.key = 'ga_session_id', p.value.int_value,    NULL)) FROM UNNEST(event_params) AS p) AS ga_session_id,
    items
  FROM `your-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', start_date)
                          AND FORMAT_DATE('%Y%m%d', end_date)
    AND event_name = 'purchase'
),

-- Transaction de-duplication: purchase is serialised once per transaction ID, so confirmation-page
-- reloads, back navigation and client-side retries collapse to the first hit per transaction.
deduplicated_orders AS (
  SELECT * EXCEPT (row_num)
  FROM (
    SELECT
      pe.*,
      ROW_NUMBER() OVER (
        PARTITION BY pe.transaction_id
        ORDER BY pe.event_timestamp ASC
      ) AS row_num
    FROM purchase_events AS pe
    WHERE pe.transaction_id IS NOT NULL
      AND TRIM(pe.transaction_id) <> ''
      -- Internal and QA orders are out of scope
      AND NOT REGEXP_CONTAINS(UPPER(pe.transaction_id), r'^(TEST|QA|INT)[-_]')
      AND LOWER(IFNULL(pe.payment_method, '')) NOT IN ('test', 'test_card', 'internal')
  )
  WHERE row_num = 1
),

order_items AS (
  SELECT
    o.order_date,
    o.transaction_id,
    o.device_category,
    o.country,
    o.currency,
    IFNULL(o.coupon_code, '(none)')                AS coupon_code,
    IFNULL(o.shipping_tier, '(not set)')           AS shipping_tier,
    IFNULL(o.payment_method, '(not set)')          AS payment_method,
    IFNULL(o.checkout_type, '(not set)')           AS checkout_type,
    CASE
      WHEN o.traffic_medium IN ('cpc', 'ppc', 'paid', 'paidsearch')          THEN 'Paid Search'
      WHEN o.traffic_medium = 'organic'                                      THEN 'Organic Search'
      WHEN o.traffic_medium IN ('email', 'e-mail', 'newsletter')             THEN 'Email'
      WHEN o.traffic_medium IN ('social', 'social-network', 'social-media')  THEN 'Organic Social'
      WHEN o.traffic_medium IN ('display', 'banner', 'cpm')                  THEN 'Display'
      WHEN o.traffic_medium = 'affiliate'                                    THEN 'Affiliates'
      WHEN o.traffic_medium = 'referral'                                     THEN 'Referral'
      WHEN o.traffic_medium IN ('(none)', '') AND o.traffic_source_name IN ('(direct)', '') THEN 'Direct'
      ELSE 'Other'
    END                                            AS default_channel_grouping,
    IFNULL(it.item_category, '(not set)')          AS item_category,
    IFNULL(it.item_brand, '(not set)')             AS item_brand,
    IFNULL(it.item_name, '(not set)')              AS item_name,
    IFNULL(it.item_variant, '(not set)')           AS item_variant,
    it.item_id,
    it.quantity                                    AS item_quantity
  FROM deduplicated_orders AS o
  CROSS JOIN UNNEST(o.items) AS it
  -- Missing, zero or negative quantity is a tagging defect and is dropped, never coerced to 1
  WHERE IFNULL(it.quantity, 0) > 0
    -- Non-merchandise lines and bundle parents whose components are transmitted separately
    AND LOWER(IFNULL(it.item_category, '')) NOT IN ('gift wrap', 'shipping insurance', 'donation', 'gift card')
    AND NOT REGEXP_CONTAINS(UPPER(IFNULL(it.item_id, '')), r'^(BUNDLE|GWRAP|GIFTCARD|DONATE)[-_]')
)

SELECT
  order_date,
  item_category,
  item_brand,
  default_channel_grouping,
  device_category,
  checkout_type,
  SUM(item_quantity)                                                AS units_sold,
  COUNT(DISTINCT transaction_id)                                    AS orders,
  COUNT(*)                                                          AS item_lines,
  ROUND(SAFE_DIVIDE(SUM(item_quantity), COUNT(DISTINCT transaction_id)), 2) AS units_per_order
FROM order_items
GROUP BY
  order_date,
  item_category,
  item_brand,
  default_channel_grouping,
  device_category,
  checkout_type
ORDER BY
  order_date DESC,
  units_sold DESC;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Metric : Units Sold — gross item quantity on completed purchase transactions
-- Grain  : one row per reporting day x merchandising / channel breakdown
-- Source : AEP ExperienceEvent dataset, eventType = 'commerce.purchases'
--          (quantity lives in the productListItems array, order context in commerce.order;
--          coupon, brand and variant in the tenant field group, checkout type in eVar12)
-- Scope  : GROSS units booked at transaction time. commerce.refunds / returns are NOT netted here;
--          Net Units Sold is published separately against the original order date.

WITH purchase_events AS (
  SELECT
    to_date(from_utc_timestamp(timestamp, 'America/Chicago'))        AS order_date,
    timestamp                                                        AS event_ts,
    _id                                                              AS event_id,
    commerce.order.purchaseID                                        AS transaction_id,
    commerce.order.currencyCode                                      AS currency,
    _tenant.order.couponCode                                         AS coupon_code,
    _experience.analytics.customDimensions.eVars.eVar12              AS checkout_type,
    commerce.order.payments[0].paymentType                           AS payment_method,
    coalesce(device.type, 'unknown')                                 AS device_category,
    coalesce(placeContext.geo.countryCode, '(not set)')              AS country,
    lower(coalesce(web.webReferrer.type, 'direct'))                  AS referrer_type,
    coalesce(marketing.trackingCode, '(none)')                       AS tracking_code,
    productListItems                                                 AS product_list_items
  FROM your_experience_event_dataset
  WHERE eventType = 'commerce.purchases'
    AND commerce.purchases.value > 0
    -- Partition-pruning filter on the ingest timestamp column
    AND timestamp >= to_timestamp('2026-08-21 00:00:00')
    AND timestamp <  to_timestamp('2026-09-18 00:00:00')
    AND commerce.order.purchaseID IS NOT NULL
    AND trim(commerce.order.purchaseID) <> ''
    AND upper(commerce.order.purchaseID) NOT RLIKE '^(TEST|QA|INT)[-_]'
    AND lower(coalesce(commerce.order.payments[0].paymentType, '')) NOT IN ('test', 'test_card', 'internal')
),

-- Transaction de-duplication: confirmation reloads and client-side retries resolve to the
-- first event received per purchase ID.
deduplicated_orders AS (
  SELECT
    order_date,
    transaction_id,
    currency,
    coalesce(coupon_code, '(none)')          AS coupon_code,
    coalesce(checkout_type, '(not set)')     AS checkout_type,
    coalesce(payment_method, '(not set)')    AS payment_method,
    device_category,
    country,
    CASE
      WHEN tracking_code <> '(none)'                 THEN 'Campaign'
      WHEN referrer_type IN ('search', 'search_paid') THEN 'Search'
      WHEN referrer_type = 'social'                   THEN 'Organic Social'
      WHEN referrer_type = 'email'                    THEN 'Email'
      WHEN referrer_type = 'external'                 THEN 'Referral'
      WHEN referrer_type IN ('direct', 'internal')    THEN 'Direct'
      ELSE 'Other'
    END                                      AS default_channel_grouping,
    product_list_items
  FROM (
    SELECT
      pe.*,
      ROW_NUMBER() OVER (
        PARTITION BY pe.transaction_id
        ORDER BY pe.event_ts ASC
      ) AS row_num
    FROM purchase_events AS pe
  ) AS ranked
  WHERE row_num = 1
),

order_items AS (
  SELECT
    o.order_date,
    o.transaction_id,
    o.device_category,
    o.country,
    o.currency,
    o.coupon_code,
    o.checkout_type,
    o.payment_method,
    o.default_channel_grouping,
    coalesce(item.productCategory, '(not set)')  AS item_category,
    coalesce(item._tenant.productBrand, '(not set)')   AS item_brand,
    coalesce(item.name, '(not set)')                   AS item_name,
    coalesce(item._tenant.productVariant, '(not set)') AS item_variant,
    item.SKU                                     AS item_sku,
    item.quantity                                AS item_quantity
  FROM deduplicated_orders AS o
  LATERAL VIEW explode(o.product_list_items) exploded_items AS item
  -- Missing, zero or negative quantity is a tagging defect and is dropped, never coerced to 1
  WHERE coalesce(item.quantity, 0) > 0
    -- Non-merchandise lines and bundle parents whose components are transmitted separately
    AND lower(coalesce(item.productCategory, '')) NOT IN ('gift wrap', 'shipping insurance', 'donation', 'gift card')
    AND upper(coalesce(item.SKU, '')) NOT RLIKE '^(BUNDLE|GWRAP|GIFTCARD|DONATE)[-_]'
)

SELECT
  order_date,
  item_category,
  item_brand,
  default_channel_grouping,
  device_category,
  checkout_type,
  SUM(item_quantity)                                              AS units_sold,
  COUNT(DISTINCT transaction_id)                                  AS orders,
  COUNT(*)                                                        AS item_lines,
  ROUND(SUM(item_quantity) / NULLIF(COUNT(DISTINCT transaction_id), 0), 2) AS units_per_order
FROM order_items
GROUP BY
  order_date,
  item_category,
  item_brand,
  default_channel_grouping,
  device_category,
  checkout_type
ORDER BY
  order_date DESC,
  units_sold DESC;

Source Data

Digital Analytics (GA4)

Dependencies

Prerequisite: List the dependencies required for this Metric

Events:

purchase

Dimensions:

item-categorycheckout-type

Report Attributes

Item Category Item Name Item Brand Item Variant Coupon Code Shipping Tier Checkout Type Payment Method Device Category Default Channel Grouping Country / Region

Recommended Reporting Views

Views
Weekly Trade ReviewCart & Checkout Funnel DashboardMerchandising Performance ReviewPromotion & Discount EffectivenessDemand & Replenishment Planning

Segment Eligibility

true

Governance

Data SensitivityInternal

Derived KPIs

Governance

Created bydevyendarm
Created on9/17/2026
Last modified bydevyendarm
Last modified on9/18/2026
Version1.0.0
Validation stateqa passed
StatusPUBLISHED

GitHub

View related Pull Requestcatalog/metrics/units-sold.yml

Community Discussion