文章目录
1从数据库中检索对象
1.1 检索单个对象
1.1.1 find
- .
client = Client.find(10)
使用 find 方法检索指定主键对应的对象,指定主键时可以使用多个选项,等同于:
SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
client = Client.find([1, 10])
,等同于:
SELECT * FROM clients WHERE (clients.id IN (1,10))
如果没有找到匹配的记录,find 方法抛出 ActiveRecord::RecordNotFound 异常。
1.1.2 take
client = Client.take
take 方法检索一条记录而不考虑排序,等同于:
SELECT * FROM clients LIMIT 1
client = Client.take(2)
take 方法接受数字作为参数,并返回不超过指定数量的查询结果。
SELECT * FROM clients LIMIT 2
1.1.3 first
client = Client.first
first 方法默认查找按主键排序的第一条记录,等同于:
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
first 方法接受数字作为参数,并返回不超过指定数量的查询结果
client = Client.first(3)
1.1.4 last 方法
client = Client.last
last 方法默认查找按主键排序的最后一条记录
last 方法接受数字作为参数,并返回不超过指定数量的查询结果
1.1.5 find_by 方法
Client.find_by first_name: 'Lifo'
SELECT * FROM clients WHERE (clients.first_name = 'Lifo') LIMIT 1
1.2 批量检索多个对象
User.all.each 会使 Active Record 一次性取回整个数据表,为每条记录创建模型对象,并把整个模型对象数组保存在内存中。事实上,如果我们有大量记录,整个模型对象数组需要占用的空间可能会超过可用的内存容量。
1.2.1 find_each 方法
每次检索一批记录,然后逐一把每条记录作为模型传入块
User.find_each do |user|
NewsMailer.weekly(user).deliver_now
end
可选字段::
- :batch_size 选项用于指明批量检索记录时一次检索多少条记录。例如,一次检索 5000 条记录:
- :start:配置想要取回的记录序列的第一个 ID,比这个 ID 小的记录都不会取回。
- :finish:用于配置想要取回的记录序列的最后一个 ID,比这个 ID 大的记录都不会取回。
- :error_on_ignore:覆盖应用的配置,指定有顺序的关系是否抛出异常。
User.find_each(start: 2000, finish: 10000,batch_size: 5000) do |user|
1.2.2 find_in_batches 方法
把一批记录作为模型数组传入块,而不是像 find_each 方法那样逐一把每条记录作为模型传入块。
Invoice.find_in_batches do |invoices|
export.add_invoices(invoices)
end
可选字段与find_each相同。
2 条件查询
2.1 纯字符串条件
Client.where("orders_count = ?", params[:orders])
注:避免SQL注入,不推荐以下写法:
Client.where("orders_count = #{params[:orders]}")
2.2 数组条件
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
2.3 散列条件
Client.where(locked: true)
或:
Client.where('locked' => true)
2.4 NOT 条件
Client.where.not(locked: true)
3 排序 order
Client.order(:created_at)
# 或
Client.order("created_at")
还可以使用 ASC(升序) 或 DESC(降序) 指定排序方式:
Client.order(orders_count: :asc, created_at: :desc)
# 或
Client.order(:orders_count, created_at: :desc)
# 或
Client.order("orders_count ASC, created_at DESC")
# 或
Client.order("orders_count ASC", "created_at DESC")
4 选择特定字段
Model.find 默认使用 select * 从结果集中选择所有字段。可以使用 select 方法从结果集中选择字段的子集。
Client.select("viewable_by, locked")
等同于:
SELECT viewable_by, locked FROM clients
在查询时如果想让某个字段的同值记录只出现一次,可以使用 distinct 方法添加唯一性约束:
Client.select(:name).distinct
等同于:
SELECT DISTINCT name FROM clients
5 限量和偏移量
limit 方法用于指明想要取回的记录数量,offset 方法用于指明取回记录时在第一条记录之前要跳过多少条记录。
Client.limit(5).offset(30)
等同于:
SELECT * FROM clients LIMIT 5 OFFSET 30
返回从第 31 条记录开始的 5 条记录。
6 分组
Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
根据订单创建日期查找订单记录
注:要想得到一次查询中分组项目的总数,可以在调用 group 方法后调用 count 方法。
Order.group(:status).count
7 having 方法
对查询到的结果,进行条件过滤
Order.select("date(created_at) as ordered_date, sum(price) as total_price"). group("date(created_at)").having("sum(price) > ?", 100)
8 条件覆盖
8.1 unscope 方法
可以使用 unscope 方法删除某些条件。 例如:
Article.where('id > 10').limit(20).order('id asc').unscope(:order)
生成的sql,等同于前面并没有加和order有关的条件过滤
SELECT * FROM articles WHERE id > 10 LIMIT 20
8.2 only 方法
可以使用 only 方法覆盖某些条件。例如:
Article.where('id > 10').limit(20).order('id desc').only(:order, :where)
生成的sql,等同于前面只加了和order,where有关的条件过滤
SELECT * FROM articles WHERE id > 10 ORDER BY id DESC
8.3 reorder 方法
class Article < ApplicationRecord
has_many :comments, -> { order('posted_at DESC') }
end
Article.find(10).comments.reorder('name')
之前在model层面定义是以posted_at进行排序,后来reorder里指定的是根据name排序,对之前的排序方式进行了覆盖的操作
SELECT * FROM articles WHERE id = 10
SELECT * FROM comments WHERE article_id = 10 ORDER BY name
8.4 reverse_order 方法
可以使用 reverse_order 方法反转排序条件。
User.order(:id)
对应的sql:
SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 11
使用 reverse_order,就可以将本来的ID升序排列反转为降序了
User.order(:id).reverse_order
SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 11
8.5 rewhere 方法
覆盖 where 方法中指定的条件
Article.where(trashed: true).rewhere(trashed: false)
生成的SQL:
SELECT * FROM articles WHERE `trashed` = 0
9 空关系
none 方法返回可以在链式调用中使用的、不包含任何记录的空关系。
User.none
返回一个空 Relation 对象,(某些特殊情况下不期望返回 [] 或 nil)而且不执行查询
10 只读对象
显式禁止修改任何返回对象。如果尝试修改只读对象,不但不会成功,还会抛出 ActiveRecord::ReadOnlyRecord 异常。
client = Client.readonly.first
11 联结表
Active Record 提供了 joins 和 left_outer_joins 这两个查找方法,用于指明生成的 SQL 语句中的 JOIN 子句。
11.1 joins 方法
Category.joins(:articles)
Author.joins("INNER JOIN posts ON posts.author_id = authors.id AND posts.published = 't'")
11.2. 单个关联的联结
Category.joins(:articles)
对应的sql
SELECT categories.* FROM categories
INNER JOIN articles ON articles.category_id = categories.id
11.3 多个关联的联结
Article.joins(:category, :comments)
11.4 单层嵌套关联的联结
Article.joins(comments: :guest)
11.5 多层嵌套关联的联结
Category.joins(articles: [{ comments: :guest }, :tags])
11.6 left_outer_joins 方法
如果想要选择一组记录,而不管它们是否具有关联记录,可以使用 left_outer_joins 方法。
Author.left_outer_joins(:posts).distinct.select('authors.*, COUNT(posts.*) AS posts_count').group('authors.id')
这个查询的意思是返回所有作者和每位作者的帖子数,而不管这些作者是否发过帖子。即左外联只要左表有记录,就将右表中与之对应的结果查询出来,而内联是坐表和右表都对应的有记录且数据值符合条件。
12 及早加载关联
Article.includes(:category, :comments).limit(10)
通过指明 includes 方法,Active Record 会使用尽可能少的查询来加载所有已指明的关联。从而解决n+1问题
13 作用域
在类中通过 scope 方法定义作用域,并传入调用这个作用域时执行的查询。
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
end
或
class Article < ApplicationRecord
def self.published
where(published: true)
end
end
我们可以在模型上调用 published 作用域:
Article.published # => [published articles]
13.1 传入参数
作用域可以接受参数:
class Article < ApplicationRecord
scope :created_before, ->(time) { where("created_at < ?", time) }
end
或:
class Article < ApplicationRecord
def self.created_before(time)
where("created_at < ?", time)
end
end
调用
Article.created_before(Time.zone.now)
当作用域需要接受参数时,推荐改用类方法(也就是通过self定义的方法)。使用类方法时,这些方法仍然可以在关联对象上访问
13.2 应用默认作用域
要想在模型的所有查询中应用作用域,我们可以在这个模型上使用 default_scope 方法。默认作用域在创建记录时同样起作用,但在更新记录时不起作用
class Client < ApplicationRecord
default_scope { where("removed_at IS NULL") }
end
14 动态查找方法
如果想同时查询 first_name 和 locked 字段,可以在动态查找方法中用 and 把这两个字段连起来,
Client.find_by_first_name_and_locked("Ryan", true)。
15 理解方法链
15.1 从多个数据表中检索过滤后的数据
Person
.select('people.id, people.name, comments.text')
.joins(:comments)
.where('comments.created_at > ?', 1.week.ago)
15.2 从多个数据表中检索特定的数据
Person
.select('people.id, people.name, companies.name')
.joins(:company)
.find_by('people.name' => 'John') # this should be the last
16 查找或创建新对象
我们经常需要查找记录并在找不到记录时创建记录,这时我们可以使用 find_or_create_by 和 find_or_create_by! 方法。
16.1 find_or_create_by 方法
find_or_create_by 方法检查具有指定属性的记录是否存在。如果记录不存在,就调用 create 方法创建记录。
Client.find_or_create_by(first_name: 'Andy')
假设我们想在新建记录时把 locked 字段设置为 false,但又不想在查询中进行设置。
- 使用 create_with
Client.create_with(locked: false).find_or_create_by(first_name: 'Andy')
- 使用块
Client.find_or_create_by(first_name: 'Andy') do |c|
c.locked = false
end
16.2 find_or_create_by! 方法
新建记录是无效的就会抛出异常
16.3 find_or_initialize_by 方法
find_or_initialize_by 方法的工作原理和 find_or_create_by 方法类似,区别之处在于前者调用的是 new 方法而不是 create 方法。这意味着新建模型实例在内存中创建,但没有保存到数据库。要想把 对象保存到数据库,还需调用 save 方法:
17 使用 SQL 语句进行查找
直接使用 SQL 语句在数据表中查找记录
Client.find_by_sql("SELECT * FROM clients")
17.1 select_all 方法
select_all 方法也会使用定制的 SQL 语句从数据库中检索对象,区别在于 select_all 方法不会对这些对象进行实例化,而是返回一个散列构成的数组,其中每个散列表示一条记录。
17.2 pluck 方法
pluck 方法用于在模型对应的底层数据表中查询单个或多个字段。它接受字段名的列表作为参数,并返回这些字段的值的数组.
Client.pluck(:id, :name)
SELECT clients.id, clients.name FROM clients
和 select 方法不同,pluck 方法把数据库查询结果直接转换为 Ruby 数组,而不是构建 Active Record 对象。
17.3 ids 方法
Person.ids
获得关联的所有 ID,也就是数据表的主键。
18 检查对象是否存在 exists?
使用 exists? 方法
Client.exists?(1)
Client.where(first_name: 'Ryan').exists?
19 计算
- average 方法:数据表中某个字段的平均值
Client.average("orders_count")
- minimum:查找数据表中某个字段的最小值
Client.minimum("age")
- maximum:数据表中某个字段的最大值
Client.maximum("age")
- sum:某个字段的所有字段值之和
Client.sum("orders_count")