ORM Party

Aggregation

Select ORMs to compare

Pick one or more ORMs from the bar above

SUM

SQL

-- Total of all order amounts
SELECT SUM(amount) AS total FROM orders;

-- Total per user
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id;

Django ORM

from django.db.models import Sum

# Total of all order amounts
total = Order.objects.aggregate(total=Sum("amount"))["total"]

# Total per user
per_user = Order.objects.values("user_id").annotate(total=Sum("amount"))

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    # Total of all order amounts
    total = session.scalar(
        select(func.sum(Order.amount))
    )

    # Total per user
    rows = session.execute(
        select(Order.user_id, func.sum(Order.amount).label("total"))
        .group_by(Order.user_id)
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    # Total of all order amounts
    total = session.exec(
        select(func.sum(Order.amount))
    ).one()

    # Total per user
    per_user = session.exec(
        select(Order.user_id, func.sum(Order.amount).label("total"))
        .group_by(Order.user_id)
    ).all()

Tortoise ORM

from tortoise.functions import Sum

# Total of all order amounts
total = await Order.annotate(
    total=Sum("amount")
).values("total")

# Total per user
per_user = await Order.annotate(
    total=Sum("amount")
).group_by("user_id").values("user_id", "total")

Peewee

from peewee import fn

# Total of all order amounts
total = Order.select(fn.SUM(Order.amount)).scalar()

# Total per user
per_user = (
    Order.select(
        Order.user,
        fn.SUM(Order.amount).alias("total"),
    )
    .group_by(Order.user)
)

Prisma

// Total of all order amounts
const result = await prisma.order.aggregate({
  _sum: { amount: true },
});
const total = result._sum.amount;

// Total per user
const perUser = await prisma.order.groupBy({
  by: ["userId"],
  _sum: { amount: true },
});

Drizzle

// Total of all order amounts
const result = await db.select({
  total: sum(orders.amount),
}).from(orders);

// Total per user
const perUser = await db.select({
  userId: orders.userId,
  total: sum(orders.amount),
})
  .from(orders)
  .groupBy(orders.userId);

Kysely

import { sql } from 'kysely'

// Total of all order amounts
const total = await db
  .selectFrom('orders')
  .select(sql<number>`sum(amount)`.as('total'))
  .executeTakeFirst()

// Total per user
const perUser = await db
  .selectFrom('orders')
  .select([
    'user_id',
    sql<number>`sum(amount)`.as('total'),
  ])
  .groupBy('user_id')
  .execute()

TypeORM

// Total of all order amounts
const result = await orderRepository
  .createQueryBuilder("order")
  .select("SUM(order.amount)", "total")
  .getRawOne();

// Total per user
const perUser = await orderRepository
  .createQueryBuilder("order")
  .select("order.userId", "userId")
  .addSelect("SUM(order.amount)", "total")
  .groupBy("order.userId")
  .getRawMany();

MikroORM

// Total of all order amounts
const total = await orm.em
  .createQueryBuilder(Order, "o")
  .select("sum(o.amount) as total")
  .execute("get");

// Total per user
const perUser = await orm.em
  .createQueryBuilder(Order, "o")
  .select(["o.user_id as user_id", "sum(o.amount) as total"])
  .groupBy("o.user_id")
  .execute();

Sequelize

import { col, fn } from "sequelize";

// Total of all order amounts
const total = await Order.sum("amount");

// Total per user
const perUser = await Order.findAll({
  attributes: [
    [col("user_id"), "userId"],
    [fn("SUM", col("amount")), "total"],
  ],
  group: ["user_id"],
});

AVG

SQL

SELECT AVG(amount) AS average FROM orders;

-- Average per user
SELECT user_id, AVG(amount) AS average
FROM orders
GROUP BY user_id;

Django ORM

from django.db.models import Avg

average = Order.objects.aggregate(avg=Avg("amount"))["avg"]

# Average per user
per_user = Order.objects.values("user_id").annotate(avg=Avg("amount"))

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    average = session.scalar(
        select(func.avg(Order.amount))
    )

    # Average per user
    rows = session.execute(
        select(Order.user_id, func.avg(Order.amount).label("average"))
        .group_by(Order.user_id)
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    average = session.exec(
        select(func.avg(Order.amount))
    ).one()

    # Average per user
    per_user = session.exec(
        select(Order.user_id, func.avg(Order.amount).label("avg"))
        .group_by(Order.user_id)
    ).all()

Tortoise ORM

from tortoise.functions import Avg

avg = await Order.annotate(
    avg=Avg("amount")
).values("avg")

# Average per user
per_user = await Order.annotate(
    avg=Avg("amount")
).group_by("user_id").values("user_id", "avg")

Peewee

from peewee import fn

average = Order.select(fn.AVG(Order.amount)).scalar()

# Average per user
per_user = (
    Order.select(
        Order.user,
        fn.AVG(Order.amount).alias("avg"),
    )
    .group_by(Order.user)
)

Prisma

const result = await prisma.order.aggregate({
  _avg: { amount: true },
});
const average = result._avg.amount;

// Average per user
const perUser = await prisma.order.groupBy({
  by: ["userId"],
  _avg: { amount: true },
});

Drizzle

const result = await db.select({
  average: avg(orders.amount),
}).from(orders);

// Average per user
const perUser = await db.select({
  userId: orders.userId,
  average: avg(orders.amount),
})
  .from(orders)
  .groupBy(orders.userId);

Kysely

import { sql } from 'kysely'

const average = await db
  .selectFrom('orders')
  .select(sql<number>`avg(amount)`.as('avg'))
  .executeTakeFirst()

// Average per user
const perUser = await db
  .selectFrom('orders')
  .select([
    'user_id',
    sql<number>`avg(amount)`.as('avg'),
  ])
  .groupBy('user_id')
  .execute()

TypeORM

const result = await orderRepository
  .createQueryBuilder("order")
  .select("AVG(order.amount)", "average")
  .getRawOne();

// Average per user
const perUser = await orderRepository
  .createQueryBuilder("order")
  .select("order.userId", "userId")
  .addSelect("AVG(order.amount)", "average")
  .groupBy("order.userId")
  .getRawMany();

MikroORM

const average = await orm.em
  .createQueryBuilder(Order, "o")
  .select("avg(o.amount) as avg")
  .execute("get");

// Average per user
const perUser = await orm.em
  .createQueryBuilder(Order, "o")
  .select(["o.user_id as user_id", "avg(o.amount) as avg"])
  .groupBy("o.user_id")
  .execute();

Sequelize

import { col, fn } from "sequelize";

const average = await Order.avg("amount");

// Average per user
const perUser = await Order.findAll({
  attributes: [
    [col("user_id"), "userId"],
    [fn("AVG", col("amount")), "avg"],
  ],
  group: ["user_id"],
});

MIN / MAX

SQL

SELECT MIN(amount) AS smallest, MAX(amount) AS largest
FROM orders;

-- Per user
SELECT user_id, MIN(amount), MAX(amount)
FROM orders
GROUP BY user_id;

Django ORM

from django.db.models import Min, Max

result = Order.objects.aggregate(
    smallest=Min("amount"),
    largest=Max("amount"),
)

# Per user
per_user = Order.objects.values("user_id").annotate(
    smallest=Min("amount"),
    largest=Max("amount"),
)

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    row = session.execute(
        select(
            func.min(Order.amount).label("smallest"),
            func.max(Order.amount).label("largest"),
        )
    ).one()

    # Per user
    rows = session.execute(
        select(
            Order.user_id,
            func.min(Order.amount),
            func.max(Order.amount),
        ).group_by(Order.user_id)
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    result = session.exec(
        select(
            func.min(Order.amount).label("smallest"),
            func.max(Order.amount).label("largest"),
        )
    ).one()

    # Per user
    per_user = session.exec(
        select(
            Order.user_id,
            func.min(Order.amount).label("smallest"),
            func.max(Order.amount).label("largest"),
        ).group_by(Order.user_id)
    ).all()

Tortoise ORM

from tortoise.functions import Min, Max

result = await Order.annotate(
    smallest=Min("amount"),
    largest=Max("amount"),
).values("smallest", "largest")

# Per user
per_user = await Order.annotate(
    smallest=Min("amount"),
    largest=Max("amount"),
).group_by("user_id").values(
    "user_id", "smallest", "largest"
)

Peewee

from peewee import fn

result = Order.select(
    fn.MIN(Order.amount).alias("smallest"),
    fn.MAX(Order.amount).alias("largest"),
).dicts().get()

# Per user
per_user = (
    Order.select(
        Order.user,
        fn.MIN(Order.amount).alias("smallest"),
        fn.MAX(Order.amount).alias("largest"),
    )
    .group_by(Order.user)
)

Prisma

const result = await prisma.order.aggregate({
  _min: { amount: true },
  _max: { amount: true },
});

// Per user
const perUser = await prisma.order.groupBy({
  by: ["userId"],
  _min: { amount: true },
  _max: { amount: true },
});

Drizzle

const result = await db.select({
  smallest: min(orders.amount),
  largest: max(orders.amount),
}).from(orders);

// Per user
const perUser = await db.select({
  userId: orders.userId,
  smallest: min(orders.amount),
  largest: max(orders.amount),
})
  .from(orders)
  .groupBy(orders.userId);

Kysely

import { sql } from 'kysely'

const result = await db
  .selectFrom('orders')
  .select([
    sql<number>`min(amount)`.as('smallest'),
    sql<number>`max(amount)`.as('largest'),
  ])
  .executeTakeFirst()

// Per user
const perUser = await db
  .selectFrom('orders')
  .select([
    'user_id',
    sql<number>`min(amount)`.as('smallest'),
    sql<number>`max(amount)`.as('largest'),
  ])
  .groupBy('user_id')
  .execute()

TypeORM

const result = await orderRepository
  .createQueryBuilder("order")
  .select("MIN(order.amount)", "smallest")
  .addSelect("MAX(order.amount)", "largest")
  .getRawOne();

// Per user
const perUser = await orderRepository
  .createQueryBuilder("order")
  .select("order.userId", "userId")
  .addSelect("MIN(order.amount)", "smallest")
  .addSelect("MAX(order.amount)", "largest")
  .groupBy("order.userId")
  .getRawMany();

MikroORM

const result = await orm.em
  .createQueryBuilder(Order, "o")
  .select([
    "min(o.amount) as smallest",
    "max(o.amount) as largest",
  ])
  .execute("get");

// Per user
const perUser = await orm.em
  .createQueryBuilder(Order, "o")
  .select([
    "o.user_id as user_id",
    "min(o.amount) as smallest",
    "max(o.amount) as largest",
  ])
  .groupBy("o.user_id")
  .execute();

Sequelize

import { col, fn } from "sequelize";

const result = {
  smallest: await Order.min("amount"),
  largest: await Order.max("amount"),
};

// Per user
const perUser = await Order.findAll({
  attributes: [
    [col("user_id"), "userId"],
    [fn("MIN", col("amount")), "smallest"],
    [fn("MAX", col("amount")), "largest"],
  ],
  group: ["user_id"],
});

GROUP BY

SQL

-- Orders per month
SELECT
  DATE_TRUNC('month', created_at) AS month,
  COUNT(*) AS order_count,
  SUM(amount) AS total
FROM orders
GROUP BY DATE_TRUNC('month', created_at)
ORDER BY month;

Django ORM

from django.db.models import Count, Sum
from django.db.models.functions import TruncMonth

# Orders per month
per_month = (
    Order.objects
    .annotate(month=TruncMonth("created_at"))
    .values("month")
    .annotate(order_count=Count("id"), total=Sum("amount"))
    .order_by("month")
)

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    month = func.date_trunc("month", Order.created_at).label("month")
    rows = session.execute(
        select(
            month,
            func.count().label("order_count"),
            func.sum(Order.amount).label("total"),
        )
        .group_by(month)
        .order_by(month)
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    month = func.date_trunc("month", Order.created_at).label("month")
    per_month = session.exec(
        select(
            month,
            func.count(Order.id).label("order_count"),
            func.sum(Order.amount).label("total"),
        )
        .group_by(month)
        .order_by(month)
    ).all()

Tortoise ORM

# No TruncMonth API; use raw SQL
from tortoise import connections

conn = connections.get("default")
rows = await conn.execute_query_dict(
    "SELECT DATE_TRUNC('month', created_at) "
    "  AS month, "
    "COUNT(*) AS order_count, "
    "SUM(amount) AS total "
    "FROM orders "
    "GROUP BY month ORDER BY month"
)

Peewee

from peewee import fn

month = fn.DATE_TRUNC("month", Order.created_at).alias("month")

# Orders per month
per_month = (
    Order.select(
        month,
        fn.COUNT(Order.id).alias("order_count"),
        fn.SUM(Order.amount).alias("total"),
    )
    .group_by(month)
    .order_by(month)
)

Prisma

// Prisma groupBy supports model fields only;
// for date truncation, use $queryRaw
const perMonth = await prisma.$queryRaw`
  SELECT
    DATE_TRUNC('month', "created_at") AS month,
    COUNT(*)::int AS order_count,
    SUM(amount) AS total
  FROM orders
  GROUP BY DATE_TRUNC('month', "created_at")
  ORDER BY month
`;

Drizzle

const month = sql`DATE_TRUNC('month', ${orders.createdAt})`;

const result = await db.select({
  month,
  orderCount: count(),
  total: sum(orders.amount),
})
  .from(orders)
  .groupBy(month)
  .orderBy(month);

Kysely

import { sql } from 'kysely'

// Orders per month
const perMonth = await db
  .selectFrom('orders')
  .select([
    sql<Date>`date_trunc('month', created_at)`.as('month'),
    sql<number>`count(id)`.as('order_count'),
    sql<number>`sum(amount)`.as('total'),
  ])
  .groupBy(sql`date_trunc('month', created_at)`)
  .orderBy('month', 'asc')
  .execute()

TypeORM

const result = await orderRepository
  .createQueryBuilder("order")
  .select("DATE_TRUNC('month', order.created_at)", "month")
  .addSelect("COUNT(*)", "orderCount")
  .addSelect("SUM(order.amount)", "total")
  .groupBy("DATE_TRUNC('month', order.created_at)")
  .orderBy("month")
  .getRawMany();

MikroORM

// Orders per month
const perMonth = await orm.em
  .createQueryBuilder(Order, "o")
  .select([
    "date_trunc('month', o.created_at) as month",
    "count(o.id) as order_count",
    "sum(o.amount) as total",
  ])
  .groupBy("date_trunc('month', o.created_at)")
  .orderBy({ month: "asc" })
  .execute();

Sequelize

import { col, fn, literal } from "sequelize";

// Orders per month
const perMonth = await Order.findAll({
  attributes: [
    [fn("DATE_TRUNC", "month", col("created_at")), "month"],
    [fn("COUNT", col("id")), "orderCount"],
    [fn("SUM", col("amount")), "total"],
  ],
  group: [literal(`DATE_TRUNC('month', "created_at")`)],
  order: [[literal(`DATE_TRUNC('month', "created_at")`), "ASC"]],
});

HAVING

SQL

-- Users who spent more than 1000 total
SELECT user_id, SUM(amount) AS total
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 1000;

Django ORM

from django.db.models import Sum

users = (
    Order.objects
    .values("user_id")
    .annotate(total=Sum("amount"))
    .filter(total__gt=1000)
)

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    rows = session.execute(
        select(Order.user_id, func.sum(Order.amount).label("total"))
        .group_by(Order.user_id)
        .having(func.sum(Order.amount) > 1000)
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    users = session.exec(
        select(Order.user_id, func.sum(Order.amount).label("total"))
        .group_by(Order.user_id)
        .having(func.sum(Order.amount) > 1000)
    ).all()

Tortoise ORM

from tortoise.functions import Sum

results = await Order.annotate(
    total=Sum("amount")
).group_by("user_id").filter(
    total__gt=1000
).values("user_id", "total")

Peewee

from peewee import fn

users = (
    Order.select(
        Order.user,
        fn.SUM(Order.amount).alias("total"),
    )
    .group_by(Order.user)
    .having(fn.SUM(Order.amount) > 1000)
)

Prisma

const result = await prisma.order.groupBy({
  by: ["userId"],
  _sum: { amount: true },
  having: {
    amount: { _sum: { gt: 1000 } },
  },
});

Drizzle

const result = await db.select({
  userId: orders.userId,
  total: sum(orders.amount),
})
  .from(orders)
  .groupBy(orders.userId)
  .having(gt(sum(orders.amount), 1000));

Kysely

import { sql } from 'kysely'

const users = await db
  .selectFrom('orders')
  .select([
    'user_id',
    sql<number>`sum(amount)`.as('total'),
  ])
  .groupBy('user_id')
  .having(sql`sum(amount)`, '>', 1000)
  .execute()

TypeORM

const result = await orderRepository
  .createQueryBuilder("order")
  .select("order.userId", "userId")
  .addSelect("SUM(order.amount)", "total")
  .groupBy("order.userId")
  .having("SUM(order.amount) > :min", { min: 1000 })
  .getRawMany();

MikroORM

const users = await orm.em
  .createQueryBuilder(Order, "o")
  .select(["o.user_id as user_id", "sum(o.amount) as total"])
  .groupBy("o.user_id")
  .having("sum(o.amount) > ?", [1000])
  .execute();

Sequelize

import { Op, col, fn, where } from "sequelize";

const users = await Order.findAll({
  attributes: [
    [col("user_id"), "userId"],
    [fn("SUM", col("amount")), "total"],
  ],
  group: ["user_id"],
  having: where(fn("SUM", col("amount")), Op.gt, 1000),
});

Subquery

SQL

-- Users whose orders exceed the average amount
SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
  WHERE amount > (SELECT AVG(amount) FROM orders)
);

Django ORM

from django.db.models import Avg, Subquery

avg_sub = Order.objects.values(
    avg=Avg("amount")
)

users = User.objects.filter(
    id__in=Subquery(
        Order.objects.filter(
            amount__gt=Subquery(avg_sub[:1])
        ).values("user_id")
    )
)

SQLAlchemy

from sqlalchemy import select, func

with Session(engine) as session:
    avg_amount = select(func.avg(Order.amount)).scalar_subquery()

    above_avg_users = select(Order.user_id).where(
        Order.amount > avg_amount
    ).distinct()

    users = session.scalars(
        select(User).where(User.id.in_(above_avg_users))
    ).all()

SQLModel

from sqlalchemy import func
from sqlmodel import select

with Session(engine) as session:
    avg_subquery = select(func.avg(Order.amount)).scalar_subquery()

    users = session.exec(
        select(User).where(
            User.id.in_(
                select(Order.user_id)
                .where(Order.amount > avg_subquery)
            )
        )
    ).all()

Tortoise ORM

# No ORM subquery API; using raw SQL
from tortoise import connections

conn = connections.get("default")
rows = await conn.execute_query_dict(
    "SELECT * FROM users "
    "WHERE id IN ("
    "  SELECT user_id FROM orders "
    "  WHERE amount > ("
    "    SELECT AVG(amount) FROM orders"
    "  )"
    ")"
)

Peewee

from peewee import fn

avg_subquery = Order.select(fn.AVG(Order.amount))

users = User.select().where(
    User.id.in_(
        Order.select(Order.user).where(
            Order.amount > avg_subquery
        )
    )
)

Prisma

// Prisma doesn't support subqueries natively;
// split into two queries
const avg = await prisma.order.aggregate({
  _avg: { amount: true },
});

const users = await prisma.user.findMany({
  where: {
    orders: {
      some: { amount: { gt: avg._avg.amount! } },
    },
  },
});

Drizzle

const avgAmount = db.select({
  value: avg(orders.amount),
}).from(orders);

const aboveAvgUsers = db.selectDistinct({
  userId: orders.userId,
})
  .from(orders)
  .where(gt(orders.amount, avgAmount));

const result = await db.select()
  .from(users)
  .where(inArray(users.id, aboveAvgUsers));

Kysely

import { sql } from 'kysely'

const users = await db
  .selectFrom('users')
  .selectAll()
  .where('id', 'in',
    db
      .selectFrom('orders')
      .select('user_id')
      .where('amount', '>',
        db
          .selectFrom('orders')
          .select(sql<number>`avg(amount)`)
      )
  )
  .execute()

TypeORM

const result = await userRepository
  .createQueryBuilder("user")
  .where((qb) => {
    const subQuery = qb.subQuery()
      .select("order.userId")
      .from(Order, "order")
      .where("order.amount > (SELECT AVG(amount) FROM orders)")
      .getQuery();
    return "user.id IN " + subQuery;
  })
  .getMany();

MikroORM

const users = await orm.em
  .createQueryBuilder(User, "u")
  .select("u.*")
  .where(`u.id in (
    select o.user_id
    from orders o
    where o.amount > (
      select avg(amount) from orders
    )
  )`)
  .getResultList();

Sequelize

import { Op, literal } from "sequelize";

const users = await User.findAll({
  where: {
    id: {
      [Op.in]: literal(`(
        SELECT user_id FROM orders
        WHERE amount > (
          SELECT AVG(amount) FROM orders
        )
      )`),
    },
  },
});