Finding unique records, ordered by field in association, with PostgreSQL and Rails 3?

混江龙づ霸主 提交于 2020-01-02 08:56:30

问题


UPDATE: So thanks to @Erwin Brandstetter, I now have this:

def self.unique_users_by_company(company)
  users = User.arel_table
  cards = Card.arel_table

  users_columns = User.column_names.map { |col| users[col.to_sym] }

  cards_condition = cards[:company_id].eq(company.id).
    and(cards[:user_id].eq(users[:id]))

  User.joins(:cards).where(cards_condition).group(users_columns).
    order('min(cards.created_at)')
end

... which seems to do exactly what I want. There are two shortcomings that I would still like to have addressed, however:

  1. The order() clause is using straight SQL instead of Arel (couldn't figure it out).
  2. Calling .count on the query above gives me this error:

    NoMethodError: undefined method 'to_sym' for #<Arel::Attributes::Attribute:0x007f870dc42c50> from /Users/neezer/.rvm/gems/ruby-1.9.3-p0/gems/activerecord-3.1.1/lib/active_record/relation/calculations.rb:227:in 'execute_grouped_calculation'

    ... which I believe is probably related to how I'm mapping out the users_columns, so I don't have to manually type in all of them in the group clause.

How can I fix those two issues?


ORIGINAL QUESTION:

Here's what I have so far that solves the first part of my question:

def self.unique_users_by_company(company)
  users = User.arel_table
  cards = Card.arel_table

  cards_condition = cards[:company_id].eq(company.id)
    .and(cards[:user_id].eq(users[:id]))

  User.where(Card.where(cards_condition).exists)
end

This gives me 84 unique records, which is correct.

The problem is that I need those User records ordered by cards[:created_at] (whichever is earliest for that particular user). Appending .order(cards[:created_at]) to the scope at the end of the method above does absolutely nothing.

I tried adding in a .joins(:cards), but that give returns 587 records, which is incorrect (duplicate Users). group_by as I understand it is practically useless here as well, because of how PostgreSQL handles it.

I need my result to be an ActiveRecord::Relation (so it's chainable) that returns a list of unique users who have cards that belong to a given company, ordered by the creation date of their first card... with a query that's written in Ruby and is database-agnostic. How can I do this?


class Company
  has_many :cards
end

class Card
  belongs_to :user
  belongs_to :company
end

class User
  has_many :cards
end

Please let me know if you need any other information, or if I wasn't clear in my question.


回答1:


The query you are looking for should look like this one:

SELECT user_id, min(created_at) AS min_created_at
FROM   cards
WHERE  company_id = 1
GROUP  BY user_id
ORDER  BY min(created_at)

You can join in the table user if you need columns of that table in the result, else you don't even need it for the query.
If you don't need min_created_at in the SELECT list, you can just leave it away.

Should be easy to translate to Ruby (which I am no good at).


To get the whole user record (as I derive from your comment):

SELECT u.*,
FROM   user u
JOIN  (
    SELECT user_id, min(created_at) AS min_created_at
    FROM   cards
    WHERE  company_id = 1
    GROUP  BY user_id
    ) c ON u.id = c.user_id
ORDER  BY min_created_at

Or:

SELECT u.*
FROM   user u
JOIN   cards c ON u.id = c.user_id
WHERE  c.company_id = 1
GROUP  BY u.id, u.col1, u.col2, ..   -- You have to spell out all columns!
ORDER  BY min(c.created_at)

With PostgreSQL 9.1+ you can simply write:

GROUP  BY u.id

(like in MySQL) .. provided id is the primary key.

I quote the release notes:

Allow non-GROUP BY columns in the query target list when the primary key is specified in the GROUP BY clause (Peter Eisentraut)

The SQL standard allows this behavior, and because of the primary key, the result is unambiguous.




回答2:


The fact that you need it to be chainable complicates things, otherwise you can either drop down into SQL yourself or only select the column(s) you need via select("users.id") to get around the Postgres issue. Because at the heart of it your query is something like

SELECT users.id
FROM users 
INNER JOIN cards ON users.id = cards.user_id
WHERE cards.company_id = 1
GROUP BY users.id, DATE(cards.created_at)
ORDER BY DATE(cards.created_at) DESC

Which in Arel syntax is more or less:

User.select("id").joins(:cards).where(:"cards.company_id" => company.id).group_by("users.id, DATE(cards.created_at)").order("DATE(cards.created_at) DESC")


来源:https://stackoverflow.com/questions/8440094/finding-unique-records-ordered-by-field-in-association-with-postgresql-and-rai

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!