通过PostgreSQL和Rails 3查找按字段排序的唯一记录?

时间:2011-12-09 01:40:13

标签: sql ruby-on-rails-3 postgresql activerecord arel

更新:感谢@Erwin Brandstetter,我现在有了这个:

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

......这似乎完全符合我的要求。然而,我仍然希望解决两个缺点:

  1. order()子句使用的是直接SQL而不是Arel(无法弄清楚)。
  2. 在上面的查询中调用.count会给我这个错误:

      

    NoMethodError:未定义的方法'to_sym'   #<阿雷尔::属性::属性:0x007f870dc42c50>从   /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'

    ...我认为这可能与我如何映射users_columns有关,因此我不必在group子句中手动输入所有这些内容。

  3. 如何解决这两个问题?


    原始问题

    这是我到目前为止所解决的问题的第一部分:

    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
    

    这给了我84条独特的记录,这是正确的。

    问题是我需要User排序的cards[:created_at]条记录(对于该特定用户而言最早)。在上述方法结束时将.order(cards[:created_at])附加到范围绝对没有。

    我尝试添加.joins(:cards),但这会返回587条记录,这是不正确的(重复User s)。 group_by据我所知,由于how PostgreSQL handles it,它在这里几乎没用。

    我需要我的结果是ActiveRecord::Relation(因此它是可链接的),它返回一个唯一用户列表,这些用户拥有属于给定公司的卡片,按创建日期排序他们的第一张卡片......用Ruby编写的查询与数据库无关。我怎么能这样做?


    class Company
      has_many :cards
    end
    
    class Card
      belongs_to :user
      belongs_to :company
    end
    
    class User
      has_many :cards
    end
    

    如果您需要任何其他信息,或者我的问题不清楚,请告诉我。

2 个答案:

答案 0 :(得分:4)

您要查找的查询应如下所示:

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)

如果您需要在结果中显示该表的列,则可以加入表user。否则您甚至不需要它用于查询。
如果min_created_at列表中不需要SELECT,则可以将其删除。

应该很容易翻译成Ruby(我不擅长)。


获取整个用户记录(因为我从您的评论中获得):

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

或者:

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)

使用PostgreSQL 9.1+,您只需编写:

GROUP  BY u.id

(就像在MySQL中一样)..提供id是主键。

我引用release notes

  

在主要时,允许查询目标列表中的非GROUP BY列   key在GROUP BY子句中指定(Peter Eisentraut)

     

SQL标准允许此行为,并且由于主键,   结果是明确的。

答案 1 :(得分:1)

您需要它可链接的事实使事情变得复杂,否则您可以自己下载到SQL或只通过select("users.id")选择所需的列来解决Postgres问题。因为它的核心是你的查询类似

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

在Arel语法中或多或少:

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")