← Back to KPIs

Split Shipment Rate

Also known as: Split Order Rate, Multi-Shipment Order Rate, Order Split Rate, Split Fulfillment Rate, Shipments per Order Split %

OperationskpiE-commerce: Post-Purchase Logistics & FulfillmentE-commerce

Split Shipment Rate is the share of shipped orders that reached the customer in two or more separate shipments, measured from order management shipment records and joined to the GA4 purchase transaction_id so it can be cut by the channel, device and campaign that produced the order. It shows how fragmented fulfillment is across nodes, backorders and drop-ship vendors, which drives parcel cost per order, delivery-promise accuracy and 'where is my order' contact volume.

Business Use Case

1️⃣ The fulfillment network planning team reviews split rate by distribution-center and item-category each week; when a node's controllable split rate rises more than 2 points above its 8-week baseline, they rebalance safety stock of the most frequently co-purchased SKUs into that node so baskets can ship complete.,2️⃣ Finance multiplies the monthly count of split orders by the incremental parcel cost per extra shipment; when that cost moves the fulfillment cost per order above plan, they propose a revised free-shipping threshold or a consolidation hold window to the pricing committee.,3️⃣ Merchandising checks split rate by fulfillment source; when drop-ship or backordered lines account for most splits in a category, they add a 'ships separately' message on the product page and cart, or move high-attach SKUs to owned inventory at the next buy.,4️⃣ Customer Service leadership compares 'where is my order' contact rate for split versus single-shipment orders; when split orders generate materially more contacts, they request a proactive multi-shipment notification in the order confirmation and shipping emails.

Business Criticality

Medium

Journey / Navigation Area

Generic

Owning Function

Operations

Scope

Event

Formula

(Orders with COUNT(DISTINCT shipment_id) >= 2 / Orders with COUNT(DISTINCT shipment_id) >= 1, where the order's first ship-confirm date falls in the period) × 100

Calculation Notes

1️⃣ Unit of analysis is the order (OMS order_id = GA4 purchase transaction_id). The denominator is every order with at least one ship-confirmed carrier shipment whose first ship-confirm timestamp falls in the reporting period; the numerator is the subset with two or more distinct shipment_ids (tracking numbers or OMS fulfillment IDs, deduplicated so a relabelled parcel is not counted twice).,2️⃣ Maturity window: an order is assigned to the period of its first shipment but can only be finalised once all lines are shipped, cancelled or closed. Publish daily and weekly values as provisional and restate them for 14 days (or until every line in the cohort is closed, whichever comes first); monthly values are locked after that window.,3️⃣ Exclude shipments that are not part of original fulfillment: replacement and reshipments for lost or damaged parcels, exchange outbound shipments, return labels and warranty sends. Identify them by the OMS shipment reason code, not by timing, otherwise a reshipment inflates the split count.,4️⃣ Exclude orders with no carrier shipment: buy-online-pick-up-in-store, curbside, ship-to-store handed over at the counter, digital goods, e-gift cards and cancelled-before-ship orders. Orders that mix a pickup line with a shipped line stay in scope and are evaluated on their shipped lines only.,5️⃣ Customer-driven splits (ship-to-multiple-addresses, customer-selected 'ship items as they become available', pre-order lines with a later release date) stay in the KPI but must carry a split_reason of customer_requested so operations can report the controllable rate (network, inventory and drop-ship splits) separately from the headline rate.,6️⃣ Stitching: the GA4 purchase is used only for attribution and device breakdowns. Deduplicate purchases on transaction_id (keep the first event), and keep OMS orders with no GA4 match (call-centre, marketplace, consent-denied traffic) in the base; report the GA4 match rate next to the KPI so digital cuts are read against their coverage. GA4 session timeout (30 minutes) has no effect on the base.,7️⃣ Timestamps come from the OMS ship-confirm event converted to the store's reporting time zone; a zero-order day returns NULL rather than 0%, and test, fraud-cancelled and internal employee orders are filtered by the OMS order-type flag before counting.

Technical Details

Measure TypePercentage
Aggregation WindowDaily, Weekly, Monthly

Events

PlatformEvent
Google Analytics 4purchase
Adobepurchase

Data Mappings

W3 Data Layer
{
  "event": [
    {
      "eventInfo": {
        "eventName": "purchase",
        "eventAction": "order_confirmation"
      }
    }
  ],
  "transaction": {
    "transactionID": "ORD-100245871",
    "total": {
      "basePrice": 184.97,
      "currency": "USD",
      "shipping": 0,
      "priceWithTax": 199.77
    },
    "attributes": {
      "plannedShipmentCount": 2,
      "splitShipmentPlanned": true,
      "splitReason": "inventory_node",
      "fulfillmentMethod": "ship_to_home",
      "shippingTier": "standard",
      "deliveryRegion": "US-TX"
    },
    "item": [
      {
        "productInfo": {
          "productID": "SKU-44821",
          "productName": "Trail Running Shoe"
        },
        "category": {
          "primaryCategory": "Footwear"
        },
        "quantity": 1,
        "price": {
          "basePrice": 129.99
        },
        "attributes": {
          "fulfillmentMethod": "ship_to_home",
          "fulfillmentSource": "owned_dc",
          "fulfillmentNode": "DC-DAL-01",
          "shipmentGroup": "1"
        }
      },
      {
        "productInfo": {
          "productID": "SKU-90317",
          "productName": "Hydration Vest"
        },
        "category": {
          "primaryCategory": "Accessories"
        },
        "quantity": 1,
        "price": {
          "basePrice": 54.98
        },
        "attributes": {
          "fulfillmentMethod": "ship_to_home",
          "fulfillmentSource": "drop_ship",
          "fulfillmentNode": "VENDOR-2231",
          "shipmentGroup": "2"
        }
      }
    ]
  }
}
GA4 Data Layer
{
  "event": "purchase",
  "ecommerce": {
    "transaction_id": "ORD-100245871",
    "value": 184.97,
    "tax": 14.8,
    "shipping": 0,
    "currency": "USD",
    "shipping_tier": "standard",
    "items": [
      {
        "item_id": "SKU-44821",
        "item_name": "Trail Running Shoe",
        "item_category": "Footwear",
        "price": 129.99,
        "quantity": 1,
        "fulfillment_method": "ship_to_home",
        "fulfillment_source": "owned_dc",
        "fulfillment_node": "DC-DAL-01",
        "shipment_group": "1"
      },
      {
        "item_id": "SKU-90317",
        "item_name": "Hydration Vest",
        "item_category": "Accessories",
        "price": 54.98,
        "quantity": 1,
        "fulfillment_method": "ship_to_home",
        "fulfillment_source": "drop_ship",
        "fulfillment_node": "VENDOR-2231",
        "shipment_group": "2"
      }
    ]
  },
  "planned_shipment_count": 2,
  "split_shipment_planned": "true",
  "split_reason": "inventory_node",
  "fulfillment_method": "ship_to_home",
  "delivery_region": "US-TX"
}
Adobe Client Data Layer
{
  "event": "purchase",
  "eventInfo": {
    "eventName": "purchase"
  },
  "transaction": {
    "purchaseID": "ORD-100245871",
    "revenue": 184.97,
    "currency": "USD",
    "shippingTier": "standard",
    "plannedShipmentCount": 2,
    "splitShipmentPlanned": true,
    "splitReason": "inventory_node",
    "fulfillmentMethod": "ship_to_home",
    "deliveryRegion": "US-TX"
  },
  "product": [
    {
      "productID": "SKU-44821",
      "name": "Trail Running Shoe",
      "category": "Footwear",
      "quantity": 1,
      "price": 129.99,
      "fulfillmentMethod": "ship_to_home",
      "fulfillmentSource": "owned_dc",
      "fulfillmentNode": "DC-DAL-01",
      "shipmentGroup": "1"
    },
    {
      "productID": "SKU-90317",
      "name": "Hydration Vest",
      "category": "Accessories",
      "quantity": 1,
      "price": 54.98,
      "fulfillmentMethod": "ship_to_home",
      "fulfillmentSource": "drop_ship",
      "fulfillmentNode": "VENDOR-2231",
      "shipmentGroup": "2"
    }
  ]
}
XDM Mapping
{
  "eventType": "commerce.purchases",
  "commerce": {
    "purchases": {
      "value": 1
    },
    "order": {
      "purchaseID": "ORD-100245871",
      "priceTotal": 184.97,
      "currencyCode": "USD"
    },
    "shipping": {
      "shippingMethod": "standard"
    }
  },
  "productListItems": [
    {
      "SKU": "SKU-44821",
      "name": "Trail Running Shoe",
      "productCategories": [
        {
          "categoryName": "Footwear"
        }
      ],
      "quantity": 1,
      "priceTotal": 129.99,
      "_experience": {
        "analytics": {
          "customDimensions": {
            "eVars": {
              "eVar41": "owned_dc",
              "eVar42": "DC-DAL-01",
              "eVar43": "1"
            }
          }
        }
      }
    },
    {
      "SKU": "SKU-90317",
      "name": "Hydration Vest",
      "productCategories": [
        {
          "categoryName": "Accessories"
        }
      ],
      "quantity": 1,
      "priceTotal": 54.98,
      "_experience": {
        "analytics": {
          "customDimensions": {
            "eVars": {
              "eVar41": "drop_ship",
              "eVar42": "VENDOR-2231",
              "eVar43": "2"
            }
          }
        }
      }
    }
  ],
  "_experience": {
    "analytics": {
      "customDimensions": {
        "eVars": {
          "eVar38": "ship_to_home",
          "eVar39": "2",
          "eVar40": "inventory_node",
          "eVar44": "US-TX"
        }
      }
    }
  }
}

SQL Queries

Google Analytics 4 (BigQuery)
-- ==========================================
-- 1. GOOGLE BIGQUERY SQL
-- ==========================================
-- Split Shipment Rate = orders with >= 2 distinct original-fulfillment shipments / all orders with >= 1 ship-confirmed carrier shipment
-- Base and numerator come from the OMS shipment table; the GA4 purchase is joined on transaction_id for attribution only.
DECLARE end_date DATE DEFAULT CURRENT_DATE('America/New_York');
DECLARE start_date DATE DEFAULT DATE_SUB(end_date, INTERVAL 29 DAY);
DECLARE purchase_lookback_days INT64 DEFAULT 60;

-- Orders left after the OMS order-type exclusions: test, employee, fraud-cancelled and cancelled-before-ship
WITH eligible_orders AS (
  SELECT o.order_id
  FROM `your-project.oms.orders` AS o
  WHERE o.is_test_order = FALSE
    AND o.is_employee_order = FALSE
    AND o.order_status NOT IN ('fraud_cancelled', 'cancelled_before_ship')
),
-- Ship-confirmed carrier shipments that belong to original fulfillment: drops BOPIS, curbside, ship-to-store and digital, plus reships, exchanges, return labels and warranty sends
original_shipments AS (
  SELECT
    s.order_id,
    COALESCE(s.shipment_id, s.tracking_number) AS shipment_key,
    s.ship_confirm_ts,
    s.split_reason
  FROM `your-project.oms.shipments` AS s
  JOIN eligible_orders AS e
    ON e.order_id = s.order_id
  WHERE s.shipment_status = 'ship_confirmed'
    AND s.delivery_mode = 'carrier'
    AND s.shipment_reason_code = 'original_fulfillment'
),
-- One row per order: deduplicated shipment count, first ship-confirm timestamp and the customer-requested split flag
order_shipments AS (
  SELECT
    order_id,
    COUNT(DISTINCT shipment_key) AS shipment_count,
    MIN(ship_confirm_ts) AS first_ship_confirm_ts,
    LOGICAL_OR(split_reason = 'customer_requested') AS is_customer_requested_split
  FROM original_shipments
  GROUP BY order_id
),
-- Orders assigned to the reporting period by their first ship-confirm date in the store reporting time zone
shipped_order_cohort AS (
  SELECT
    order_id,
    shipment_count,
    is_customer_requested_split,
    DATE(first_ship_confirm_ts, 'America/New_York') AS first_ship_date
  FROM order_shipments
  WHERE DATE(first_ship_confirm_ts, 'America/New_York') BETWEEN start_date AND end_date
),
-- First purchase per transaction_id, scanned from before the cohort starts because an order can be placed well before it ships
ga4_purchases AS (
  SELECT
    transaction_id,
    device_category,
    traffic_source
  FROM (
    SELECT
      ecommerce.transaction_id AS transaction_id,
      device.category AS device_category,
      CONCAT(IFNULL(traffic_source.source, '(direct)'), ' / ', IFNULL(traffic_source.medium, '(none)')) AS traffic_source,
      ROW_NUMBER() OVER (PARTITION BY ecommerce.transaction_id ORDER BY event_timestamp) AS purchase_rank
    FROM `your-project.analytics_123456789.events_*`
    WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(start_date, INTERVAL purchase_lookback_days DAY))
                            AND FORMAT_DATE('%Y%m%d', end_date)
      AND event_name = 'purchase'
      AND ecommerce.transaction_id IS NOT NULL
      AND ecommerce.transaction_id NOT IN ('', '(not set)')
  )
  WHERE purchase_rank = 1
)
-- Headline rate, the controllable rate that excludes customer-requested splits, and the GA4 match rate the digital cuts must be read against
SELECT
  c.first_ship_date,
  COALESCE(g.device_category, '(no digital match)') AS device_category,
  COALESCE(g.traffic_source, '(no digital match)') AS traffic_source,
  COUNT(*) AS shipped_orders,
  COUNTIF(c.shipment_count >= 2) AS split_orders,
  COUNTIF(c.shipment_count >= 2 AND NOT c.is_customer_requested_split) AS controllable_split_orders,
  ROUND(SAFE_DIVIDE(COUNTIF(c.shipment_count >= 2), COUNT(*)) * 100, 2) AS split_shipment_rate_pct,
  ROUND(SAFE_DIVIDE(COUNTIF(c.shipment_count >= 2 AND NOT c.is_customer_requested_split), COUNT(*)) * 100, 2) AS controllable_split_rate_pct,
  ROUND(SAFE_DIVIDE(SUM(COUNTIF(g.transaction_id IS NOT NULL)) OVER (PARTITION BY c.first_ship_date), SUM(COUNT(*)) OVER (PARTITION BY c.first_ship_date)) * 100, 2) AS ga4_match_rate_pct
FROM shipped_order_cohort AS c
LEFT JOIN ga4_purchases AS g
  ON g.transaction_id = c.order_id
GROUP BY 1, 2, 3
ORDER BY first_ship_date, shipped_orders DESC;
Adobe Experience Platform (AEP Query Service)
-- ==========================================
-- 2. ADOBE AEP QUERY SERVICE SQL
-- ==========================================
-- Split Shipment Rate = orders with >= 2 distinct original-fulfillment shipments / all orders with >= 1 ship-confirmed carrier shipment
-- OMS order and shipment records are ingested as backend datasets; the web purchase (XDM commerce.purchases) is joined on purchaseID for attribution only.
-- Reporting window: the last 30 days of first ship-confirms; the purchase scan reaches 60 days further back because an order can be placed well before it ships.
-- Orders left after the OMS order-type exclusions: test, employee, fraud-cancelled and cancelled-before-ship
WITH eligible_orders AS (
  SELECT o.order_id
  FROM oms_orders_backend_dataset AS o
  WHERE o.is_test_order = false
    AND o.is_employee_order = false
    AND o.order_status NOT IN ('fraud_cancelled', 'cancelled_before_ship')
),
-- Ship-confirmed carrier shipments that belong to original fulfillment: drops BOPIS, curbside, ship-to-store and digital, plus reships, exchanges, return labels and warranty sends
original_shipments AS (
  SELECT
    s.order_id,
    COALESCE(s.shipment_id, s.tracking_number) AS shipment_key,
    s.ship_confirm_ts,
    s.split_reason
  FROM oms_shipments_backend_dataset AS s
  JOIN eligible_orders AS e
    ON e.order_id = s.order_id
  WHERE s.shipment_status = 'ship_confirmed'
    AND s.delivery_mode = 'carrier'
    AND s.shipment_reason_code = 'original_fulfillment'
),
-- One row per order: deduplicated shipment count, first ship-confirm timestamp and the customer-requested split flag
order_shipments AS (
  SELECT
    order_id,
    COUNT(DISTINCT shipment_key) AS shipment_count,
    MIN(ship_confirm_ts) AS first_ship_confirm_ts,
    MAX(CASE WHEN split_reason = 'customer_requested' THEN 1 ELSE 0 END) AS is_customer_requested_split
  FROM original_shipments
  GROUP BY order_id
),
-- Orders assigned to the reporting period by their first ship-confirm date in the store reporting time zone
shipped_order_cohort AS (
  SELECT
    order_id,
    shipment_count,
    is_customer_requested_split,
    to_date(from_utc_timestamp(first_ship_confirm_ts, 'America/New_York')) AS first_ship_date
  FROM order_shipments
  WHERE to_date(from_utc_timestamp(first_ship_confirm_ts, 'America/New_York'))
        BETWEEN date_sub(current_date(), 29) AND current_date()
),
-- First purchase per purchaseID, scanned from before the cohort starts because an order can be placed well before it ships
web_purchases AS (
  SELECT
    purchase_id,
    device_type,
    tracking_code
  FROM (
    SELECT
      commerce.order.purchaseID AS purchase_id,
      device.type AS device_type,
      COALESCE(marketing.trackingCode, '(direct)') AS tracking_code,
      ROW_NUMBER() OVER (PARTITION BY commerce.order.purchaseID ORDER BY timestamp) AS purchase_rank
    FROM web_events_dataset
    WHERE timestamp >= CAST(date_sub(current_date(), 89) AS timestamp)
      AND timestamp < CAST(date_add(current_date(), 1) AS timestamp)
      AND eventType = 'commerce.purchases'
      AND commerce.purchases.value = 1
      AND commerce.order.purchaseID IS NOT NULL
      AND commerce.order.purchaseID <> ''
  ) AS ranked
  WHERE purchase_rank = 1
)
-- Headline rate, the controllable rate that excludes customer-requested splits, and the digital match rate the digital cuts must be read against
SELECT
  c.first_ship_date,
  COALESCE(w.device_type, '(no digital match)') AS device_type,
  COALESCE(w.tracking_code, '(no digital match)') AS tracking_code,
  COUNT(*) AS shipped_orders,
  SUM(CASE WHEN c.shipment_count >= 2 THEN 1 ELSE 0 END) AS split_orders,
  SUM(CASE WHEN c.shipment_count >= 2 AND c.is_customer_requested_split = 0 THEN 1 ELSE 0 END) AS controllable_split_orders,
  ROUND(100.0 * SUM(CASE WHEN c.shipment_count >= 2 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 2) AS split_shipment_rate_pct,
  ROUND(100.0 * SUM(CASE WHEN c.shipment_count >= 2 AND c.is_customer_requested_split = 0 THEN 1 ELSE 0 END) / NULLIF(COUNT(*), 0), 2) AS controllable_split_rate_pct,
  ROUND(100.0 * SUM(SUM(CASE WHEN w.purchase_id IS NOT NULL THEN 1 ELSE 0 END)) OVER (PARTITION BY c.first_ship_date) / NULLIF(SUM(COUNT(*)) OVER (PARTITION BY c.first_ship_date), 0), 2) AS digital_match_rate_pct
FROM shipped_order_cohort AS c
LEFT JOIN web_purchases AS w
  ON w.purchase_id = c.order_id
GROUP BY c.first_ship_date, COALESCE(w.device_type, '(no digital match)'), COALESCE(w.tracking_code, '(no digital match)')
ORDER BY c.first_ship_date, shipped_orders DESC;

Source Data

Digital + OMS (Order Management) shipment records

Dependencies

Prerequisite: List the dependencies required for this KPI

Events:

purchasedelivery-confirmation

Metrics:

shipment-volume

Dimensions:

number-of-shipmentsfulfillment-methodorder-status

KPIs:

Report Attributes

distribution-center fulfillment-method carrier shipping-tier item-category delivery-region device-category traffic-source

Recommended Reporting Views

Views
Fulfillment & Delivery PerformancePost-Purchase Experience ScorecardCost-to-Serve by OrderWeekly Operations Review

Segment Eligibility

true

Governance

Data SensitivityInternal

Governance

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

GitHub

View related Pull Requestcatalog/kpis/split-shipment-rate.yml

Community Discussion