2011-07-27 20 views
5

考虑下面的记录(第一行是列名):如何选择通过列独特的记录与ActiveRecord的和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)* ...”,但这需要我先按名称排序,当我真的想按日期列排序时。

  • 订单记录日期
  • 如果存在具有相同的名称,选择一个(这并不重要)多个记录
  • 选择所有列

如何在Rails中实现这个在PostgreSQL上?

回答

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 
+0

如果'name,min(date)'对在表中出现两次,这就有唯一性问题。 –

2

我你不喜欢它,当多个名字是在那里(这将是所有列真)行中检索和表有一个结构,你可以简单地做喜欢

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

或Rails的

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

当我这样做时,我得到以下错误:列“games.id”必须出现在GROUP BY子句中或用于聚合函数 –

+1

而不是downvoting,更好地解释你的结构,从你的问题看来,你似乎是有一张表,由于该错误消息,这是不正确的。改为发布完整的结构。 – Fabio

+0

+1,擦除这个不可理解的-1 – apneadiving

7

你不能做一个简单的.group(:name),因为这会产生在你的SQL一个GROUP BY name时,你会选择拆散然后unaggregate d列,留下模糊性挑哪一行和PostgreSQL (rightly IMHO) complains

When GROUP BY is present, it is not valid for the SELECT list expressions to refer to ungrouped columns except within aggregate functions, since there would be more than one possible value to return for an ungrouped column.

如果你开始添加更多的列到您的分组像这样的东西:

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

然后你会被进行分组你不想要的东西,你最终会拉出整个桌子,这不是你想要的。如果您尝试进行聚合以避免出现分组问题,那么您最终会混合不同的行(即一列将来自一行,而另一列将来自其他行),但这不是您想要的。

ActiveRecord确实不是为这种事情而建的,但你可以在一定的努力下将其屈服于你的意愿。

您正在使用AR,因此您大概有一个id列。如果你有PostgreSQL 8。4或更高,那么你可以使用window functions作为一种本地化的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 
) 

然后包裹在一个find_by_sql,你有你的对象。

如果您在共享数据库(或其他没有8.4或更高版本的环境)中使用Heroku,那么您会遇到PostgreSQL 8.3,并且您将不具有窗口功能。在这种情况下,你可能想筛选出在Ruby中,土地的重复:

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/min(thedate)对那么8.3兼容的解决方案可能做你最好的选择;但是,如果会有大量重复项,那么您希望数据库尽可能地做大量的工作,以避免创建数千个您将要丢弃的AR对象。

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

+0

+1获得很好的答案! – apneadiving

+0

@apneadiving:我不得不在你的“挑战”后:) –

+0

这个答案终于帮助我理解PostgreSQL内部为这种查询发生了什么。感谢您的详细解答。 –

相关问题