The note uses GORM as a case study in ORM abstractions.
db.Preload("Orders").Find(&users)
-- It takes 2 queires
SELECT * FROM users;
SELECT * FROM orders WHERE user_id IN (1, 2, 3, 4);
-- This is the opposite of:
-- SELECT *
-- FROM users
-- JOIN orders ON orders.user_id = users.id;
Client can get the filled domain model object without reconstructing
user.Orders []Order
// -- name: ListUsers :many
// SELECT id, name
// FROM users;
users, _ := q.ListUsers(ctx)
ids := make([]int64, 0, len(users))
for _, u := range users {
ids = append(ids, u.ID)
}
// -- name: ListOrdersByUserIDs :many
// SELECT id, user_id, amount
// FROM orders
// WHERE user_id = ANY($1);
orders, _ := q.ListOrdersByUserIDs(ctx, ids)
ordersByUser := make(map[int64][]Order)
for _, o := range orders {
ordersByUser[o.UserID] = append(ordersByUser[o.UserID], o)
}
for i := range users {
users[i].Orders = ordersByUser[users[i].ID]
}
-- Pagnate user first
SELECT id, name
FROM users
ORDER BY id
LIMIT 10 OFFSET 0;
-- Then fetch pagnated rows later
SELECT *
FROM orders
WHERE user_id IN (A, B, C);
type ListUsersWithOrdersRow struct {
UserID int64
UserName string
OrderID sql.NullInt64
Amount sql.NullInt64
}
// -- name: ListUsersWithOrders :many
// SELECT
// u.id AS user_id,
// u.name AS user_name,
// o.id AS order_id,
// o.amount
// FROM users u
// LEFT JOIN orders o ON o.user_id = u.id
// ORDER BY u.id;
rows, _ := q.ListUsersWithOrders(ctx)
users := make([]User, 0)
userIndex := make(map[int64]int)
for _, r := range rows {
idx, exists := userIndex[r.UserID]
if !exists {
users = append(users, User{
ID: r.UserID,
Name: r.UserName,
Orders: []Order{},
})
idx = len(users) - 1
userIndex[r.UserID] = idx
}
if r.OrderID.Valid {
users[idx].Orders = append(users[idx].Orders, Order{
ID: r.OrderID.Int64,
Amount: r.Amount.Int64,
})
}
}
-- Pattern: “page first, join later”
-- name: ListUsersWithOrdersPaged :many
WITH paged_users AS (
SELECT id, name
FROM users
ORDER BY id
LIMIT $1 OFFSET $2
)
SELECT
u.id AS user_id,
u.name AS user_name,
o.id AS order_id,
o.amount
FROM paged_users u
LEFT JOIN orders o ON o.user_id = u.id
ORDER BY u.id;
There are no hidden SQL tricks inside GORM worth reverse-engineering.