0

I'm trying to get unique polymorphic objects by the value of one of the columns. I'm using Postgres.

The object has the following properties: id, available_type, available_id, value, created_at, updated_at.

I'm looking to get the most recent object per available_id (recency determined by created_at) for records with the available_type of "User".

I've been trying ActiveRecord queries like this:

Service.where(available_type: "User").order(created_at: :desc).distinct(:available_id)

But it isn't limiting to one per available_id.

Md. Farhan Memon
  • 6,055
  • 2
  • 11
  • 36
Andrew
  • 472
  • 2
  • 24

2 Answers2

1

Using a left join is going to be your probably most efficient way

The following sql selects only rows where there are no rows with a larger created_at.

See this post for more info: https://stackoverflow.com/a/27802817/5301717

query = <<-SQL
    SELECT m.*                    # get the row that contains the max value
    FROM services m                 # "m" from "max"
    LEFT JOIN services b        # "b" from "bigger"
        ON m.available_id = b.available_id    # match "max" row with "bigger" row by `home`
        AND m.available_type = b.available_type
        AND m.created_at < b.created_at    # want "bigger" than "max"
    WHERE b.created_at IS NULL      # keep only if there is no bigger than max
    AND service.available_type = 'User'
SQL

Service.find_by_sql(query)

distinct doesn't take a column name as an argument, only true/false.

distinct is for returning only distinct records and has nothing to do with filtering for a specific value.

if you need a specific available_id, you need to use where

e.g.

Service.distinct.where(available_type: "User").where(available_id: YOUR_ID_HERE).order(created_at: :desc)

to only get the most recent add limit

Service.distinct.where(available_type: "User").where(available_id: YOUR_ID_HERE).order(created_at: :desc).limit(1)

if you need to get the most recent of each distinct available_id, that will require a loop

first get the distinct polymorphic values by only selecting the columns that need to be distinct with select:

available_ids = Service.distinct.select(:available_id).where(available_type: 'User')

then get the most recent of each id:

recents = []
available_ids.each do |id|
  recents << Service.where(available_id: id).where(available_type: 'User').order(created_at: :desc).limit(1)
end
TheRealMrCrowley
  • 976
  • 7
  • 24
1

Try

Service.where(id: Service
              .where(available_type: "User")
              .group(:available_id)
              .maximum(:id).values)
Md. Farhan Memon
  • 6,055
  • 2
  • 11
  • 36
  • It spawns 2 consecutive queries at any point of time, independent of number of records. Whereas, the other solution is clearly n+1... – Md. Farhan Memon Jun 09 '17 at 01:44
  • This solution is not grabbing the most recent :created_at. Only the largest :id. In most cases they are the same thing, but just realize you might want to swap the :id with :created_at – TheRealMrCrowley Jun 09 '17 at 21:53
  • And remember that this is using a subquery, which has its own inefficiencies. I've update my answer with a join operation that will usually be more performant than a subquery – TheRealMrCrowley Jun 09 '17 at 21:54