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

  • 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_eachfind_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

 


 

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") 

 

 

 

 

 


posted @ 2017-12-17 11:51  Mr-chen  阅读(439)  评论(0编辑  收藏  举报