简体   繁体   中英

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.

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.

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.

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.

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

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...) :

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. As you can see, ActiveRecord knows nothing about these additional functions used, so there's no support or built-in methods to use them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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