给出以下记录(第一行是列名):
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中实现这一目标?
答案 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.3
。 2.6.1
被释放。 Rails稳定版本为5.2.2
。 6.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.first
或p.last.last
获取嵌套数组的第一条记录或最后一条记录。
解决方案2: .collect
或.each
方法。
Person.all.order(:date).group_by(&:name).collect {|key, value| value.last}