QuerySeter 复杂查询

ORM 以 QuerySeter 来组织查询,每个返回 QuerySeter 的方法都会获得一个新的 QuerySeter 对象。

基本使用方法:

  1. o := orm.NewOrm()
  2. // 获取 QuerySeter 对象,user 为表名
  3. qs := o.QueryTable("user")
  4. // 也可以直接使用 Model 结构体作为表名
  5. qs = o.QueryTable(&User)
  6. // 也可以直接使用对象作为表名
  7. user := new(User)
  8. qs = o.QueryTable(user) // 返回 QuerySeter
  9. // 后面可以调用qs上的方法,执行复杂查询。

QuerySeter的方法大体上可以分成两类:

  • 中间方法:用于构造查询
  • 终结方法:用于执行查询并且封装结果
  • 每个返回 QuerySeter 的 api 调用时都会新建一个 QuerySeter,不影响之前创建的。

  • 高级查询使用 Filter 和 Exclude 来做常用的条件查询。囊括两种清晰的过滤规则:包含, 排除

查询表达式

Beego 设计了自己的查询表达式,这些表达式可以用在很多方法上。

一般来说,你可以对单表的字段使用表达式,也可以在关联表上使用表达式。例如单个使用:

  1. qs.Filter("id", 1) // WHERE id = 1

或者在关联表里面使用:

  1. qs.Filter("profile__age", 18) // WHERE profile.age = 18
  2. qs.Filter("Profile__Age", 18) // 使用字段名和 Field 名都是允许的
  3. qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18
  4. // WHERE profile.age IN (18, 20) AND NOT profile_id < 1000

字段组合的前后顺序依照表的关系,比如 User 表拥有 Profile 的外键,那么对 User 表查询对应的 Profile.Age 为条件,则使用 Profile__Age 注意,字段的分隔符号使用双下划线 __

除了描述字段, 表达式的尾部可以增加操作符以执行对应的 sql 操作。比如 Profile__Age__gt 代表 Profile.Age > 18 的条件查询。在没有指定操作符的情况下,会使用=作为操作符。

当前支持的操作符号:

后面以 i 开头的表示:大小写不敏感

exact

Filter / Exclude / Condition expr 的默认值

  1. qs.Filter("name", "slene") // WHERE name = 'slene'
  2. qs.Filter("name__exact", "slene") // WHERE name = 'slene'
  3. // 使用 = 匹配,大小写是否敏感取决于数据表使用的 collation
  4. qs.Filter("profile_id", nil) // WHERE profile_id IS NULL

iexact

  1. qs.Filter("name__iexact", "slene")
  2. // WHERE name LIKE 'slene'
  3. // 大小写不敏感,匹配任意 'Slene' 'sLENE'

contains

  1. qs.Filter("name__contains", "slene")
  2. // WHERE name LIKE BINARY '%slene%'
  3. // 大小写敏感, 匹配包含 slene 的字符

icontains

  1. qs.Filter("name__icontains", "slene")
  2. // WHERE name LIKE '%slene%'
  3. // 大小写不敏感, 匹配任意 'im Slene', 'im sLENE'

in

  1. qs.Filter("age__in", 17, 18, 19, 20)
  2. // WHERE age IN (17, 18, 19, 20)
  3. ids:=[]int{17,18,19,20}
  4. qs.Filter("age__in", ids)
  5. // WHERE age IN (17, 18, 19, 20)
  6. // 同上效果

gt / gte

  1. qs.Filter("profile__age__gt", 17)
  2. // WHERE profile.age > 17
  3. qs.Filter("profile__age__gte", 18)
  4. // WHERE profile.age >= 18

lt / lte

  1. qs.Filter("profile__age__lt", 17)
  2. // WHERE profile.age < 17
  3. qs.Filter("profile__age__lte", 18)
  4. // WHERE profile.age <= 18

startswith

  1. qs.Filter("name__startswith", "slene")
  2. // WHERE name LIKE BINARY 'slene%'
  3. // 大小写敏感, 匹配以 'slene' 起始的字符串

istartswith

  1. qs.Filter("name__istartswith", "slene")
  2. // WHERE name LIKE 'slene%'
  3. // 大小写不敏感, 匹配任意以 'slene', 'Slene' 起始的字符串

endswith

  1. qs.Filter("name__endswith", "slene")
  2. // WHERE name LIKE BINARY '%slene'
  3. // 大小写敏感, 匹配以 'slene' 结束的字符串

iendswith

  1. qs.Filter("name__iendswithi", "slene")
  2. // WHERE name LIKE '%slene'
  3. // 大小写不敏感, 匹配任意以 'slene', 'Slene' 结束的字符串

isnull

  1. qs.Filter("profile__isnull", true)
  2. qs.Filter("profile_id__isnull", true)
  3. // WHERE profile_id IS NULL
  4. qs.Filter("profile__isnull", false)
  5. // WHERE profile_id IS NOT NULL

中间方法

Filter

  1. Filter(string, ...interface{}) QuerySeter

多次调用Filter方法,会使用AND将它们连起来。

  1. qs.Filter("profile__isnull", true).Filter("name", "slene")
  2. // WHERE profile_id IS NULL AND name = 'slene'

FilterRaw

  1. FilterRaw(string, string) QuerySeter

该方法会直接把输入当做是一个查询条件,因此如果输入有错误,那么拼接得来的 SQL 则无法运行。Beego 本身并不会执行任何的检查。

例如:

  1. qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)")
  2. //sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18)

Exclude

  1. Exclude(string, ...interface{}) QuerySeter

准确来说,Exclude表达的是NOT的语义:

  1. qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000)
  2. // WHERE profile.age IN (18, 20) AND NOT profile_id < 1000

SetCond

  1. SetCond(*Condition) QuerySeter

设置查询条件:

  1. cond := orm.NewCondition()
  2. cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000)
  3. //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
  4. num, err := qs.SetCond(cond1).Count()

Condition中使用的表达式,可以参考查询表达式

GetCond

  1. GetCond() *Condition

获得查询条件。例如:

  1. cond := orm.NewCondition()
  2. cond = cond.And("profile__isnull", false).AndNot("status__in", 1)
  3. qs = qs.SetCond(cond)
  4. cond = qs.GetCond()
  5. cond := cond.Or("profile__age__gt", 2000)
  6. //sql-> WHERE T0.`profile_id` IS NOT NULL AND NOT T0.`Status` IN (?) OR T1.`age` > 2000
  7. num, err := qs.SetCond(cond).Count()

Limit

  1. Limit(limit interface{}, args ...interface{}) QuerySeter

该方法第二个参数args实际上只是表达偏移量。也就是说:

  • 如果你只传了limit,例如说 10,那么相当于LIMIT 10
  • 如果你同时传了args 为 2, 那么相当于 LIMIT 10 OFFSET 2,或者说LIMIT 2, 10
  1. var DefaultRowsLimit = 1000 // ORM 默认的 limit 值为 1000
  2. // 默认情况下 select 查询的最大行数为 1000
  3. // LIMIT 1000
  4. qs.Limit(10)
  5. // LIMIT 10
  6. qs.Limit(10, 20)
  7. // LIMIT 10 OFFSET 20 注意跟 SQL 反过来的
  8. qs.Limit(-1)
  9. // no limit
  10. qs.Limit(-1, 100)
  11. // LIMIT 18446744073709551615 OFFSET 100
  12. // 18446744073709551615 是 1<<64 - 1 用来指定无 limit 限制 但有 offset 偏移的情况

如果你没有调用该方法,或者调用了该方法,但是传入了一个负数,Beego 会使用默认的值,例如 1000。

Offset

  1. Offset(offset interface{}) QuerySeter

设置偏移量,等同于Limit方法的第二个参数。

GroupBy

  1. GroupBy(exprs ...string) QuerySeter

设置分组,参数是列名。

OrderBy

  1. OrderBy(exprs ...string) QuerySeter

设置排序,使用的是一种特殊的表达:

  • 如果传入的是列名,那么代表的是按照列名 ASC 排序;
  • 如果传入的列名前面有一个负号,那么代表的是按照列名 DESC 排序;

例如:

  1. // ORDER BY STATUS DESC
  2. qs.OrderBy("-status")
  3. // ORDER BY ID ASC, STATUS DESC
  4. qs.OrderBy("id", "-status")

同样地,也可以使用查询表达式,例如:

  1. qs.OrderBy("id", "-profile__age")
  2. // ORDER BY id ASC, profile.age DESC
  3. qs.OrderBy("-profile__age", "profile")
  4. // ORDER BY profile.age DESC, profile_id ASC

ForceIndex

  1. qs.ForceIndex(`idx_name1`,`idx_name2`)

强制使用某个索引。你需要确认自己使用的数据库支持该特性,并且确认该特性在数据库上的语义。

参数是索引的名字。

UseIndex

  1. UseIndex(indexes ...string) QuerySeter

使用某个索引。你需要确认自己使用的数据库支持该特性,并且确认该特性在数据库上的语义。比如说在一些数据库上,该特性是“建议使用某个索引”,但是数据库在真实执行查询的时候,完全可能不使用这里指定的索引。

参数是索引的名字。

IgnoreIndex

  1. IgnoreIndex(indexes ...string) QuerySeter

忽略某个索引。你需要确认自己使用的数据库支持该特性,并且确认该特性在数据库上的语义。比如说在一些数据库上,该特性是“建议不使用某个索引”,但是数据库在真实执行查询的时候,完全可能使用这里指定的索引。

参数是索引的名字。

RelatedSel

  1. RelatedSel(params ...interface{}) QuerySeter

加载关联表的数据。如果没有传入参数,那么 Beego 加载所有关联表的数据。而如果传入了参数,那么只会加载特定的关联表数据。

在加载的时候,如果对应的字段是可以为 NULL 的,那么会使用 LEFT JOIN,否则使用 JOIN。

例如:

  1. // 使用 LEFT JOIN 加载 user 里面的所有关联表数据
  2. qs.RelatedSel().One(&user)
  3. // 使用 LEFT JOIN 只加载 user 里面 profile 的数据
  4. qs.RelatedSel("profile").One(&user)
  5. user.Profile.Age = 32

默认情况下直接调用 RelatedSel 将进行最大DefaultRelsDepth层的关系查询

Distinct

  1. Distinct() QuerySeter

为查询加上 DISTINCT 关键字

ForUpdate

  1. ForUpdate() QuerySeter

为查询加上 FOR UPDATE 片段。

PrepareInsert

  1. PrepareInsert() (Inserter, error)

用于一次 prepare 多次 insert 插入,以提高批量插入的速度。

  1. var users []*User
  2. ...
  3. qs := o.QueryTable("user")
  4. i, _ := qs.PrepareInsert()
  5. for _, user := range users {
  6. id, err := i.Insert(user)
  7. if err == nil {
  8. ...
  9. }
  10. }
  11. // PREPARE INSERT INTO user (`name`, ...) VALUES (?, ...)
  12. // EXECUTE INSERT INTO user (`name`, ...) VALUES ("slene", ...)
  13. // EXECUTE ...
  14. // ...
  15. i.Close() // 别忘记关闭 statement

Aggregate

  1. Aggregate(s string) QuerySeter

指定聚合函数。例如:

  1. type result struct {
  2. DeptName string
  3. Total int
  4. }
  5. var res []result
  6. o.QueryTable("dept_info").Aggregate("dept_name,sum(salary) as total").GroupBy("dept_name").All(&res)

终结方法

Count

  1. Count() (int64, error)

执行查询并且返回结果集的大小。

Exist

  1. Exist() bool

判断查询是否返回数据。等效于Count() 返回大于 0 的值。

Update

  1. Update(values Params) (int64, error)

依据当前查询条件,进行批量更新操作。

  1. num, err := o.QueryTable("user").Filter("name", "slene").Update(orm.Params{
  2. "name": "astaxie",
  3. })
  4. fmt.Printf("Affected Num: %s, %s", num, err)
  5. // SET name = "astaixe" WHERE name = "slene"

原子操作增加字段值

  1. // 假设 user struct 里有一个 nums int 字段
  2. num, err := o.QueryTable("user").Update(orm.Params{
  3. "nums": orm.ColValue(orm.ColAdd, 100),
  4. })
  5. // SET nums = nums + 100

orm.ColValue 支持以下操作

  1. ColAdd // 加
  2. ColMinus // 减
  3. ColMultiply // 乘
  4. ColExcept // 除

Delete

  1. Delete() (int64, error)

删除数据,返回被删除的数据行数。

All

  1. All(container interface{}, cols ...string) (int64, error)

返回对应的结果集对象。参数支持 *[]Type*[]*Type 两种形式的切片

  1. var users []*User
  2. num, err := o.QueryTable("user").Filter("name", "slene").All(&users)
  3. fmt.Printf("Returned Rows Num: %s, %s", num, err)

All / Values / ValuesList / ValuesFlat 受到 Limit 的限制,默认最大行数为 1000

可以指定返回的字段:

  1. type Post struct {
  2. Id int
  3. Title string
  4. Content string
  5. Status int
  6. }
  7. // 只返回 Id 和 Title
  8. var posts []Post
  9. o.QueryTable("post").Filter("Status", 1).All(&posts, "Id", "Title")

对象的其他字段值将会是对应类型的默认值。

One

  1. One(container interface{}, cols ...string) error

尝试返回单条记录:

  1. var user User
  2. err := o.QueryTable("user").Filter("name", "slene").One(&user)
  3. if err == orm.ErrMultiRows {
  4. // 多条的时候报错
  5. fmt.Printf("Returned Multi Rows Not One")
  6. }
  7. if err == orm.ErrNoRows {
  8. // 没有找到记录
  9. fmt.Printf("Not row found")
  10. }

Values

  1. Values(results *[]Params, exprs ...string) (int64, error)

返回结果集的 key => value

key 为模型里的字段名, value 是interface{}类型,例如,如果你要将 value 赋值给 struct 中的某字段,需要根据结构体对应字段类型使用断言获取真实值。:Name : m["Name"].(string)

  1. var maps []orm.Params
  2. num, err := o.QueryTable("user").Values(&maps)
  3. if err == nil {
  4. fmt.Printf("Result Nums: %d\n", num)
  5. for _, m := range maps {
  6. fmt.Println(m["Id"], m["Name"])
  7. }
  8. }

TODO: 暂不支持级联查询 RelatedSel 直接返回 Values

第二个参数可以是列名,也可以是查询表达式:

  1. var maps []orm.Params
  2. num, err := o.QueryTable("user").Values(&maps, "id", "name", "profile", "profile__age")
  3. if err == nil {
  4. fmt.Printf("Result Nums: %d\n", num)
  5. for _, m := range maps {
  6. fmt.Println(m["Id"], m["Name"], m["Profile"], m["Profile__Age"])
  7. // map 中的数据都是展开的,没有复杂的嵌套
  8. }
  9. }

ValuesList

  1. ValuesList(results *[]ParamsList, exprs ...string) (int64, error)

顾名思义,返回的结果集以切片存储,其排列与模型中定义的字段顺序一致,每个元素值是 string 类型。

  1. var lists []orm.ParamsList
  2. num, err := o.QueryTable("user").ValuesList(&lists)
  3. if err == nil {
  4. fmt.Printf("Result Nums: %d\n", num)
  5. for _, row := range lists {
  6. fmt.Println(row)
  7. }
  8. }

当然也可以指定查询表达式返回指定的字段:

  1. var lists []orm.ParamsList
  2. num, err := o.QueryTable("user").ValuesList(&lists, "name", "profile__age")
  3. if err == nil {
  4. fmt.Printf("Result Nums: %d\n", num)
  5. for _, row := range lists {
  6. fmt.Printf("Name: %s, Age: %s\m", row[0], row[1])
  7. }
  8. }

ValuesFlat

  1. ValuesFlat(result *ParamsList, expr string) (int64, error)

只返回特定的字段的值,将结果集展开到单个切片里。

  1. var list orm.ParamsList
  2. num, err := o.QueryTable("user").ValuesFlat(&list, "name")
  3. if err == nil {
  4. fmt.Printf("Result Nums: %d\n", num)
  5. fmt.Printf("All User Names: %s", strings.Join(list, ", "))
  6. }

RowsToMap 和 RowsToStruct

这两个方法都没有实现。