Join 查询

多表关联查询是数据库操作的核心。GORM 的 Join 方法灵活支持各种连接方式。

基本 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)

Left Join

左连接,保留左表所有记录:

db.Table("users").
    Select("users.name, orders.amount").
    Joins("LEFT JOIN orders ON orders.user_id = users.id").
    Find(&results)

没有订单的用户也会显示,orders.amount 为 NULL。

Right Join

右连接,保留右表所有记录:

db.Table("users").
    Select("users.name, orders.amount").
    Joins("RIGHT JOIN orders ON orders.user_id = users.id").
    Find(&results)

没有用户的订单也会显示。

Join 条件

带条件的 Join:

db.Joins("JOIN orders ON orders.user_id = users.id AND orders.status = ?", "paid").Find(&users)

Join 与 Where

Join 后再加条件:

db.Joins("JOIN orders ON orders.user_id = users.id").
    Where("orders.amount > ?", 1000).
    Find(&users)

Join 查询结果

映射到结构体:

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)

Join 统计

统计每个用户的订单数:

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 去重

用户可能有多个订单,Join 后会重复:

db.Distinct("users.id").Joins("JOIN orders ON orders.user_id = users.id").Find(&users)

Join 与预加载

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

多条件 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)

Join 性能

索引

连接字段必须有索引:

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 vs 预加载

Join

  • 一次查询
  • 结果扁平化
  • 适合简单关联

预加载

  • 两次查询
  • 结果结构化
  • 适合复杂关联

选择建议:

  • 需要关联字段筛选,用 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 是多表查询的基础。理解内连接、左连接的区别,注意索引和性能优化。复杂场景下,预加载可能是更好的选择。