如何使用ActiveRecord和Postgresql按列选择唯一记录

时间:2011-07-27 13:02:41

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

给出以下记录(第一行是列名):

name              platform           other_columns     date
Eric              Ruby               something         somedate
Eric              Objective-C        something         somedate
Joe               Ruby               something         somedate

如何使用所有列检索单个记录,以便名称列在结果集中始终是唯一的?我想在这个例子中的查询返回第一个Eric(w / Ruby)记录。

我认为我最接近的是使用“select distinct on(name)* ...”,但这需要我先按名称排序,当我实际想要按日期列排序记录时。

  • 按日期订购记录
  • 如果有多个同名记录,请选择一个(无关紧要)
  • 选择所有列

如何在PostgreSQL上的Rails中实现这一目标?

4 个答案:

答案 0 :(得分:7)

你不能做一个简单的.group(:name),因为当你选择未分组和未分页的列时,你会在SQL中产生GROUP BY name,这使得选择哪一行和{{{ 3}}:

  

当GROUP BY存在时,SELECT列表表达式无法引用除聚合函数之外的未组合列,因为对于未组合列,将返回多个可能的值。

如果您开始向分组添加更多列,请执行以下操作:

T.group(T.columns.collect(&:name))

然后你会按照你不想要的东西进行分组,你最终会拿出整张桌子,这不是你想要的。如果你尝试聚合以避免分组问题,你最终会混合不同的行(即一列将来自一行,而另一列将来自其他行),这也不是你想要的。

ActiveRecord确实不是为这类东西而构建的,但您可以通过一些努力将其弯曲到您的意愿。

您正在使用AR,因此您可能会有id列。如果你有PostgreSQL 8.4或更高版本,那么你可以使用PostgreSQL (rightly IMHO) complains作为一种本地化的GROUP BY;你需要两次窗口:一次找出name / thedate对,然后再挑选一个id(以防万一你有多个行{{1} }}和name匹配最早的thedate),因此得到一个唯一的行:

thedate

然后将其包裹在window functions中,你就拥有了你的对象。

如果您将Heroku与共享数据库(或其他没有8.4或更高版本的环境)一起使用,那么您将遇到find_by_sql并且您将无法使用窗口功能。在这种情况下,您可能希望过滤掉Ruby-land中的重复项:

select your_table.*
from your_table
where id in (
    -- You don't need DISTINCT here as the IN will take care of collapsing duplicates.
    select min(yt.id) over (partition by yt.name)
    from (
        select distinct name, min(thedate) over (partition by name) as thedate
        from your_table
    ) as dt
    join your_table as yt
      on yt.name = dt.name and yt.thedate = dt.thedate
)

如果您非常确定不会有重复with_dups = YourTable.find_by_sql(%Q{ select yt.* from your_table yt join (select name, min(thedate) as thedate from your_table group by name) as dt on yt.name = dt.name and yt.thedate = dt.thedate }); # Clear out the duplicates, sorting by id ensures consistent results unique_matches = with_dups.sort_by(&:id).group_by(&:name).map { |x| x.last.first } / name对,那么兼容8.3的解决方案可能是您最好的选择;但是,如果会有很多重复项,那么您希望数据库尽可能多地工作,以避免创建数千个您将要丢弃的AR对象。

也许拥有比我更强壮的PostgreSQL-Fu的其他人会来并提供更好的东西。

答案 1 :(得分:2)

我不关心当多个名称存在时检索哪一行(对于所有列都是如此)并且表具有该结构,您可以简单地执行查询,如

SELECT * FROM table_name GROUP BY `name` ORDER BY `date`

或在Rails中

TableClass.group(:name).order(:date)

答案 2 :(得分:0)

获取名称和最短日期列表,然后将其连接回原始表格以获取您正在寻找的行集。

select
    b.*
from
    (select name, min(date) as mindate from table group by name) a
    inner join table b
        on  a.name = b.name and a.mindate = b.date

答案 3 :(得分:0)

我知道这个问题已经8岁了。当前的红宝石版本为2.5.32.6.1被释放。 Rails稳定版本为5.2.26.0.0 beta2被释放。

让您将表命名为Person

Person.all.order(:date).group_by(&:name).map{|p| p.last.last}

Person.all.order(:date).group_by(&:name).collect {|key, value| value.last}

说明:首先在人员表中获取所有记录。然后按日期排序(降序或升序),然后按名称分组(具有重复名称的记录将被分组)。

Person.all.order(:date).group_by(&:name)

这将返回哈希。

{"Eric" => [#<Person id: 1, name: "Eric", other_fields: "">, #<Person id: 2, name: "Eric", other_fields: "">], "Joe" => [#<Person id: 3, name: "Joe", other_fields: "">]}

解决方案1: .map方法。

Person.all.order(:date).group_by(&:name).map{|p| p.last.last}

我们得到了哈希值。我们将其循环为数组。 p.last将给出

[[#<Person id: 1, name: "Eric", other_fields: "">, #<Person id: 2, name: "Eric", other_fields: "">],[#<Person id: 3, name: "Joe", other_fields: "">]]

使用p.last.firstp.last.last获取嵌套数组的第一条记录或最后一条记录。

解决方案2: .collect.each方法。

Person.all.order(:date).group_by(&:name).collect {|key, value| value.last}