【问题标题】:Why is Foo.first returning the last record?为什么 Foo.first 返回最后一条记录?
【发布时间】:2012-05-01 00:37:43
【问题描述】:

我在Foo 中有 2 条记录,id 分别为 1 和 2。两者都是按此顺序创建的。请记住,在 Postgres 中,记录没有固有的顺序。

在 Rails 控制台中。 Foo.first 和 Foo.last 返回最后一条记录。我的印象是Foo.first 会返回第一条记录。

这就是问题所在。 SQL 查询如下所示:

SELECT "foos".* FROM "foos" LIMIT 1
SELECT "foos".* FROM "foos" ORDER BY "foos"."id" DESC LIMIT 1

第二个查询 (Foo.last) 有一个 ORDER BY DESC。那么为什么 AR 没有 ORDER BY ASC 对应 .first 呢?这背后的逻辑是什么?似乎有点“不一致”。

我可以通过以下方式轻松解决此问题:Foo.order('id ASC').first。但正在寻找解释。

【问题讨论】:

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


    【解决方案1】:

    它没有任何逻辑,如果 first(或 last 对此)有任何意义,那么如果您忽略指定显式顺序作为参数first 或作为当前作用域链的一部分。在关系数据库的上下文中,first 和 last 都没有任何意义,除非明确指定了顺序。

    我的猜测是,如果没有明确的order by,写first 的人就认为order by whatever_the_pk_is 是隐含的。然后他们可能做了一些实验来凭经验验证他们的假设,并且它恰好与他们检查的特定表和数据库的预期一样工作(迷你咆哮:这就是为什么你永远不会假设未指定的行为;如果一个特定的行为是'未明确指定,即使当前实现的行为方式或经验证据表明它的行为方式,也不要假设它。

    如果你追踪一个简单的M.first,你会发现it does this:

    limit(1).to_a[0]
    

    没有显式排序,因此您可以获得数据库感觉使用的任何随机排序,可能是order by pk,也可能是表在磁盘上的块顺序。如果您通过M.last 追踪,您将到达find_last:

    def find_last
      #...
            reverse_order.limit(1).to_a[0]
      #...
    end
    

    还有reverse_order:

    def reverse_order
      relation = clone
      relation.reverse_order_value = !relation.reverse_order_value
      relation
    end
    

    @reverse_order_value 实例变量未初始化,因此它将以 nil 开头,! 会将其转换为 true。如果你想知道@reverse_order_value 是如何使用的,你会找到reverse_sql_order:

    def reverse_sql_order(order_query)
      order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
      #...
    

    还有作者关于订购的无效假设暴露给所有人看。那行应该是:

    raise 'Specify an order you foolish person!' if order_query.empty?
    

    我建议您始终使用.order(...).limit(1).first 而不是first 或last,这样一切都很好而且明确;当然,如果您想要last,您可以反转.order 条件。或者你可以一直说 .first(:order => :whatever) 和 .last(:order => :whatever) 再次明确所有内容。

    【讨论】:

    • 我认为在数据库调用中使用 .last 或 .first 意味着 LIMIT 1(至少在我使用的 Rails 版本中)。
    • @Chris:问题是.first 和.last 没有明确的 ORDER BY 可能是模棱两可的。在.last 的情况下,颠倒排序条件可能需要人工干预,以避免将结果阵列化以剥离最后一个。这就是为什么我说你最好把一切都说清楚。
    【解决方案2】:

    对于Rails 4+版本,如果你不定义任何顺序,它将按主键排序。

    # Find the first record (or first N records if a parameter is supplied).
    # If no order is defined it will order by primary key.
    #
    #   Person.first # returns the first object fetched by SELECT * FROM people
    #   Person.where(["user_name = ?", user_name]).first
    #   Person.where(["user_name = :u", { u: user_name }]).first
    #   Person.order("created_on DESC").offset(5).first
    #   Person.first(3) # returns the first three objects fetched by SELECT * FROM people LIMIT 3
    def first(limit = nil)
      if limit
        if order_values.empty? && primary_key
          order(arel_table[primary_key].asc).limit(limit).to_a
        else
          limit(limit).to_a
        end
      else
        find_first
      end
    end
    

    来源:https://github.com/rails/rails/blob/4-0-stable/activerecord/lib/active_record/relation/finder_methods.rb#L75-L82

    【讨论】:

      猜你喜欢
      • 2018-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      相关资源
      最近更新 更多