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

Eri*_* M. 5 sql postgresql ruby-on-rails ruby-on-rails-3

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

name              platform           other_columns     date
Eric              Ruby               something         somedate
Eric              Objective-C        something         somedate
Joe               Ruby               something         somedate
Run Code Online (Sandbox Code Playgroud)

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

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

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

我如何在PostgreSQL上的Rails中实现这一点?

mu *_*ort 7

你不能做一个简单的事情,.group(:name)因为GROUP BY name当你选择未分组和未分页的列时,会在你的SQL 中生成一个,这使得选择哪一行和PostgreSQL(正确的恕我直言)抱怨的含糊不清:

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

如果您开始使用以下内容向分组中添加更多列:

T.group(T.columns.collect(&:name))
Run Code Online (Sandbox Code Playgroud)

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

ActiveRecord确实不是为这类东西而构建的,但你可以通过一些努力将它弯曲到你的意志.

你正在使用AR,所以你可能有一个id专栏.如果你有PostgreSQL 8.4或更高版本,那么你可以使用窗口函数作为一种本地化的GROUP BY; 你需要窗口两次:一次找出name/ thedate对,然后再选一次id(以防万一你有多个行相同namethedate最匹配thedate),因此得到一个唯一的行:

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
)
Run Code Online (Sandbox Code Playgroud)

然后将其包裹在一个find_by_sql并且你有你的对象.

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

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 }
Run Code Online (Sandbox Code Playgroud)

如果你非常确定没有重复name/ min(thedate)配对,那么兼容8.3的解决方案可能是你最好的选择; 但是,如果会有很多重复项,那么您希望数据库尽可能多地完成工作,以避免创建数千个您将要丢弃的AR对象.

也许其他人比我更强大的PostgreSQL-Fu会出现并提供更好的东西.


Der*_*omm 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
Run Code Online (Sandbox Code Playgroud)