Revenue
Avoiding revenue fanout when joining orders to order items
How much revenue do orders bring, how many items are in an order, and what is the average order value, without counting any order twice?
Short answer
Trust: InferredSum revenue at the grain where it lives. Order totals belong to the orders table; summed after a join to items, each total repeats once per item. Use item lines (price times quantity) only for item-level revenue, and keep the two numbers apart. In this project the item revenue also leaves quantity out.
Decision it supports: Which revenue number to report, and whether average order value can be trusted.
What to measure
Order revenue
Trust: Source-definedThe sum of order totals, one row per order.
Defined as transactions.transactions_value
SUM(transactions.tr_total)Trust: Translated
Show the SQL: Order revenue
SUM(transactions.tr_total)
Translated to Redshift SQL from event_transactions.view.lookml L37-40, L65-67. Not run against a warehouse.
Show the original LookML: event_transactions.view.lookml L65-67
type: count_distinct sql: ${order_id} detail: transaction_detail* - measure: transactions_value type: sum sql: ${transaction_value} sets: transaction_detail: - order_id
dating-platform, event_transactions.view.lookml, lines 65-67
Orders
Trust: Source-definedDistinct order ids.
Defined as transactions.transactions_count
COUNT(DISTINCT transactions.tr_orderid)Trust: Translated
Show the SQL: Orders
COUNT(DISTINCT transactions.tr_orderid)
Translated to Redshift SQL from event_transactions.view.lookml L32-34, L61-64. Not run against a warehouse.
Show the original LookML: event_transactions.view.lookml L61-64
sql: ${TABLE}.tr_tstamp - dimension: order_items sql: ${order_id} - measure: transactions_count type: count_distinct sql: ${order_id} detail: transaction_detail* - measure: transactions_value type: sum sql: ${transaction_value}
dating-platform, event_transactions.view.lookml, lines 61-64
Item sales value
Trust: Source-definedThe sum of item prices. Quantity is not multiplied in.
Defined as transaction_items.sales_value
SUM(transaction_items.ti_price)Trust: Translated
Show the SQL: Item sales value
SUM(transaction_items.ti_price)
Translated to Redshift SQL from purchase_items.view.lookml L27-30, L42-44. Not run against a warehouse.
Show the original LookML: purchase_items.view.lookml L42-44
- measure: items_bought_count type: sum sql: ${quantity} - measure: sales_value type: sum sql: ${price} - measure: orders_count type: count_distinct sql: ${order_id}
dating-platform, purchase_items.view.lookml, lines 42-44
Items bought
Trust: Source-definedThe sum of quantities.
Defined as transaction_items.items_bought_count
SUM(transaction_items.ti_quantity)Trust: Translated
Show the SQL: Items bought
SUM(transaction_items.ti_quantity)
Translated to Redshift SQL from purchase_items.view.lookml L31-33, L39-41. Not run against a warehouse.
Show the original LookML: purchase_items.view.lookml L39-41
- measure: distinct_skus_bought type: count_distinct sql: ${sku} - measure: items_bought_count type: sum sql: ${quantity} - measure: sales_value type: sum sql: ${price}
dating-platform, purchase_items.view.lookml, lines 39-41
Average order value
Trust: Source-definedItem sales value over distinct orders, so it inherits the missing quantity.
Defined as transaction_items.average_order_value
SUM(transaction_items.ti_price) / NULLIF(COUNT(DISTINCT transaction_items.ti_orderid),0)::REALTrust: Translated
Show the SQL: Average order value
SUM(transaction_items.ti_price) / NULLIF(COUNT(DISTINCT transaction_items.ti_orderid),0)::REAL
Translated to Redshift SQL from purchase_items.view.lookml L19-20, L27-30, L42-48, L53-56. Not run against a warehouse.
Show the original LookML: purchase_items.view.lookml L53-56
type: number decimals: 2 sql: ${items_bought_count} / NULLIF(${orders_count},0)::REAL - measure: average_order_value type: number decimals: 2 sql: ${sales_value} / NULLIF(${orders_count},0)::REAL - measure: count type: count sets:
dating-platform, purchase_items.view.lookml, lines 53-56
Revenue
Trust: InferredOrder totals and summed item prices are two different revenue numbers. The project never says which one is revenue.
Line revenue
Trust: SuggestedPrice times quantity per line. Not defined in the source.
The query
Order revenue by month
Trust: SuggestedOrder revenue and orders per month from the orders table alone, so no join can repeat a total. It depends on the orders derived table, which may not build as written (see What can change the answer). The project's own revenue tiles read a view that is missing, so this field combination is ours.
Show the SQL: order revenue by month
SELECT DATE_TRUNC('month', transactions.tr_tstamp) AS occurred_month, SUM(transactions.tr_total) AS transactions_value, COUNT(DISTINCT transactions.tr_orderid) AS transactions_countFROM transactions -- derived table, defined in the sourceGROUP BY 1ORDER BY occurred_month
Suggested query. Fields translated to Redshift SQL from event_tracker.model.lookml L18 and event_transactions.view.lookml L32-34, L37-40, L51-58, L61-67. Not run against a warehouse.
The data behind it
Trust: Inferred- Entities
- order (transaction), order item
- Grain
- Orders: meant to be one row per order id, though the derived table also groups by user and session. Items: one row per distinct item line.
- Time
occurredon orders. Items have no timestamp of their own.- Key fields
transactions.order_idtransactions.transaction_valuetransaction_items.order_idtransaction_items.pricetransaction_items.quantity
What can change the answer 4
Caution: Item revenue ignores quantity
Trust: Source-definedsales_value is SUM(price). Quantity is summed separately and never multiplied in.
What goes wrong: An order of three units at 10 reports 10 of sales value, and average order value drops with it.
Show the SQL: sales value
SUM(transaction_items.ti_price)
Translated to Redshift SQL from purchase_items.view.lookml L27-30, L42-44. Not run against a warehouse.
Show the original LookML: purchase_items.view.lookml L42-44
- measure: items_bought_count type: sum sql: ${quantity} - measure: sales_value type: sum sql: ${price} - measure: orders_count type: count_distinct sql: ${order_id}
dating-platform, purchase_items.view.lookml, lines 42-44
Caution: Identical lines collapse
Trust: Source-definedGrouping by all six columns merges two identical lines of one order into a single row.
Read the guide: Primary keys and counts
Source: purchase_items.view.lookml L12-14
ti_price, ti_quantity FROM ${TABLE}.events_atomic WHERE event = 'transaction_item' GROUP BY 1,2,3,4,5,5,6 ORDER BY ti_orderid sql_trigger_value: SELECT MAX(collector_tstamp) FROM ${TABLE}.events_atomic distkey: ti_orderid sortkeys: ti_orderid
dating-platform, purchase_items.view.lookml, lines 12-14
Caution: The orders table groups by an aggregate
Trust: Source-definedGROUP BY 1,2,3,4,5,5,6,7,8,9,10 includes column 10, which is MIN(collector_tstamp). Most warehouses reject this, so the table may not build.
Source: event_transactions.view.lookml L14-18
tr_city, tr_state, tr_country, MIN(collector_tstamp) AS tr_tstamp FROM ${TABLE}.events_atomic WHERE event = 'transaction' GROUP BY 1,2,3,4,5,5,6,7,8,9,10 ORDER BY tr_orderid sql_trigger_value: SELECT MAX(collector_tstamp) FROM ${TABLE}.events_atomic distkey: domain_userid sortkeys:
dating-platform, event_transactions.view.lookml, lines 14-18
Note: Two explores named transactions
Trust: Source-definedThe enterprise model has a different transactions explore over a missing view. Dashboards can resolve to either.
Read the guide: Missing tables and stale references
Source: event_tracker.model.lookml L18-22
- join: visitors sql_on: sessions.domain_userid = visitors.domain_userid- explore: visitors- explore: transactions- explore: transaction_items joins: - join: transactions sql_foreign_key: transaction_items.ti_orderid- explore: session_apps
dating-platform, event_tracker.model.lookml, lines 18-22
How the real implementation does it
transactions
Trust: Source-definedOrders come from transaction events, one row per order with its first timestamp.
Source: event_transactions.view.lookml L1-23
- view: transactions derived_table: sql: | SELECT domain_userid, domain_sessionidx, tr_orderid, tr_affiliation, tr_total, tr_tax, tr_city, tr_state, tr_country, MIN(collector_tstamp) AS tr_tstamp FROM ${TABLE}.events_atomic WHERE event = 'transaction' GROUP BY 1,2,3,4,5,5,6,7,8,9,10 ORDER BY tr_orderid sql_trigger_value: SELECT MAX(collector_tstamp) FROM ${TABLE}.events_atomic distkey: domain_userid sortkeys: - domain_sessionidx - tr_orderid fields: - dimension: user_id sql: ${TABLE}.domain_userid
dating-platform, event_transactions.view.lookml, lines 1-23
transaction_items
Trust: Source-definedItems come from transaction_item events, grouped by every column.
Source: purchase_items.view.lookml L1-14
- view: transaction_items derived_table: sql: | SELECT ti_orderid, ti_category, ti_sku, ti_name, ti_price, ti_quantity FROM ${TABLE}.events_atomic WHERE event = 'transaction_item' GROUP BY 1,2,3,4,5,5,6 ORDER BY ti_orderid sql_trigger_value: SELECT MAX(collector_tstamp) FROM ${TABLE}.events_atomic distkey: ti_orderid sortkeys: ti_orderid
dating-platform, purchase_items.view.lookml, lines 1-14
transaction_items
Trust: Source-definedItem sales value sums price only.
Show the SQL: item sales value
SUM(transaction_items.ti_price)
Translated to Redshift SQL from purchase_items.view.lookml L27-30, L42-44. Not run against a warehouse.
Show the original LookML: purchase_items.view.lookml L42-44
- measure: items_bought_count type: sum sql: ${quantity} - measure: sales_value type: sum sql: ${price} - measure: orders_count type: count_distinct sql: ${order_id}
dating-platform, purchase_items.view.lookml, lines 42-44
event_tracker
Trust: InferredThe item explore joins orders on the order id column. Items to orders is many-to-one, but no relationship is declared.
Source: event_tracker.model.lookml L21-22
- explore: transactions- explore: transaction_items joins: - join: transactions sql_foreign_key: transaction_items.ti_orderid- explore: session_apps
dating-platform, event_tracker.model.lookml, lines 21-22
Other ways to measure it
Define line revenue
Trust: SuggestedMultiply price by quantity per line and sum that per order. Average order value can then use the same numerator.
Show the suggested SQL: line revenue per order
SELECT ti_orderid, SUM(ti_price * ti_quantity) AS line_revenue, SUM(ti_quantity) AS items_boughtFROM transaction_items -- the derived table defined in the sourceGROUP BY ti_orderid
Join items only after aggregating them
Trust: SuggestedWhen a report needs order totals and item figures side by side, bring items to one row per order first, then join. The join cannot repeat an order total.
Show the suggested SQL: orders with item figures
WITH items AS ( SELECT ti_orderid, SUM(ti_price * ti_quantity) AS line_revenue FROM transaction_items -- the derived table defined in the source GROUP BY ti_orderid)SELECT SUM(t.tr_total) AS order_revenue, SUM(i.line_revenue) AS line_revenueFROM transactions AS t -- the derived table defined in the sourceLEFT JOIN items AS i ON i.ti_orderid = t.tr_orderid
Keep item lines distinct
Trust: SuggestedSelect the event id with each item so identical lines stay separate, and declare it as the primary key.
