关于gorm多表联合查询(left join)的小记

时间:2022-07-22
本文章向大家介绍关于gorm多表联合查询(left join)的小记,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Golang很流行,但是有些方面资料很少而且不详实,譬如:gorm的联合查询,当然,也不推荐复杂语句使用orm模型。

现将自己总结的写法和遇到的坑记录如下: Golang要求使用“驼峰命名法”,比如systemId,因为我以前用的是Python,使用Django的orm序列化后返回的参数和数据库表字段一致,基于这个不适合Go的思路,我将表字段也建成了systemId,和struct映射参数相同。(其实表字段应该命名为system_id)

一、下面建两张表,用于联合查询(以left join示例)

MySQL > desc go_system_info;
+——————+——————-+———+——-+——————-+————————+
| Field | Type | Null | Key | Default | Extra |
+——————+——————-+———+——-+——————-+————————+
| id | int(11) | NO | PRI | NULL | auto_increment |
| systemId | varchar(30) | NO | MUL | NULL | |
| systemName | varchar(50) | NO | | defaultNull | |
+——————+——————-+———+——-+——————-+————————+
3 rows in set (0.01 sec)

MySQL > desc go_service_info;
+——————-+——————-+———+——-+——————-+————————+
| Field | Type | Null | Key | Default | Extra |
+——————-+——————-+———+——-+——————-+————————+
| id | int(11) | NO | PRI | NULL | auto_increment |
| systemId | varchar(30) | NO | MUL | NULL | |
| serviceId | varchar(50) | NO | MUL | defaultNull | |
| serviceName | varchar(50) | NO | | defaultNull | |
+——————-+——————-+———+——-+——————-+————————+
4 rows in set (0.00 sec)

MySQL >

二、表建好后,我们来定义表结构体:

type GoSystemInfo struct {
    ID           int    `gorm:"primary_key"`
    SystemId     string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"`
    SystemName   string `gorm:"column:systemName;type:varchar(50);not null;default:'defaultNull'"`
}
type GoServiceInfo struct {
    ID           int    `gorm:"primary_key"`
    SystemId     string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"`
    ServiceId    string `gorm:"column:serviceId;type:varchar(50);not null;default:'defaultNull';index:ServiceId"`
    ServiceName  string `gorm:"column:serviceName;type:varchar(50);not null;default:'defaultNull'"`
}

小知识:ORM(Object Relation Mapping),对象关系映射,实际上就是对数据库的操作进行封装,对上层开发人员屏蔽数据操作的细节,开发人员看到的就是一个个对象,大大简化了开发工作,提高了生产效率,也可以避免sql注入等问题。

由于gorm是使用的orm映射,所以需要定义要操作的表的model,在go中需要定义一个struct, struct的名字就是对应数据库中的表名,注意gorm查找struct名对应数据库中的表名的时候会默认把你的struct中的大写字母转换为小写并加上“s”,所以可以加上 db.SingularTable(true) 让gorm转义struct名字的时候不用加上“s”。

golang中,首字母大小写来表示public或者private,因此结构体中字段首字母必须大写。

定义model,即struct时,我们可以只定义我们需要从数据库中取回的特定字段: gorm在转义表名的时候会把struct的大写字母(首字母除外) 替换成“_”,所以下面的”GoSystemInfo”会转义成数据库中对应的“go_system_info”的表名, 对应的字段名的查找会先按照tag里面的名称去里面查找,如果没有定义标签则按照struct定义的字段查找,查找的时候struct字段中的大写会被转义成“_”,如:“SystemId”会去查找表中的system_id字段。

在本例,我们在struct使用如gorm:”column:systemId”,column映射mysql表字段名称。

三、联合查询

单表查询用上面的原表结构体接收数据就可以了, 联合查询涉及两张表中的全部/部分数据,我们定义新的结构体接收取回的特定字段:

type result struct {
    SystemId    string `json:"systemId"`
    SystemName  string `json:"systemName"`
    ServiceId   string `json:"serviceId"`
    ServiceName string `json:"serviceName"`
}

我们从go_service_info取serviceId、serviceName,从go_system_info取对应的systemId、systemName:

db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId").Scan(&results)

where条件:

db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId where go_service_info.serviceId <> ? and go_system_info.systemId = ?", "xxx", "xxx").Scan(&results)

注意:这里需要使用别名as system_id,映射返回值结构体,并且因为查找的时候struct字段中的大写会被转义成“_”,所以别名也要将大写转为“_”。

若使用原生语句:

db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId").Scan(&results)

where条件:

db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId where a.serviceId <> ? and b.systemId = ?", "xxx", "xxx").Scan(&results)

结果相同。

避坑建议: 表字段命名为如system_id,默认映射到结构体字段SystemId。当然建表原则上也是用小写和下划线,不过历史表难免会有大写命名的情况,所以新表还是遵照相关规范吧。

源码:

package main
import (
    "fmt"
    "log"
    "encoding/json"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)
type GoSystemInfo struct {
    ID           int    `gorm:"primary_key"`
    SystemId     string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"`
    SystemName   string `gorm:"column:systemName;type:varchar(50);not null;default:'defaultNull'"`
}
type GoServiceInfo struct {
    ID           int    `gorm:"primary_key"`
    SystemId     string `gorm:"column:systemId;type:varchar(30);not null;index:SystemId"`
    ServiceId    string `gorm:"column:serviceId;type:varchar(50);not null;default:'defaultNull';index:ServiceId"`
    ServiceName  string `gorm:"column:serviceName;type:varchar(50);not null;default:'defaultNull'"`
}
type result struct {
    SystemId    string `json:"systemId"`
    SystemName  string `json:"systemName"`
    ServiceId   string `json:"serviceId"`
    ServiceName string `json:"serviceName"`
}
//定义数据库连接
type ConnInfo struct { 
    MyUser   string 
    Password string 
    Host     string 
    Port     int 
    Db       string
}
func dbConn(MyUser, Password, Host, Db string, Port int) *gorm.DB {
    connArgs := fmt.Sprintf("%s:%s@(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", MyUser,Password, Host, Port, Db )
    db, err := gorm.Open("mysql", connArgs)
    if err != nil {  
        log.Fatal(err) 
    } 
    db.SingularTable(true)
    return db
}
func mapToJson(result interface{}) string {
    // map转 json str
    jsonBytes, _ := json.Marshal(result)
    jsonStr := string(jsonBytes)
    return jsonStr
}
func main() {
    var results []result
    cn := ConnInfo{  
      "xxx",  
      "xxx",  
      "127.0.0.1",  
      3306,  
      "xxx", 
    }  
    db := dbConn(cn.MyUser,cn.Password,cn.Host,cn.Db,cn.Port)
    defer db.Close()
    /*
    // 创建表
    db.AutoMigrate(&GoSystemInfo{})
    product := GoSystemInfo{SystemId:"sysid", SystemName:"sysname"}
    fmt.Println(db.NewRecord(product))
    db.AutoMigrate(&GoServiceInfo{})
    products := GoServiceInfo{SystemId:"sysid", ServiceId:"serid", ServiceName:"sername"}
    fmt.Println(db.NewRecord(products))
    */
    // 联合查询(left join)
    db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId").Scan(&results)
    fmt.Println(mapToJson(results))
    // where
    db.Table("go_service_info").Select("go_service_info.serviceId as service_id, go_service_info.serviceName as service_name, go_system_info.systemId as system_id, go_system_info.systemName as system_name").Joins("left join go_system_info on go_service_info.systemId = go_system_info.systemId where go_service_info.serviceId <> ? and go_system_info.systemId = ?", "xxx", "xxx").Scan(&results)
    fmt.Println(mapToJson(results))
    // 原生sql
    db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId").Scan(&results)
    fmt.Println(mapToJson(results))
    // where
    db.Raw("SELECT a.serviceId as service_id,a.serviceName as service_name, b.systemId as system_id, b.systemName as system_name FROM go_service_info a LEFT JOIN go_system_info b ON a.systemId = b.systemId where a.serviceId <> ? and b.systemId = ?", "xxx", "xxx").Scan(&results)
    fmt.Println(mapToJson(results))
}

示例结果:

参考:https://www.jb51.net/article/151051.htm