子查询是嵌套在其他查询中的查询。复杂业务场景下,子查询能解决很多问题。
在 WHERE 条件中使用子查询:
db.Where("age > (?)", db.Model(&User{}).Select("avg(age)")).Find(&users)
生成的 SQL:
SELECT * FROM users WHERE age > (SELECT avg(age) FROM users)
db.Where("id IN (?)", db.Model(&Order{}).Select("user_id").Where("amount > ?", 1000)).Find(&users)
生成的 SQL:
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000)
在 FROM 中使用子查询:
type Result struct {
Age int
Count int
}
var results []Result
db.Table("(?) as u", db.Model(&User{}).Select("age, count(*) as count").Group("age")).Find(&results)
在 SELECT 中使用子查询:
type UserOrderCount struct {
ID uint
Name string
OrderCnt int
}
var results []UserOrderCount
db.Model(&User{}).
Select("id, name, (SELECT count(*) FROM orders WHERE orders.user_id = users.id) as order_cnt").
Find(&results)
db.Where("EXISTS (?)", db.Model(&Order{}).Select("1").Where("orders.user_id = users.id")).Find(&users)
查询有订单的用户。
Not Exists:
db.Where("NOT EXISTS (?)", db.Model(&Order{}).Select("1").Where("orders.user_id = users.id")).Find(&users)
查询没有订单的用户。
子查询可以多层嵌套:
subQuery := db.Model(&Order{}).
Select("user_id, sum(amount) as total").
Group("user_id")
db.Table("(?) as o", subQuery).
Where("total > ?", 1000).
Find(&results)
查询每个用户的最新订单:
type UserLatestOrder struct {
UserID uint
OrderID uint
Amount float64
CreatedAt time.Time
}
var results []UserLatestOrder
db.Table("orders o1").
Select("o1.user_id, o1.id as order_id, o1.amount, o1.created_at").
Where("o1.created_at = (?)",
db.Table("orders o2").
Select("max(o2.created_at)").
Where("o2.user_id = o1.user_id"),
).
Find(&results)
复杂子查询用 Raw 更清晰:
var users []User
db.Raw(`
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders
WHERE amount > ?
GROUP BY user_id
HAVING count(*) > ?
)
`, 1000, 5).Scan(&users)
子查询可能影响性能,注意:
相关子查询
子查询依赖外部查询,每行都要执行一次:
db.Where("age > (?)", db.Model(&User{}).Select("avg(age)")).Find(&users)
这个例子不相关,只执行一次。
相关子查询:
db.Where("EXISTS (?)",
db.Model(&Order{}).Select("1").Where("orders.user_id = users.id"),
).Find(&users)
每行用户都要执行一次子查询。
优化方案
用 JOIN 替代:
db.Distinct("users.id").Joins("JOIN orders ON orders.user_id = users.id").Find(&users)
很多子查询可以改写成 JOIN:
子查询方式
db.Where("id IN (?)",
db.Model(&Order{}).Select("user_id").Where("amount > ?", 1000),
).Find(&users)
JOIN 方式
db.Distinct().Joins("JOIN orders ON orders.user_id = users.id AND orders.amount > ?", 1000).Find(&users)
JOIN 通常性能更好,但子查询有时更直观。
查询高于平均分的用户
db.Where("score > (?)", db.Model(&User{}).Select("avg(score)")).Find(&users)
查询每个分类的最新文章
db.Raw(`
SELECT a.* FROM articles a
INNER JOIN (
SELECT category, max(created_at) as max_created
FROM articles
GROUP BY category
) b ON a.category = b.category AND a.created_at = b.max_created
`).Scan(&articles)
查询订单总额前 10 的用户
type UserTotal struct {
UserID uint
Name string
Total float64
}
var results []UserTotal
db.Table("users").
Select("users.id, users.name, sum(orders.amount) as total").
Joins("JOIN orders ON orders.user_id = users.id").
Group("users.id").
Order("total desc").
Limit(10).
Find(&results)
子查询是复杂查询的利器,但要注意性能。能用 JOIN 替代的尽量用 JOIN,实在复杂的用 Raw SQL 更清晰。