【问题标题】:Does ActiveRecord#first method always return record with minimal ID?ActiveRecord#first 方法是否总是返回具有最小 ID 的记录?
【发布时间】:2015-11-25 13:29:46
【问题描述】:

环境: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.3ms) SELECT "accounts".* FROM "accounts" ORDER BY "accounts"."id" ASC LIMIT 1

ActiveRecord 上次:

2.2.3 :002 > Account.last
帐户负载 (0.8ms) SELECT "accounts".* FROM "accounts" ORDER BY "accounts"."id" DESC LIMIT 1

===========

稍后添加:

所以,我做了自己的调查(基于D 方面 的答案),答案是NO。一般来说,唯一的保证是 first 方法将从集合中返回 first 记录。它可能会作为一个副作用ORDER BY PRIMARY_KEY 条件添加到 SQL,但这取决于记录是否已加载到缓存/内存中。

以下是从 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

现在有多个关系的例子:

Account has_many AccountStatuses, AccountStatus belongs_to Account

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 记录(可以按 any 顺序加载,即无序)。如果我想确保 first 方法将返回具有最小 ID 的记录 - 那么我应用 first 方法的集合应该在之前适当地排序。

关于first 方法的Rails 文档是错误的,需要重写。 http://guides.rubyonrails.org/active_record_querying.html

1.1.3先

第一种方法查找按主键排序的第一条记录。

【问题讨论】:

    标签: postgresql ruby-on-rails-4 activerecord


    【解决方案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】:

      它可能会根据您的数据库引擎而改变,它总是使用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
      

      【讨论】:

        【解决方案3】:

        我不认为您引用的答案是相关的(甚至与它所在的问题有关),因为它指的是无序查询,而 firstlast 确实应用了基于 id 的订单.

        在某些情况下,如果您在查询中应用自己的group,则不能使用firstlast,因为如果分组不包括id,则无法应用排序依据,但您可以使用 take 而不是只返回第一行。

        有些版本 first 和/或 last 没有应用该命令(我记得是 PostgreSQL 上的最新 Rails 3 之一),但它们是错误的。

        【讨论】:

        • 那么,相信first 将返回ID 最低的记录和high - 最高的记录是否安全?
        • high 你的意思是last,但是是的。
        猜你喜欢
        • 2020-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多