Rails 3.1 with PostgreSQL: GROUP BY must be used in an aggregate function

前端 未结 4 1814
再見小時候
再見小時候 2020-12-15 12:30

I am trying to load the latest 10 Arts grouped by the user_id and ordered by created_at. This works fine with SqlLite and MySQL, but gives an error on my new PostgreSQL data

4条回答
  •  情话喂你
    2020-12-15 12:39

    The sql generated by the expression is not a valid query, you are grouping by user_id and selecting lot of other fields based on that but not telling the DB how it should aggregate the other fileds. For example, if your data looks like this:

    a  | b
    ---|---
    1  | 1
    1  | 2
    2  | 3
    

    Now when you ask db to group by a and also return b, it doesn't know how to aggregate values 1,2. You need to tell if it needs to select min, max, average, sum or something else. Just as I was writing the answer there have been two answers which might explain all this better.

    In your use case though, I think you don't want a group by on db level. As there are only 10 arts, you can group them in your application. Don't use this method with thousands of arts though:

     arts = Art.all(:order => "created_at desc", :limit => 10)
     grouped_arts = arts.group_by {|art| art.user_id}
     # now you have a hash with following structure in grouped_arts
     # { 
     #    user_id1 => [art1, art4],
     #    user_id2 => [art3],
     #    user_id3 => [art5],
     #    ....
     # }
    

    EDIT: Select latest_arts, but only one art per user

    Just to give you the idea of sql(have not tested it as I don't have RDBMS installed on my system)

    SELECT arts.* FROM arts
    WHERE (arts.user_id, arts.created_at) IN 
      (SELECT user_id, MAX(created_at) FROM arts
         GROUP BY user_id
         ORDER BY MAX(created_at) DESC
         LIMIT 10)
    ORDER BY created_at DESC
    LIMIT 10
    

    This solution is based on the practical assumption, that no two arts for same user can have same highest created_at, but it may well be wrong if you are importing or programitically creating bulk of arts. If assumption doesn't hold true, the sql might get more contrieved.

    EDIT: Attempt to change the query to Arel:

    Art.where("(arts.user_id, arts.created_at) IN 
                 (SELECT user_id, MAX(created_at) FROM arts
                    GROUP BY user_id
                    ORDER BY MAX(created_at) DESC
                    LIMIT 10)").
        order("created_at DESC").
        page(params[:page]).
        per(params[:per])
    

提交回复
热议问题