[Rails] Active Record Queries
资料来源:Rails Guide
Guide
-“查询”就是根据条件查找记录。
-“查询操作”多种多样:select, where, order, etc
-“方法链”:将一系列查询操作的方法通过.
串链在一起
-“预载入”eager load
通过加载关联对象,以减少查询次数
-如何检查查询的记录是否存在,或关联的对象是否存在
1. Why
为什么要使用关联?
-(1) 可增强代码可读性
-(2) 不依赖于特定的数据库
-(3) 嘿嘿最爽的,不用写复杂的 SQL
Tips: 某些特殊情况为了提高效率你还是得写 :(
2. What
查询 通过指定的条件得到你想要的结果。
2.1 查询步骤
-(1) 将查询方法转化为 SQL 查询语句
-(2) 执行 SQL 查询语句,返回数据库中相关记录
-(3) 将相关记录转化为关联模型的 Ruby 对象
-(4) 执行after_find
和 after_initialize
回调
2.2 查询结果通常可分为两类
-(1) 若查找一条记录,返回的是模型对象的一个实例;
-(2) 若查找结果为多个模型对象实例的集合,返回ActiveRecord::Relation
对象
Tip: 区分··关联查找返回的是ActiveRecord::Association::CollectionProxy
对象
Tip: 若是通过关联在进行查询,返回ActiveRecord::AssociationRelation
对象
2.2.1 返回单个对象
查询方法:find, find_by, take, first, last
Tip1: find
找不到时抛出ActiveRecord::RecordNotFound
,其他方法返回nil
Tip2: 若想让其他方法也抛出ActiveRecord::RecordNotFound
,则使用**方法
Tip3: 这些方法也可以返回多条记录(数组),find
后面跟一组id
组成的数组可返回多个记录,其他方法加数量参数也可返回多个记录;注意find_by
方法无法返回数组,属性对应数组的话只是增加查询所要满足的条件
2.2.2 返回多个对象(不含条件)
-(1) all
载入所有记录到内存中,当数据量很大时会爆炸!
-(2) find_each
将记录分成大小相等的块,一块一块地载入,代码块中处理单个记录
-(3) find_in_batch
同上,分块载入,代码块中处理整个载入的块 (数组)
-分块载入方法添加选项 batch_size, start, finish
分别指定大小,开始结束位置
Tip: the 'find_each' and 'find_in_batch' methods are intended for use in the batch processing of large number of records that wouldn't fit in memory all at once. if you just need to loop over a thousand records the regular find methods (all, where...) are the preferred option.
2.2.3 返回多个对象(含条件)
2.2.3.1 where
条件可以是纯字符串 (存在SQL注入),数组,占位符,哈希键值对
一般情况下建议使用哈希的形式 (Rails way),若不能满足使用数组或占位符
哈希键值对的使用可分为三种情况: (1) Equality (2) Range (3) Subset
Tip: not
跟在where
后面是用表示不等于。
2.2.3.2 order
order
对查找的结果排序 (:asc 升序, :desc 降序
),默认为升序
2.2.3.3 select
默认情况下,会查询记录中所有字段,使用select
可以指定要查找的字段
distinct query = Client.select(:id, :name).distinct <~> query.distinct(false)
Tip1: 后面接distinct
方法查找单个记录,也可使用distinct(false)
去除限制
Tip2: 当要查找的字段不存在时会抛出ActiveRecord::MissingAttributeError
Tip3: 若指定的字段不含id
则关联将会失效
Tip4: 只查找个别字段会提高查询的效率
2.2.3.4 limit & offset
limit
来指定你想得到多少条记录offset
来制定你想跳过多少条记录后开始查找query = Client.limit(5).offset(30) # start with 31st get 5 client records
2.2.3.5 group & having
group
用来把属性相同的记录组织在一起,用于计算等having
用来对使用group
之后计算得到的值来限制条件
.group("date(created_at)").having("sum(price) > ?", 100)```
**Tip**: ```group```方法后面加```count```可以的到每组对应的记录个数 (Hash)
```Order.group(:status).count # => { 'awaiting_approval' => 7, 'paid' => 12 }```
#### 2.2.3.6 Overriding Conditions
```unscope``` 移除之前定义过的某个查询方法或某个条件
```Article.where('id > 10').limit(20).order('id asc').unscope(:order)```
```Article.where(id: 10, trashed: false).unscope(where: :id)```
```only``` 用来限制指定的查询方法是有效的,其他无效
```reorder``` 用来重新指定排序的字段
```reverse_order``` 与之前的排序相反
```rewhere``` 重新指定查询的条件
#### 2.2.3.7 none
只要查询方法链中包含```none```就返回空的查询结果 ```#<ActiveRecord::Relation []>```
```User.none if user.in_blacklist? # => #<ActiveRecord::Relation []> ```
**Tip**: 当你在特定情况下,如该用户被拉入黑名单,他的查询操作结果应该为空
#### 2.2.3.8 readonly
一个查询结果后添加```readonly```用来明确指出该记录是不可被修改的
若你强行修改它系统会抛出```ActiveRecord::ReadOnlyRecord```.
#### 2.2.3.9 joins
```joins``` 后面可直接加 SQL 语句来进行查询,或接关联
```joins``` 还可以连接多个关联,可以连接嵌套关联
```left_outer_joins``` 用来处理左连接,即使关联为空,也会放入查询结果
```ruby
Author.left_outer_joins(:posts).distinct
.select('authors.*, COUNT(posts.*) AS posts_count')
.group('authors.id')
=> (sql) SELECT DISTINCT authors.*, COUNT(posts.*) AS posts_count FROM "authors"
LEFT OUTER JOIN posts ON posts.author_id = authors.id GROUP BY authors.id
3. Existence of Objects
比较这些方法:present?, exists?, any?, many?
-(1) 避免使用 present?
查看结果是否存在,效率较低:SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
-(2) exists?, any?
查看结果是否存在,效率较高 (查到一条记录存在就停止)SELECT 1 AS one FROM "posts" WHERE "posts"."user_id"=? LIMIT ? [..., limit 1]
-(3) 使用 many?
查看结果是否有不止一个 (查询语句使用count
):SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ? [["user_id", 1]]
]
Tip: 和any?
相比,exists?
后面可以加条件参数,查看满足条件的记录是否存在Client.exists?(name: ['Wende', 'Zhaobo', 'Junda'])
Client.where(name: ['Wende', 'Zhaobo', 'Junda']).exists?
4. Locking
给记录上锁是为了避免多人同时操作同一条记录可能产生的冲突。
4.1 Optimistic Locking
- 乐观锁允许多个用户获取并操作同一记录,在操作完成并提交更新时通过查看该记录的版本来判断是否会发生冲突,若版本不一致抛出
ActiveRecord::StaleObjectError
并默认无视所更新的内容。对于失败的情况我们要处理这种异常,回滚,合并或是进行其他逻辑操作来弥补。(实现方式通过添加lock_version
自动实现) - 乐观锁用来处理竞争关系,谁都可以获取记录的操作权,谁先完成,谁就可以更新记录,其他的更新将会在本次竞争中失败。所以少了等待的过程。
4.2 Pessimistic Locking
- 悲观锁是当某个用户准备操作某条记录之前,给这个记录或表上锁,这样其他人在他没完成操作之前都无法打开这个锁,不存在竞争关系,必定是这个上锁的人完成更新操作。(悲观锁是数据库底层自带的锁机制,使用
lock, lock!, with_lock
来上锁) - 悲观锁可确保第一个获取记录操作权的人完成更新。并且避免大量用户操作同个记录中只有一人更新其他则做无用功的情况,在操作完成时更新比较记录锁的版本号也是要耗费性能的。
5. Eager Loading Association
通过使用includes
方法解决 n +1 queries 问题Category.includes(articles: [ { comments: :guest}, :tags ]).find(1)
上面的例子会找到 id 为 1 的类别记录,并且加载他所有关联的文章,文章相关的标签和评论,还有相关评论人,这样你在对这些数据进行操作时就不必在进行其他查询了
6. Scopes
Scope 可以让你把常用的关联查询语句构建成为一个类方法,供类和关联对象使用
它的返回值统一为relation
这样便于调用链方法,就好像是自己构建的一个查询方法
Tips: scope 和类方法的区别?scope 返回一个ActiveRecord::Relation
对象,即使当查询条件不满足时也会返回一个空的relation
。反观类方法当结果不满足时你可以返回任何你想返回的值,如nil
,而这就会导致查询链断裂,因为无法对一个空的对象在进行更多查询了。
7. Enums
enum availability: [:available, :unavailable]
使用enum
宏方法来匹配类型为integer
的字段,已达到枚举不同状态的效果。
使用它非常方便清晰,会自动拥有许多帮助方法,当情况更加复杂时可以使用状态机。
8. Dynamic Finders
find_by_first_name('wende'), find_by_last_name('lu'), find_by_age(25)
find_by_first_name_and_last_name_and_age('wende', 'lu', 25)
9. Method Chain
在方法链末尾使用where
来过滤得到一组记录。
在方法链末尾使用find_by
来过滤得到一条记录。
10. Find or Build a new Object
find_or_create_by, find_or_initialize_by
创建或初始化一条记录如果它不存在。
Tip1: 使用created_with
或代码块在创建时赋值,注意在查询时该语句将会被忽略。
Tip2: 使用persisted?, new_record?
来判断记录是否存在在数据库中。
11. Find by SQL
find_by_sql
使用自定义的 SQL 将查询结果初始化为对象存到数组中。connection#select_all
自定义查询,获取由哈希键值对所组成的数组 (不初始化对象)。pluck
接收一系列列名作为参数并且返回包含记录中该列的值的数组。Client.pluck(:id) 等价于 Client.select(:id).map(&:id)
Client.pluck(:id, :name) 等价于 Client.select(:id, :name).map {|c| [c.id, c.name]}
Tips: 在一个已查询的relation
上使用pluck
并不会重新去查询数据库。
Tips: ids
相当于使用pluck
获得所有主键id
的数组。
Tips: select_all, pluck
直接把数据库查询的结果转化为数组,并不会创建模型对象。对于操作量打并且查询次数频繁的查询操作,可以提高效率。
12. Calculations
count, average, minimum, maximum, sum
Tips: count
后可以添加列名,表示查找该列名存在的记录个数。
Tips: 这些计算操作都是在数据库层执行 SQL 计算得到的,需要对数据库操作。
为提高效率,我么希望先获得查询结果,转化为普通数组在进行操作,以减少查询。
13. Explain
对relation
使用explain
方法可以查看该查询语句的详细信息,用于调优。
不同数据库都有自己的EXPLAIN
方法,查看并掌握他们十分有用。
Tips: 对relation
使用to_sql
可以查看该查询方法链所生成的 SQL 语句。
推荐阅读
-
yii2中使用Active Record模式的方法_php实例
-
yii2中使用Active Record模式的方法
-
[Rails] Active Record Queries
-
[Rails] Active Record Migration
-
Active Record: Sexy migrations
-
7.1.3 active record callback
-
yii2中使用Active Record模式的方法
-
Active Record batch processing in parallel processes
-
Active Record batch processing in parallel processes
-
Yii框架官方指南系列26——使用数据库:关系型 Active Record