子查询

子查询是嵌套在其他查询中的查询。复杂业务场景下,子查询能解决很多问题。

Where 子查询

在 WHERE 条件中使用子查询:

db.Where("age > (?)", db.Model(&User{}).Select("avg(age)")).Find(&users)

生成的 SQL:

SELECT * FROM users WHERE age > (SELECT avg(age) FROM users)

IN 子查询

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 子查询

在 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 子查询

在 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)

Exists 子查询

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 构建子查询

复杂子查询用 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)

子查询 vs JOIN

很多子查询可以改写成 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 更清晰。