- http://guides.rubyonrails.org/active_record_querying.html
✅How to find records using a variety of methods and conditions.
✅How to specify the order, retrieved attributes,grouping, and other properties of the found records.
✅ How to use eager loading(预先积极加载) to reduce the number of database queries needed for data retrieval.
✅ How to use dynamic finder methods.
✅ How to use method chaining to use multiple Active Record methods together.✅ How to check for the existence of particular records
✅ How to perform to do sth useful or difficult various calculations on Active Record models.
✅ How to run EXPLAIN on relations.(如何在关联上跑explain命令)
Active Record will perform queries on the database for you and is compatible(兼容) with most database systems, including MySQL,SQLite ,etc..
1 .Retrieving Objects from the Database
The methods are: find ,where,select,group,20多种。
Finder methods that return a collection, such as where and group, return an instance of ActiveRecord::Relation.
Methods that find a single entity, such as find and first, return a single instance of the model.
The primary operatin of Model.find(options) can be summarized as:
- 根据options转化成SQL语句,激发查询语句并从数据库返回结果
- 从数据库返回的模型的每行结果实例化为相关的Ruby对象
- Run after_find and then after_initialize callbacks, if any.
1.1Retrieving single objects
1.11 find
通过指定primary key来检索object。可以同时检索多个primary key ,并返回一个数组,数组内包含所以匹配的记录。例子:
client = Client.find([1,10]) //或者写Client.find(1,10)
#=> [#
等同于:
SELECT * FROM clients WHERE(clients.id IN (1,10))
如果primary key没有匹配的记录,find method会抛出ActiveRecord::RecordNotFound异常
1.12 take(数字)
The take method retrieves a record without any implicit ordering .例子:
参数是几,就返回几条记录。
client = Client.take
# => #
|
等同于:
SELECT
*
FROM
clients LIMIT 1
|
如果没有检索到哪怕一条数据就会返回 nil.
1.13 first
find the first record ordered by primary key(default).
如果没有检索到哪怕一条数据就会返回 nil.可以传入数字参数,根据数字,返回对应的记录数量。
可以和order连用。
first! ,if no matching record is found, it will rasie ActiveRecord::RecordNotFound
1.14 last
用法和first一样,sql语句是按照 DESC 降序排列。
1.15 find_by
finds the first record matching some conditions.例子:
Client.find_by first_name: 'XXX'
等同于
Client.where(first_name: 'xxx').take //take是取where找到的第一个数据。
1.2 Retrieving Multiple Objects in Batches
分批取回大量对象,多次检索,每次检索的记录数量有限制。默认1000条。
当dadabase非常大的时候,User.all.each 。。end 会占用超大内存,和时间。所以Rails提供了2个method:
find_each和find_in_batches,1000条以上记录,用这个比较好。batch processing 批处理
1.2.1 find_each 方法
User.find_each(可选参数)
do
|user|
NewsMailer.weekly(user).deliver_now
end
|
有3个options for find_each:
- :batch_size: 指定每次检索返回多少条数据.
- :start and :finish 根据primary id,确定开始和结束。
find_in_batches()方法,和find_each方法的区别
find_in_batches()每次返回的是一个数组,里面是模型对象的集合。
find_each()每次返回的是独立数量的模型对象,没有用数组括起来。
2 conditions --where()
不要用pure string,容易被injection exploits.注射攻击剥削。
㊗️:find和find_by method在ruby和rails 中已经自动可以规避这个问题。
攻击方法:在where的查询中添加一些string(有多种方法),来绕过登陆验证,获取数据 。
⚠️ :where, find_by_sql, connection.execute()等条件片段的查询,需要手动避免注入。 好的方法就是只用数组,或hash.
Returns a new relation, which is the result of filtering the current relation according to the conditions in the arguments
2.2 Array Conditions
Clinet.where("orders_count = ?", params[:orders])
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
placeholder, 可以在条件字符串中指定keys,之后附加相关的keys/values对儿。
Client.where("created_at >= :start_date AND created_at < :end_date", {start_date: params[:start_date], end_date: params[:end_date]})
2.3 Hash Condititons 具体看api
可以用Equality Conditions,Range Conditions, Subset Conditions.
Client.where(locked: true)
-> select * from clients where(clients.locked = 1)
Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)
->SELECT
*
FROM
clients
WHERE
(clients.created_at
BETWEEN
'2008-12-21 00:00:00'
AND
'2008-12-22 00:00:00'
)
Client.where(orders_count: [1,3,5])
-> SELECT
*
FROM
clients
WHERE
(clients.orders_count
IN
(1,3,5))
在 belongs_to relationship, an association key can be used to specify the model if an ActiveRecord object is used as the value.
for example:
author = Author.find(1); // author 被定义为一个关联的对象
Post.where(author_id: author) //也可以写成:where(author: author) 但前者容易理解。
NOT
Client.where.not(locked: true)
-> select * from clients where(clients.locked != 1)
OR
Client.where(locked: true).or(Client.where(orders_count: [1,3,5]))
->select * from clients where(clients.locked = 1 or clients.orders_count in (1,3,5))
3 Ordering
Client.order(created_at:
:desc
)
相当于MySQL:
SELECT * from client ORDER BY created_at ASC;
Client.order("orders_count ASC").order("created_at DESC")
-> select * from clients order by orders_count asc, created_at desc
4 Selecting Specific Fields
select用于从结果集中选择字段的子集,就是只捞出column "vewable_by"和“locked”的值,其他列不捞出。
Client.select(
"viewable_by, locked"
)
|
等同于
SELECT
viewable_by, locked
FROM
clients
|
5 Limit and Offset
You can use limit to specify the number of records to be retrieved,
Client.limit(5) 等同于 SELECT * FROM clients LIMIT 5
如果使用offset
Client.limit(
5
).offset(
30
)将返回5条记录,从31st开始。
⚠️ :用offset必须先用limit
6
Group
To apply a Group by clause to the SQL fired by the finder, you can use the group method.
Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
->
select date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at)
7 Having方法
用having进行group by的条件限制。例子:
Order.select(
"date(created_at) as ordered_date, sum(price) as total_price"
).
group(
"date(created_at)"
).having(
"sum(price) > ?"
,
100
)
|
等同于:
SELECT
date
(created_at)
as
ordered_date,
sum
(price)
as
total_price
FROM
orders
GROUP
BY
date
(created_at)
HAVING
sum
(price) > 100
This returns the data and total price for each order object, grouped by the day they were created and where the price is more than $100.
上面的语句返回每个order对象的日期和总价,查询结果按照日期分组并排序(合并?),并且总价需要大于100.
8 Overriding Conditions 覆盖条件(对条件进行限制)
8.1 unscope
移除指定的条件。
Article.where(
'id > 10'
).limit(
20
).order(
'id asc'
).unscope(
:order
)等同于:
SELECT
*
FROM
articles
WHERE
id > 10 LIMIT 20
|
8.2 only
指定用哪个查询条件。 和unscope正相反。
8.3 reorder :
The reorder method overrides the default scope order.当建立了一个一对多关联并指定排列的顺序后,想要在查询某个一对多的对象的实例上从新排序(不使用在model中申明的):可以用reorder.
class
Article < ApplicationRecord
has_many
:comments
, -> {order(
'posted_at DESC'
) }
end
Article.find(
10
).comments.reorder(
'name'
)
|
SQL语句会被执行executed:
SELECT
*
FROM
articles
WHERE
id = 10
SELECT
*
FROM
comments
WHERE
article_id = 10
ORDER
BY
name
|
8.4 reverse_order 配合reorder或者order,升序变降序。
9 Null Relation
Article.none
# returns an empty Relation and fires no queries.返回nil, []
|
10 Readonly Objects
当一个对象被设置为只读,他的属性不能被修改。
client = Client.readonly.first
client.visits += 1
client.save #这时会报错❌,raise ActiveRecord::ReadOnlyRecord exception
11 Locking Records for Update
积极锁,和消极锁。 防止在数据库更新时,出现混乱情况。
(没仔细看。)
积极锁允许多个用户同时更新同一条记录,并假定产生数据冲突的可能性最小。其原来是检查读取记录后看是否有其他进程尝试更新记录,如果有就抛出异常。
为了使用乐观锁,表格有一个整数型字段lock_version。每次记录更新,会同步增加这个字段的值。 如果更新请求中字段的值比当前数据库的字段的值小,更新请求失败,抛出异常。
抛出异常后,需要救援异常并处理冲突,或回滚或合并或使用其他逻辑来解决冲突。
12 Joining Tables 联结表
连个方法:
- joins: 对应SQL的 INNER JOIN ...ON
- left_outer_joins: 对应LEFT OUTER JOIN
1 使用字符串 SQL 片段
Author.joins("INNER JOIN posts ON posts.author_id = authors.id")
->
SELECT authors.* FROM authors INNER JOIN posts ON posts.author_id = authors.id
2 使用 Array/Hash of Named Associations使用具名关联数组或散列
如果连个model已经建立了关联:
Category.joins(:articels)
-》
select categories.* from categories inner join articles on articles.category_id = categories.id
会返回一个包含category 对象的数组,如过有多个article和一个category关联,则返回的这个数组中会出现多次这个category对象。
可以附加destinct方法 ,这样重复的category对象不会出现了。
3. 多个关联的联结
category has_many :articles
articles has_many :comments
Article.joins(:category, :comments)
->
select articles.* from articles
inner join categories on articles.category_id = categories.id
inner join comments on comments.artice_id = articles.id
解释:把属于某个目录并至少有一条评论的文章作为一个Article对象返回。
同样,拥有多个评论的一篇文章会在Article对象的数组中出现多次。
4单层嵌套关联的联结⚠️ 没细看
Article.joins(comments: :guest)
5 为joining table 指明条件
可以使用array, string条件 作为关联数据表的条件。
散列需要特殊的语法:
time_range = (Time.now。midnight - 1.day)..Time.now.midnight
Client.joins(:orders).where("orders.created_at" => time_range)
或者
Client.joins(:orders).where(orders: {created_at: time_range})
select clients.* from clients
inner join orders on clients.order_id = orders.id
where orders.created_at between "XXX时间" and "xxx时间"
解释:查找昨天创建过订单的所有客户,在生成的SQL中使用了between..and..
13 Eager Loading Associations
一种查找机制:目的是减少查询数据库的次数。
client has_one :address
clients = Client.limit(10)
clients.each do |c|
puts c.address.postcode
end
先查询10条clients记录,然后每条记录执行关联查询,一个调用了数据库11次。
为了提高效能:使用includes()方法。
clients = Client.includes(:address).limit(10)
这行代码执行了2次数据库。
1. select * from clients limit 10
2. select addresses.* from addresses where(addresses.client_id IN (1,2,3,..10))
14 Scopes
把常用的查询定义为方法,可以在关联对象或模型上作为方法调用。
总会返回一个ActiveRecord::Relation object
scope :published, -> {where(published: ture)}
完全等同于类方法:
def self.publish
where(published: true)
end
在作用域中可以链接其他scope。
如:
Article.published #作为类方法使用
category.articles.published #在关联对象上调用。
14.1 传递参数。
scope :created_before, ->(time) {where("created_at < ?", time)}
14.2 可以在{}内使用 if.
谨慎使用,如果if 的结果是false,会返回nil,并导致NoMethodError❌
14.3 默认作用域
default_scope:在模型的任意查询中会自动附加上默认作用域、
default_scope {where("removed_at IS NULL")}
⚠️更新记录时,不会加上这个方法。
⚠️default_scope方法总在自定义的scope和where方法前起作用
14.4 合并作用域
两个scope连接起来,相当于使用了AND来合并作用域。
14.5 unscoped方法:删除所有的作用域。
Client.unscoped.all只执行常规查询 -》 select * from clients
Client.where(published:false).unscoped.all
不会执行where条件查询,把where当成了scope.
15 Dynamic Finders
find_by_*()方法。其实就是find_by(key/value)
16 enum
给一个类型是integer的列,指定一组值作为选项。
create_table :conversations do |t|
t.column :status, :integer, default: 0
end
class Conversation < ActiveRecord::Base
enum status: { active: 0, archived: 1 }
end
17. Understanding the chain方法链连接多个ActiveRecord方法。
Person
.select('people.id, people.nam')
.joins(:comments)
.where('comments.created_at > ?', 1.week.ago)
等同
SELECT people.id, people.name
FROM people
INNER JOIN comments ON comments.people_id = people.id
WHERE comments.created_at > "2015-01-01"
19 可以使用完全的SQL:
find_by_sql() ,返回对象的数组。
sellect_all(),和find_by_sql()是近亲,区别是返回的是散列构成的数组,每行散列表示一条记录,没有被实例化。
pluck(),返回查询字段的值的数组。
Client.where(active: true) .pluck(:id)
-> select id from clients where active =1 #=> [1, 2, 3]
pluck()方法相当于使用select().map{|x| x.attr}
Client.select(:id, :name).map{|c| [c.id, c.name]}
-> Client.pluck(:id, :name)
⚠️,pluck()返回的是数组,所以不能放在查询方法链的中间,只能放置在最后。
20 ids方法
获得关联的所有ID, 数据表的primary_key
collection_singular_ids 这是has_many中的选项。返回关联对象的id的数组。\
@book_ids = @author.book_ids
21 exists?()方法,看是否存在记录。
也可以拥有关联记录collections.exists?()
也可以用于表的查询: 返回true/false,可以接受多个参数,
Client.exists?(1) #id等1的客户是否存在。
any? 是否存在一条记录
many? 是否存在2条(含)以上的记录。
21 calculation
Client.count
-> select count(*) as count_all from clients 算有多少条记录
Client.where(first_nam: "Tom").count
-> select count(*) as count_all from clients where(first_name = 'Tom')
算名字是Tom 的记录的数量。
Client.count(:age),返回有age字段的记录的数量
Client.average("orders_count") , 返回字段的平均值
Client.minimum("age")
Client.maximum("age")
Client.sum("orders_count")