简体   繁体   English

如何查询 ActiveRecord 以仅返回每个 object 的第一个实例,其中 enum = X、Y、Z?

[英]How do I query ActiveRecord to return only the first instance of each object where enum = X, Y, Z?

Our CMS has an Article model that uses an enum to define the article type.我们的 CMS 有一篇文章 model,它使用枚举来定义文章类型。

enum display_format: {
   webpage: 0, 
   blog: 1,
   interactive: 2,
   advertisement: 3,
   experience: 4, 
   product_questions: 5
} 

On our homepage I display the most recent article in each category, each currently as a separate query like so.在我们的主页上,我显示每个类别中的最新文章,每个当前都作为单独的查询,就像这样。

Article.where(display_format: :webpage).order(:pub_date).first
Article.where(display_format: :blog).order(:pub_date).first
Article.where(display_format: :interactive).order(:pub_date).first

Is there a way to return those three records in a single query?有没有办法在一个查询中返回这三个记录?

I could use Article.where(display_format: [:webpage, :blog, :interactive]).order(:pub_date) then filter the collection to get the first item of each category, but I'm hoping there's a more efficient way to do this in ActiveRecord.我可以使用Article.where(display_format: [:webpage, :blog, :interactive]).order(:pub_date)然后过滤集合以获取每个类别的第一项,但我希望有一种更有效的方法在 ActiveRecord 中执行此操作。

if you have ordered it by publication date order, you can either use .limit(n) where n is the number of most recent articles you would like to get.如果您已按发布日期顺序对其进行排序,则可以使用.limit(n)其中 n 是您想要获得的最新文章的数量。

You can also use .first(n) or .last(n) depending on how you get all the results and they will return n many of articles.您还可以使用 .first( .first(n).last(n) ,具体取决于您获得所有结果的方式,它们将返回n多篇文章。

scope :most_recent, ->(formats, num) {
    from(
      <<~SQL
      (
        SELECT articles.*, 
        row_number() OVER (
         PARTITION BY articles.display_format 
         ORDER BY articles.updated_at DESC
        ) AS rn
        FROM articles
      ) articles
      SQL
    ).where(display_format: formats).where("articles.rn <= #{num}")
  }

Article.most_recent([:x, :y, :z], 1) # get top 1 each display_format
Article.most_recent([:x, :y, :z], 10) # get top 10 each display_format

note when your db grow up and you care about performance, you should consider table partition请注意,当您的数据库长大并且您关心性能时,您应该考虑table partition

There are different ways to achieve this, and it heavily depends on the RDBMS you're using, whether you have indexes to cover the query, version, etc. But if you're using Postgres, you can use ROW_NUMBER plus OVER (PARTITION BY...) :有不同的方法可以实现这一点,这在很大程度上取决于您使用的 RDBMS,是否有索引来覆盖查询、版本等。但是如果您使用的是 Postgres,则可以使用ROW_NUMBER加上OVER (PARTITION BY...)

Article
  .from(
    Article
      .select(
        :display_format,
        :pub_date,
        'ROW_NUMBER() OVER(PARTITION BY articles.display_format ORDER BY articles.pub_date DESC) AS idx'
      )
      .where(display_format: [0, 1, 2]),
    :articles
  )
  .where(idx: 1)

In such cases there's no problem in using a "raw" SQL string, if taking proper care of binding any value you receive.在这种情况下,使用“原始” SQL 字符串没有问题,如果适当注意绑定您收到的任何值。 As you can see, ActiveRecord knows nothing about these additional functions used, so there's no support or built-in methods to use them.如您所见,ActiveRecord 对使用的这些附加功能一无所知,因此没有支持或内置方法来使用它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM