【GORM】查询操作

  • ~9.34K 字
  1. 1. 查询操作
    1. 1.1. 基本查询
      1. 1.1.1. 根据主键查询
    2. 1.2. 条件
      1. 1.2.1. string条件
      2. 1.2.2. Struct & Map 条件
      3. 1.2.3. 内联条件
      4. 1.2.4. Not条件
      5. 1.2.5. OR条件
    3. 1.3. 选择特定字段
    4. 1.4. 排序
    5. 1.5. 分页
    6. 1.6. Groupby与Having
    7. 1.7. Distinct
    8. 1.8. Join
      1. 1.8.1. JOINS预加载
  2. 2. 得到的数据
    1. 2.1. Find类
    2. 2.2. Rows
    3. 2.3. Scan

查询操作

基本查询

假设定义var user User,查询数据存入user

  • db.First(&user):获取按主键升序的第一条记录
  • db.Take(&user):获取一条记录,不指定排序字段
  • db.Last(&user):获取按主键升序的第一条记录
  • db.Find(&user):获取所有记录

First和Last只有struct传入的是指针或通过db.Model指定model才有效,另外,如果没有定义主键,则按第一个字段排序

根据主键查询

如果主键是数字类型,可以用内联条件检索

1
2
3
4
5
6
7
8
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是字符串,查询类似如下:

1
2
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

如果目标对象有一个主键值,可以使用主键构建查询条件

1
2
3
4
5
6
7
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

条件

使用db.Where().xxx()在查询前添加条件,Where里用问号格式化检索条件,其余和sql语句类似,代替sql中WHERE的作用

string条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

注意:若对象设置了主键,条件查询不会覆盖主键的值,而是用And相连,这样就报错了,所以记得要提前将id这样的主键设置为nil

1
2
3
4
var user = User{ID: 10}
db.Where("id = ?", 20).First(&user)
// SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1

Struct & Map 条件

Where里也可以塞结构体,map和切片

1
2
3
4
5
6
7
8
9
10
11
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

结构体查询的时候会省略零值,如果想要保留零值需要使用map

使用结构体的时候还可以指定结构体查询字段:

1
2
3
4
5
6
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;

db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;

内联条件

类似Where的用法,First和Find方法可以将上述条件写入方法参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Get by primary key if it were a non-integer type
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;

Not条件

类似Where,代替sql中<>和NOT IN的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;

// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

OR条件

代替sql中OR的作用

1
2
3
4
5
6
7
8
9
10
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

选择特定字段

可以用db.Select选择从表获取的特定字段,默认获取全部字段

1
2
3
4
5
6
7
8
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

以防你忘了:COALESCES的作用是接收多个参数,返回第一个非NULL的参数,可以更好的处理NULL值

Rows()返回的是一个*sql.Rows对象,拿到以后要手动循环读取并处理每一行数据,用法如下:

1
2
3
4
5
6
7
8
9
10
11
rows, err := db.Table("users").Select("COALESCE(age, ?)", 42).Rows()
if err != nil {
panic(err)
}
defer rows.Close()
for rows.Next() {
var age int64
// 用 rows.Scan 扫描当前行的值
rows.Scan(&age)
fmt.Println(age)
}

排序

使用db.Order()设置排序

1
2
3
4
5
6
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// Multiple orders
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

分页

使用db.Limit()和db.Offset()控制分页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)

db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;

db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;

// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

Groupby与Having

db.Group()代替sql里的group by操作,db.Having代替sql里的having操作

1
2
3
4
5
6
7
8
9
10
11
type result struct {
Date time.Time
Total int
}

db.Model(&User{}).Select("name, sum(age) as total").Where("name LIKE ?", "group%").Group("name").First(&result)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "group%" GROUP BY `name` LIMIT 1


db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"

Distinct

使用db.Distinct代替sql中的distinct

1
db.Distinct("name", "age").Order("name, age desc").Find(&results)

Join

使用db.Joins()实现JOIN操作,左外与右外在参数内部指明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type result struct {
Name string
Email string
}

db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

JOINS预加载

JOINS预加载可以自动对传入的表左外连接,用一条sql查出主表和关联表的数据,同时可以选择是左外连接还是内连接

1
2
3
4
5
6
7
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;

// inner join
db.InnerJoins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;

得到的数据

Find类

通过Find,First方法返回的数据类型一定是一个指向模型切片或结构体的指针,GORM会根据字段名和数据库列明映射

特点是强类型,只能接收和数据库表有对应关系的模型结构体,不需要手动管理资源

Rows

来自go标准库的*sql.Rows,必须使用rows.Next()遍历,用rows.Scan()逐列扫描到基础变量,最后需要rows.Close()

1
2
3
4
5
6
7
8
rows, _ := db.Table("users").Select("name, age").Rows()
defer rows.Close()
for rows.Next() {
var name string
var age int
rows.Scan(&name, &age) // 这是 database/sql 包的 Scan
fmt.Println(name, age)
}

特点是灵活,可以处理任意复杂动态查询,不受约束,且逐行处理适合大型结果

Scan

返回的数据类型可以是任意类型的切片或结构体指针,或者map切片

不需要模型定义,只要接受变量的结构和查询结果列名匹配

且是一次性把所有行扫描到提供的目标内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 扫描到自定义结构体切片
type Result struct {
Dept string
Count int
}
var results []Result
db.Raw("SELECT department, COUNT(*) as count FROM users GROUP BY department").Scan(&results)

// 扫描到 map 切片
var list []map[string]interface{}
db.Table("users").Select("name").Scan(&list)

// 扫描单个列到切片
var ages []int
db.Table("users").Pluck("age", &ages) // Pluck 是 Scan 的特殊情况

// 扫描单行单列到变量
var maxAge int
db.Table("users").Select("MAX(age)").Scan(&maxAge)

特点是比row方便且比find自由 适合聚合查询和跨表查询

赞助喵
非常感谢您的喜欢!
赞助喵