Active Record Query Interface 数据查询接口(界面) 看到第8节。

时间:2022-05-24 03:22:02
  • 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)

#=> [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
 等同于:

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
# => #<Client id: 1, first_name: "Lifo">

等同于:

SELECT * FROM clients LIMIT 1

如果没有检索到哪怕一条数据就会返回 nil.

1.13 first

find the first record ordered by primary key(default).

如果没有检索到哪怕一条数据就会返回 nil.

可以传入数字参数,根据数字,返回对应的记录数量。

Product.first(2) 等同于 

SELECT  "products".* FROM "products" ORDER BY "products"."id" ASC LIMIT ?  [["LIMIT", 2]]

可以和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 联结表

连个方法:

  1. joins: 对应SQL的 INNER JOIN ...ON
  2. 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")