Rails 中的数据查询方法

Rails中的Acvtice Record使得我们不用写SQL语句就可以查询数据库中的数据。
当使用Rails中的方法查询数据时,过程大体是这样的:

  • 将查询选项转为sql语句
  • 触发sql语句在数据库中查询
  • 将查询结果实例化(变成对应的模型对象)

1– 查询单个对象

(1)find方法
find方法用于查询指定主键的对象;Author.find(1)

(2) find_by方法
find_by 方法会查询到符合条件的第一条记录
Author.find_by(:name => "鲁迅") 也可以写成
Author.find_by name: "鲁迅"

(3)take方法
take方法会查询到一条记录,往往是数据表中的第一条记录。
Author.take

2– 查询多个对象

我们常常使用each方法,用来检索数据表中的记录。它的原理是会让Active Record 捞出整个数据表的数据,然后对每条记录创建模型对象,并把整个模型对象以数组形式保存在内存中。如果数据量很多的话,这样明显是行不通的。Rails有两种方法可以解决这个问题:
(1)find_each
工作原理:find_each方法每次检索一批记录,然后把每条记录实例化成模型对象传入块。默认每次检索1000条记录。

1
2
3
Player.find_each do |player|
player.name
end

a. 使用batch_size 来指定每次检索的记录总数

1
2
3
Player.find_each(batch_size: 5000) do |player|
player.name
end

b. 使用start 实现从起始点进行ID检索 ,比这个ID小的都不会取回。并且这个ID必须是主键。平常情况下较少用这个方法。

1
2
3
4
# 检索id从10开始的作者姓名
Player.find_each(start: 10) do |player|
player.name
end

c.使用finish 实现从起始点进行ID检索,比这个ID大的都不取回。与start类似。

1
2
3
4
# 检索id为10000到20000的作者姓名
Player.find_each(start: 10000, finish: 20000) do |player|
player.name
end

(2)find_in_batches
工作原理:find_in_batches 方法每次检索一批记录,然后把每批记录实例化成模型数组传入块。用法和find_each 类似。

3.条件查询

(1)数组条件中的占位符
a. ?

1
2
3
$ Player.where("age >= ?", params[:age])
或者
$ Player.where("player.age >= ?", params[:age])

b. 字符
如果条件中有很多变量,那么以下做法会更易于代码的阅读。

1
$ Player.where("age >= :age AND saler >= :wage_level_", { age: params[:age], wage_level: params[:wage_level]})

(2)使用not 反向查询
查询年龄在30岁以上的玩家

1
2
3
$ Player.where("diamond > ?", 30)
也可以写成
$ Player.where.not(" diamond <= ?", 30)

4. 查询特定字段

使用find、find_by、where方法时会返回table的全部字段,若想返回指定的字段可以使用select ,比如查询年龄大于20 的玩家名字。
(1) select方法

1
2
3
4
5
Player.select(:name).where("age >= ?", 30)
查询名字和id
Player.select(:userid, :name).where("age >= ?", 30)
&
Player.select("userid, name").where("age >= ?", 30)

(2)pluck方法
pluck 方法与select方法不同,select返回是一组对象模型,pluck方法是返回字段数组,因此在数据量很大的情况下使用pluck会好很多。

1
2
3
4
5
6
Player.select(:nickname, :userid).map{ |u| [u.userid, u.nickname]}
相当于
Player.pluck(:nickname, :userid)

PS:map 的另一种写法
Player.select(:userid).map(&:userid)

pluck方法会触发即时查询,个人理解为立即查询,所以当其他查询在pluck之前时,会执行成功;在pluck之后时,会查询失败。例如:

1
2
3
Player.pluck(:userid).limit(5) #=> undefined method `limit

Player.limit(5).pluck(:userid) #=> [1], [2], [3], [4], [5]

(3)ids方法
ids方法会获得模型的主键,rails中模型主键默认为id,返回的是数组。

1
2
3
4
5
6
7
8
9
10
11
class Player < ApplicationRecord
self.primary_key = "userid"
end

Player.ids
相当于
Player.pluck(:userid)

Player.select(:id).map(&:userid)

Player.select(:id).map{ |u| u.userid}

5.关联查询

(1) joins 方法
示例代码:

1
2
3
4
5
6
7
class Player < ApplicationRecord
has_many :groups, class_name: "GroupMsg", foreign_key: "userid"
end

class GroupMsg < ApplicationRecord
belongs_to :owner, class_name: "Player", foreign_key: "userid"
end

1
$ GroupMsg.joins(:owner)

这条语句的意思为所有属于某个用户的圈子作为一个GroupMsg对象返回。

1
2
3
$ GroupMsg.joins(:owner).where(:userid => 2)
查询结果相当于
$ Player.find_by(userid: 2).groups

1
$ Player.joins(:groups)

这条语句的意思为查询所有创建了圈子的用户作为Player对象返回(可以简单理解为查询创建过圈子的用户)。如果一个用户创建了很多圈子,那么也会被重复列出。

(2)includes方法
includes可以解决N+1queries问题,因为它可以及早加载关联。

1
Player.includes(:groups)

这条命令会取出所有的用户和所有的关联圈子。
对应的SQL语句为

1
2
3
#=> 假设只有五个用户
select * from players
select * from group_msgs where group_msgs.userid in (1,2,3,4,5)

1
GroupMsg.includes(:owner)

这个命令会捞出所有的圈子和相关联的用户。
对应的SQL语句为

1
2
select * from group_msg
select * from players where player.userid = [2,3,4]