【问题标题】:ORDER BY and DISTINCT ON (...) in RailsRails 中的 ORDER BY 和 DISTINCT ON (...)
【发布时间】:2014-12-11 05:15:37
【问题描述】:

我正在尝试按 created_at 排序,然后根据外键获取 DISTINCT 集。

另一部分是使用 ActiveModelSerializer。具体来说,我希望能够声明:

has_many :somethings

在序列化程序中。让我进一步解释......

我可以使用这个自定义 sql 获得我需要的结果:

def latest_product_levels
  sql = "SELECT DISTINCT ON (product_id) client_product_levels.product_id,       
  client_product_levels.* FROM client_product_levels WHERE client_product_levels.client_id = #{id} ORDER BY product_id, 
  client_product_levels.created_at DESC";
  results = ActiveRecord::Base.connection.execute(sql)
end

是否有任何可能的方法来获得此结果,但作为 has_many 关系的一个条件,以便我可以在 AMS 中使用它?

在伪代码中:@client.products_levels 会做类似的事情:@client.order(created_at: :desc).select(:product_id).distinct

这当然会因为我无法理解的原因而失败。

任何帮助都会很棒。

谢谢。

【问题讨论】:

    标签: ruby-on-rails postgresql active-model-serializers


    【解决方案1】:

    构建此结构的一个好方法是将查询分成两部分:第一部分管理行过滤,以便您仅获得最新的客户端产品级别。第二部分使用标准的has_many 关联将ClientClientProductLevel 连接起来。

    从您的 ClientProductLevel 模型开始,您可以创建一个范围来执行最新过滤:

    class ClientProductLevel < ActiveRecord::Base
      scope :latest, -> {
        select("distinct on(product_id) client_product_levels.product_id,
                                        client_product_levels.*").
        order("product_id, created_at desc")
      }
    end
    

    您可以在有返回 ClientProductLevel 对象列表的查询的任何地方使用此范围,例如,ClientProductLevel.latestClientProductLevel.where("created_at &lt; ?", 1.week.ago).latest 等。

    如果您还没有这样做,请使用 has_many 关系设置您的 Client 类:

    class Client < ActiveRecord::Base
      has_many :client_product_levels
    end
    

    然后在你的 ActiveModelSerializer 中试试这个:

    class ClientSerializer < ActiveModel::Serializer
      has_many :client_product_levels
    
      def client_product_levels
        object.client_product_levels.latest
      end
    end
    

    当您调用ClientSerializer 来序列化Client 对象时,序列化程序会看到has_many 声明,它通常会将其转发给您的Client 对象,但由于我们有一个本地定义的方法该名称,而是调用该方法。 (请注意,这个has_many 声明与ActiveRecord has_many 不同,后者指定了表之间的关系:在这种情况下,它只是说序列化程序应该在键“client_product_levels”下呈现一个序列化对象数组。 )

    ClientSerializer#client_product_levels 方法依次从客户端对象调用has_many 关联,然后将latest 范围应用于它。 ActiveRecord 最强大的地方在于它允许您将不同的组件链接到一个查询中。在这里,has_many 生成 `where client_id = $X' 部分,作用域生成查询的其余部分。瞧!

    在简化方面:ActiveRecord 没有对distinct on 的原生支持,因此您只能使用自定义 sql 的那一部分。我不知道您是否需要在您的选择子句中明确包含client_product_levels.product_id,因为它已经包含在* 中。您可以尝试转储它。

    【讨论】:

    • 谢谢!这行得通。我知道这有点离题,但你介意解释一下这是如何工作的吗?查看 ClientProductLevel 上的 sql 看起来它只会返回全球最新的。我看到实际生成的 sql:SELECT distinct on(product_id) client_product_levels.product_id, client_product_levels.* FROM "client_product_levels" WHERE "client_product_levels"."client_id" = $1 ORDER BY product_id, created_at desc [["client_id", 312]]。只是想了解它是如何在范围和 has_many 之间创建的
    • 另外,有没有办法在没有自定义 sql 的情况下构建这个查询?
    • 不客气!很高兴它奏效了。我将在上面的答案中提供更多解释。 AFAIK 的 ActiveRecord 不支持 Postgres distinct on 机制,并且仅使用 AR 原生支持的功能来构建查询的替代方法完全是一团糟。
    • 谢谢!很好的解释。感谢您抽出宝贵时间!
    猜你喜欢
    • 2019-01-08
    • 2012-04-05
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多