环境:Rails 4.2.4,Postgres 9.4.1.0
是否可以保证ActiveRecord#first
方法始终返回具有最小ID和ActiveRecord#last
的记录 - 具有最大ID?
我可以从Rails控制台看到,对于这两种方法,适当的ORDER ASC/DESC
被添加到生成的SQL中。但另一个SO帖子Rails with Postgres data is returned out of order的作者告诉first
方法返回 NOT 第一条记录......
首先是ActiveRecord:
2.2.3:001> Account.first
帐户加载(1.3毫秒)SELECT"帐户"。* FROM"帐户" ORDER BY" accounts"。" id" ASC LIMIT 1
ActiveRecord last:
2.2.3:002> Account.last
帐户加载(0.8毫秒)SELECT"帐户"。* FROM"帐户" ORDER BY" accounts"。" id" DESC LIMIT 1
==========
稍后添加:
所以,我做了自己的调查(基于 D-side 答案),答案是否。一般来说,唯一的保证是first
方法将从集合中返回第一个记录。它可能作为副作用向SQL添加ORDER BY PRIMARY_KEY
条件,但它取决于是否已将任何记录加载到缓存/内存中。
这里从Rails 4.2.4中提取方法: 的 /activerecord/lib/active_record/relation/finder_methods.rb
# Find the first record (or first N records if a parameter is supplied).
# If no order is defined it will order by primary key.
# ---> NO, IT IS NOT. <--- This comment is WRONG.
def first(limit = nil)
if limit
find_nth_with_limit(offset_index, limit)
else
find_nth(0, offset_index) # <---- When we get there - `find_nth_with_limit` method will be triggered (and will add `ORDER BY`) only when its `loaded?` is false
end
end
def find_nth(index, offset)
if loaded?
@records[index] # <--- Here's the `problem` where record is just returned by index, no `ORDER BY` is applied to SQL
else
offset += index
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
以下是一些明确的例子:
Account.first # True, records are ordered by ID
a = Account.where('free_days > 1') # False, No ordering
a.first # False, no ordering, record simply returned by @records[index]
Account.where('free_days > 1').first # True, Ordered by ID
a = Account.all # False, No ordering
a.first # False, no ordering, record simply returned by @records[index]
Account.all.first # True, Ordered by ID
现在有很多关系的例子:
帐户has_many AccountStatuses,AccountStatus belongs_to帐户
a = Account.first
a.account_statuses # No ordering
a.account_statuses.first
# Here is a tricky part: sometimes it returns @record[index] entry, sometimes it may add ORDER BY ID (if records were not loaded before)
以下是我的结论:
将方法first
视为从已加载的集合中返回第一个记录(可以在任何顺序中加载,即无序)。如果我想确保first
方法将返回具有最小ID的记录 - 那么我应该在之前适当地订购我应用first
方法的集合。
关于first
方法的Rails文档是错误的,需要重写。
http://guides.rubyonrails.org/active_record_querying.html
1.1.3第一次
第一种方法查找主键排序的第一条记录。 &lt; ---不,不是!
答案 0 :(得分:1)
它可能会根据你的数据库引擎而改变,它总是使用first
方法返回mysql中的最小ID但是对于postgresql它不起作用,当我是一个nobai时,我遇到了几个问题,我的应用程序在本地使用mysql正常工作,但是当使用postgresql部署到heroku时,一切都搞砸了,所以为了避免postgresql的问题,总是在查询之前通过id命令你的记录:
Account.order(:id).first
以上确保mysql,postgresql和任何其他数据库引擎的最小ID,如您在查询中所见:
SELECT `accounts`.* FROM `accounts` ORDER BY `accounts`.`id` ASC LIMIT 1
答案 1 :(得分:1)
如果未选择排序,则行将以未指定的方式返回 为了即可。在这种情况下的实际顺序将取决于扫描和连接 计划类型和磁盘上的订单,但不得依赖。一个 只有排序步骤才能保证特定的输出顺序 明确选择。
http://www.postgresql.org/docs/9.4/static/queries-order.html(强调我的)
因此,ActiveRecord实际上通过主键(无论哪个)添加排序,以保持结果的确定性。使用pry
很容易找到相关的源代码,但这里是Rails 4.2.4的摘录:
# show-source Thing.all.first
def first(limit = nil)
if limit
find_nth_with_limit(offset_index, limit)
else
find_nth(0, offset_index)
end
end
# show-source Thing.all.find_nth
def find_nth(index, offset)
if loaded?
@records[index]
else
offset += index
@offsets[offset] ||= find_nth_with_limit(offset, 1).first
end
end
# show-source Thing.all.find_nth_with_limit
def find_nth_with_limit(offset, limit)
relation = if order_values.empty? && primary_key
order(arel_table[primary_key].asc) # <-- ATTENTION
else
self
end
relation = relation.offset(offset) unless offset.zero?
relation.limit(limit).to_a
end
答案 2 :(得分:0)
我不认为您引用的答案是相关的(甚至是它所涉及的问题),因为它指的是无序查询,而first
和last
确实应用了订单基于id
。
在某些情况下,如果您在查询中应用自己的group
,则无法使用first
或last
,因为如果分组不包含{,则无法应用订单{1}},但您可以使用id
代替返回第一行。
有take
和/或first
没有应用订单的版本(我回忆起PostgreSQL上已故的Rails 3之一),但它们都是错误。