在本文中,您将学习Active Record查询的基础知识,并学习有关SQL的一些基础知识。 它面向想要开始学习更多有关Ruby on Rails中的数据库查询的初学者。
主题
- 单一物件
- 多个物件
- 条件
- 定购
- 限度
- 分组与拥有
Active Record用于查询数据库。 它可以与SQL,PostgresSQL和SQLite一起使用。 为了从数据库中检索记录,您可以使用几种查找器方法。 关于它们的很酷的事情是,您可以省去编写原始SQL的麻烦。
查找器方法的真正作用是什么? 基本上有三件事:您提供的选项将转换为SQL查询。 然后执行SQL查询并从数据库中检索数据。 此外,对于该结果列表中的每一行,我们都会获得与查询相对应的模型的新实例化Ruby对象。
如果您以前没有玩过SQL,那么我会尽力使事情变得简单,并向您介绍最基本的知识。 遵循SQL示例,尝试理解这些简单的查询。 SQL确实不是火箭科学,只是语法需要一点时间来适应。 希望这会激发您的胃口,以寻找一些有用的教程来填补空白。
让我们看一下您可以使用的几种方法:
-
find
-
first
-
last
-
find_by
-
all
-
find_each
-
find_in_batches
-
where
-
order
-
limit
-
offset
-
group
-
having
所有这些都将返回ActiveRecord::Relation
的实例。 什么啊 这是一个在module ActiveRecord
命名空间的类,它使我们可以调用多个查询方法并将其链接。 该对象是Rails中使用的查询语法的核心。 让我们检查此类对象的类,然后自己看看:
滑轨
Agent.where(name: 'James Bond').class
# => ActiveRecord::Relation
单一物件
-
find
通过此方法,您可以提供对象的主要ID,并为您检索该单个对象。 如果提供ID数组,则还可以检索多个对象。
滑轨
bond = Agent.find(7)
的SQL
SELECT "agents".* FROM "agents" WHERE "agents"."id" = ? LIMIT 1 [["id", 7]]
这行SQL指出您要从agents
表中选择所有( *
)属性,并仅“过滤”具有ID 7的记录。限制使它仅从数据库返回一条记录。
-
first
,last
毫不奇怪,这些将为您提供可以通过其主键标识的第一条记录和最后一条记录。 但是,有趣的部分是您可以提供一个可选数字,该数字将返回该记录数的第一个或最后一个。
滑轨
enemy_agents = SpectreAgent.first(10)
enemy_agents = SpectreAgent.last(10)
在引擎盖下,您正在为所提供的数字提供新的限制,并按升序或降序对其进行排序。
的SQL
SELECT "spectreagents".* FROM "spectreagents" ORDER BY "spectreagents"."id" ASC LIMIT 10
SELECT "spectreagents".* FROM "spectreagents" ORDER BY "spectreagents"."id" DESC LIMIT 10
-
find_by
该查找器返回与您提供的条件匹配的第一个对象。
滑轨
bond = Agent.find_by(last_name: 'Bond')
的SQL
SELECT "agents".* FROM "agents" WHERE "agents"."last_name" = ? LIMIT 1 [["last_name", "Bond"]]
多个物件
显然,我们经常需要遍历具有某些议程的对象集合。 手动检索单个对象或选定的几个对象是不错的选择,但通常,我们希望Active Record批量检索对象。
向用户显示各种列表是大多数Rails应用程序的基础。 我们需要的是一个功能强大的工具,它具有方便的API为我们收集这些对象-希望这种方式可以使我们避免大部分时间自己编写涉及的SQL。
-
all
滑轨
mi6_agents = Agents.all
的SQL
SELECT "agents".* FROM "agents"
此方法适用于相对较小的对象集合。 尝试想象在所有Twitter用户的集合上执行此操作。 不,不是一个好主意。 相反,我们想要的是对更大的表大小进行更好地调整的方法。
获取整个表格将无法扩展! 为什么? 因为我们不仅需要一堆对象,而且还需要在该表的每行建立一个对象,并将它们放入内存中的数组。 我希望这听起来不是一个好主意! 那么对此有什么解决方案? 分批! 我们将这些集合分为多个批次,这些批次在内存中更易于处理。 oo!
让我们看一下find_each
和find_in_batches
。 两者相似,但在将对象变成块的方式上却有所不同。 他们接受调整批量大小的选项。 默认值为1,000。
-
find_each
滑轨
NewRecruit.find_each do |recruit|
recruit.start_hellweek
end
的SQL
SELECT "newrecruits".* FROM "newrecruits" ORDER BY "newrecruits"."id" ASC LIMIT 1000
在这种情况下,我们将检索默认的1,000名新员工批次,将其放到区块中,然后逐个发送到地狱周。 由于批次正在分割集合,因此我们也可以通过start
告诉它们从哪里start
。 假设我们要一次性处理3,000名新兵,并且希望从4,000名开始。
滑轨
NewRecruit.find_each(start: 4000, batch_size: 3000) do |recruit|
recruit.start_hellweek
end
的SQL
SELECT "newrecruits".* FROM "newrecruits" WHERE ("newrecruits"."id" >= 4000) ORDER BY "newrecruits"."id" ASC LIMIT 3000
重申一下,我们首先检索一批3,000个Ruby对象,然后将它们发送到块中。 start
让我们指定要开始获取该批处理的记录的ID。
-
find_in_batches
该对象将其批处理作为数组传递到块,将其传递给另一个更喜欢处理集合的对象。 这里的SQL是一样的。
滑轨
NewRecruit.find_in_batches(start: 2700, batch_size: 1350) do |recruits|
field_kitchen.prepare_food(recruits)
end
条件
-
where
我们需要先走到where
然后再继续。 这使我们可以指定限制查询返回的记录数的条件-一种用于“何处”从数据库中检索记录的过滤器。 如果您使用过SQL WHERE
子句,那么您可能会感到宾至如归-与此Ruby包装器一样。
在SQL中,这使我们可以指定要影响的表行,基本上是在满足某种条件的地方。 顺便说一下,这是一个可选的子句。 在下面的原始SQL中,我们仅选择通过WHERE
成为孤儿的新兵。
从表中选择特定的行。
的SQL
SELECT * FROM Recruits
WHERE FamilyStatus = 'Orphan';
通过where
,您可以使用字符串,哈希或数组指定条件。 将所有这些放在一起,Active Record可让您过滤以下情况:
滑轨
promising_candidates = Recruit.where("family_status = 'orphan'")
的SQL
SELECT "recruits".* FROM "recruits" WHERE (family_status = 'orphan')
很整洁吧? 我想提一下,这仍然是一个查找操作-我们只指定我们要立即过滤此列表的方式。 从所有新兵的名单中,这将返回筛选出的孤儿候选人名单。 此示例是字符串条件。 远离纯字符串条件,因为它们容易受到SQL注入的攻击,因此不被认为是安全的。
论点安全
在上面的示例中,我们将带条件的orphan
变量放入字符串中。 由于这是不安全的,因此被认为是不好的做法。 我们需要对变量进行转义以避免此安全漏洞。 如果这对您来说是个新闻,那么您应该阅读有关SQL注入的信息-您的数据库可能依赖于它。
滑轨
promising_candidates = Recruit.where("family_status = ?", 'orphan'")
?
将由参数列表中的下一个值替换为条件值。 因此,问号基本上是一个占位符。 您还可以使用多个?
指定多个条件?
并将它们链接在一起。 在现实生活中,我们将使用以下参数散列:
promising_candidates = Recruit.where("family_status = ?", params[:recruits])
如果您有大量可变条件,则应使用键/值占位符条件。
滑轨
promising_candidates = Recruit.where(
"family_status = :preferred_status
AND iq >= :required_iq
AND charming = :lady_killer",
{ preferred_status: 'orphan',
required_iq: 140,
lady_killer: true
}
)
的SQL
SELECT "recruits".* FROM "recruits" WHERE (family_status = 'orphan' AND iq >= 140 AND lady_killer = true)
当然,上面的示例很愚蠢,但是它清楚地显示了占位符表示法的好处。 通常,哈希符号绝对是更易读的符号。
滑轨
promising_candidates = Recruit.where(family_status: 'orphan')
promising_candidates = Recruit.where('charming': true)
如您所见,您可以随意使用符号或字符串。 让我们通过NOT通过范围和负条件关闭本节。
滑轨
promising_candidates = Recruit.where(birthday: ('1994-01-01'..'2000-01-01'))
两个点,您可以建立所需的任何范围。
promising_candidates = Recruit.where.not(character: 'coward')
您可以将not
塞在where
以过滤掉所有胆小鬼,并只获得不具有该特定不需要属性的结果。 在引擎盖下, !=
否定WHERE“过滤器”。
的SQL
SELECT "recruits".* FROM "recruits" WHERE ("recruits"."character" != ?) [["character", "coward"]]
定购
-
order
为了不让您对此感到无聊,让我们快一点。
candidates = Recruit.order(:date_of_birth)
candidates = Recruit.order(:date_of_birth, :desc)
应用:asc
或:desc
对其进行相应排序。 基本上就是这样,让我们继续吧!
限度
-
limit
您可以将返回的记录数减少到特定数目。 如前所述,大多数情况下,您不需要返回所有记录。 下面的示例将为您提供数据库中的前五名新兵,即前五个ID。
滑轨
five_candidates = Recruit.limit(5)
的SQL
SELECT "recruits".* FROM "recruits" LIMIT 5
-
offset
如果您曾经想过分页如何在引擎盖下工作,那么limit
和offset
结合使用)就可以完成艰苦的工作。 limit
可以独立存在,但offset
取决于前者。
设置偏移量对于分页最有用,它使您可以跳过数据库中所需的行数。 候选人列表的第二页可以像这样查找:
滑轨
Recruit.limit(20).offset(20)
SQL看起来像这样:
SELECT "recruits".* FROM "recruits" LIMIT 20 OFFSET 20
同样,我们从Recruit
数据库模型中选择所有列,将返回的记录限制为Class Recruit的20个Ruby对象,并跳过前20个。
分组与拥有
假设我们想要一个按智商分组的新兵列表。 在SQL中,这可能看起来像这样。
SELECT "recruits".* FROM "recruits" GROUP BY "recruits"."iq"
这将为您提供一个列表,您可以在其中查看哪些可能的应聘者的智商为120,然后是另一组的智商为140,依此类推-无论他们的智商如何,有多少智商都落在特定数字以下。 因此,当两名新兵的智商为130时,他们将被分组在一起。
可能患有幽闭恐惧症,对身高恐惧或在医学上不适合潜水的候选人可能会列出另一个列表。 Active Record查询将看起来像这样:
-
group
滑轨
Candidate.group(:iq)
当我们计算候选人的数量时,我们会得到一个非常有用的哈希。
滑轨
Candidate.group(:iq).count
# => { 130=>7, 134=>4, 135=>3, 138=>2, 140=>1, 141=>1 }
我们去了—我们有七名可能的新人,智商为130,而只有一名新人的智商为141。产生的SQL看起来像这样:
的SQL
SELECT COUNT(*) AS count_all, iq AS iq FROM "candidates" GROUP BY "candidates"."iq"
重要的是GROUP BY
部分。 如您所见,我们使用候选人表获取其ID。 从这个简单的示例中还可以看到,Active Record版本的读取和写入更加方便。 想象一下,在更多奢侈的示例上手动进行此操作。 当然,有时您必须这样做,但是很显然,我们一直很高兴可以避免这种痛苦。
-
having
我们可以通过使用更加指定此组HAVING
的一个过滤器的-sort group
。 从这个意义上说, having
是GROUP
的一种WHERE
子句。 换句话说, having
依赖于使用group
。
滑轨
Recruit.having('iq > ?', 134).group(:iq)
的SQL
SELECT "recruits".* FROM "recruits" GROUP BY "recruits"."iq" HAVING iq > '134'
现在,我们将候选人分类为智商最低为135的人员列表。让我们对他们进行计数以获得一些统计信息:
滑轨
Recruit.having('iq > ?', 134).group(:iq).count
# => { 135=>3, 138=>2, 140=>1, 141=>1 }
的SQL
SELECT COUNT(*) AS count_all, iq AS iq FROM "recruits" GROUP BY "recruits"."iq" HAVING iq > '134'
我们还可以将它们混合和匹配,例如,查看哪些智商高于140的候选人是否陷入了恋爱关系。
滑轨
Recruit.having('iq > ?', 140).group(:family_status)
的SQL
SELECT "recruits".* FROM "recruits" GROUP BY "recruits"."family_status" HAVING iq > '140'
现在计算这些组太容易了:
滑轨
Recruit.having('iq > ?', 140).group(:family_status).count
# => { "married"=>2, "single"=>1 }
的SQL
SELECT COUNT(*) AS count_all, family_status AS family_status FROM "recruits" GROUP BY "recruits"."family_status" HAVING iq > '140'
最后的想法
我希望这是对Active Record必须提供的有用信息,以使您的查询工作尽可能地可读和方便。 总体而言,我想说这是一个出色的包装程序,可以使您在大多数情况下不必手动编写SQL。
在下一篇文章中,我们将研究更多参与的发现者,并扩展到目前为止所学的知识。
翻译自: https://code.tutsplus.com/articles/queries-in-rails-part-1--cms-26409