多表关联查询是数据库操作的核心。GORM 的 Join 方法灵活支持各种连接方式。
内连接:
db.Joins("JOIN orders ON orders.user_id = users.id").Find(&users)
生成的 SQL:
SELECT users.* FROM users JOIN orders ON orders.user_id = users.id
连接多个表:
db.Table("users").
Select("users.name, orders.amount, products.title").
Joins("JOIN orders ON orders.user_id = users.id").
Joins("JOIN products ON products.id = orders.product_id").
Find(&results)
左连接,保留左表所有记录:
db.Table("users").
Select("users.name, orders.amount").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Find(&results)
没有订单的用户也会显示,orders.amount 为 NULL。
右连接,保留右表所有记录:
db.Table("users").
Select("users.name, orders.amount").
Joins("RIGHT JOIN orders ON orders.user_id = users.id").
Find(&results)
没有用户的订单也会显示。
带条件的 Join:
db.Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", "paid").Find(&users)
Join 后再加条件:
db.Joins("JOIN orders ON orders.user_id = users.id").
Where("orders.amount > ?", 1000).
Find(&users)
映射到结构体:
type UserOrder struct {
UserName string
OrderID uint
Amount float64
}
var results []UserOrder
db.Table("users").
Select("users.name as user_name, orders.id as order_id, orders.amount").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Find(&results)
统计每个用户的订单数:
type UserOrderCount struct {
Name string
OrderCnt int
}
var results []UserOrderCount
db.Table("users").
Select("users.name, count(orders.id) as order_cnt").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Group("users.id").
Find(&results)
用户可能有多个订单,Join 后会重复:
db.Distinct("users.id").Joins("JOIN orders ON orders.user_id = users.id").Find(&users)
Join 和预加载结合:
type User struct {
ID uint
Name string
Orders []Order
}
var users []User
db.Joins("JOIN orders ON orders.user_id = users.id").
Preload("Orders").
Find(&users)
同一张表自连接:
type Employee struct {
ID uint
Name string
ManagerID *uint
Manager *Employee
}
var employees []Employee
db.Table("employees e1").
Select("e1.name, e2.name as manager_name").
Joins("LEFT JOIN employees e2 ON e2.id = e1.manager_id").
Find(&employees)
多条件 Join:
db.Table("users").
Select("users.name, orders.amount").
Joins("LEFT JOIN orders ON orders.user_id = users.id AND orders.created_at > ?", time.Now().AddDate(0, -1, 0)).
Find(&results)
索引
连接字段必须有索引:
CREATE INDEX idx_orders_user_id ON orders(user_id);
小表驱动大表
让小表作为驱动表:
db.Table("orders").Joins("JOIN users ON users.id = orders.user_id")
如果 orders 表小,这样更高效。
**避免 SELECT ***
只查需要的字段:
db.Table("users").Select("users.name, orders.amount").Joins("JOIN orders ON orders.user_id = users.id")
Join
预加载
选择建议:
查询用户及其订单总额
type UserTotal struct {
Name string
Total float64
}
var results []UserTotal
db.Table("users").
Select("users.name, COALESCE(sum(orders.amount), 0) as total").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Group("users.id").
Order("total desc").
Find(&results)
查询有未支付订单的用户
var users []User
db.Distinct("users.id").
Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", "unpaid").
Find(&users)
查询用户最后下单时间
type UserLastOrder struct {
Name string
LastOrder time.Time
}
var results []UserLastOrder
db.Table("users").
Select("users.name, max(orders.created_at) as last_order").
Joins("LEFT JOIN orders ON orders.user_id = users.id").
Group("users.id").
Find(&results)
Join 是多表查询的基础。理解内连接、左连接的区别,注意索引和性能优化。复杂场景下,预加载可能是更好的选择。