AVG() over a JOINed orders/order_items table gives the wrong "average order value" — correct SQLAlchemy aggregation pattern?
12:19 18 Aug 2026

I have a fairly standard e-commerce schema: one orders row per order, and one order_items row per line item on that order (an order has 1-N items):

sql

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    order_date DATE
);

CREATE TABLE order_items (
    id INTEGER PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    quantity INTEGER NOT NULL,
    unit_price REAL NOT NULL  -- price at time of order, not a live lookup
);

I need "average order value" for a date range: total revenue divided by number of orders. My first attempt joined the two tables and averaged the line total directly:

sql

SELECT AVG(oi.quantity * oi.unit_price) AS avg_order_value
FROM order_items oi
JOIN orders o ON o.id = oi.order_id
WHERE o.order_date BETWEEN :start AND :end;

This runs without error, but the number is wrong once I check it by hand. Minimal example:

orderitemsorder totalA1 × 100100B3 × 1030

Real average order value: (100 + 30) / 2 orders = 65.

What AVG(quantity * unit_price) actually computes: (100 + 10 + 10 + 10) / 4 rows = 32.50.

The JOIN produces one row per line item, so order B — which is worth less — pulls the average toward its own item prices three times just because it has three items, while order A only counts once. The more items an order has, the more it distorts the average away from being "per order."

I know I could fetch the matching orders and their items into Python and average sum(item.quantity * item.unit_price for item in order.items) per order in application code, but this needs to stay fast as the table grows into the hundreds of thousands of rows, so re-aggregating in Python per request isn't what I want.

What's the correct SQL — and specifically SQLAlchemy Core/ORM — pattern to get a true average-per-order from a normalized orders/order_items table, in one query, without pulling every order's line items into Python first?

sqlalchemy