[英]How do I select unique records by column with ActiveRecord and 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中實現這一點?
你不能做一個簡單的.group(:name)
因為當你選擇未分組和未分頁的列時,會在你的SQL中產生GROUP BY name
,這使得選擇哪一行和PostgreSQL(正確的恕我直言)抱怨不明確 :
當存在GROUP BY時,SELECT列表表達式無法引用除聚合函數之外的未組合列,因為對於未組合列,將返回多個可能的值。
如果您開始使用以下內容向分組中添加更多列:
T.group(T.columns.collect(&:name))
那么你將按照你不想要的東西進行分組,你最終會拉出整張桌子,這不是你想要的。 如果你嘗試聚合以避免分組問題,你最終會混合不同的行(即一列將來自一行,而另一列將來自其他行),這也不是你想要的。
ActiveRecord確實不是為這類東西而構建的,但你可以通過一些努力將它彎曲到你的意志。
你正在使用AR,所以你可能有一個id
列。 如果你有PostgreSQL 8.4或更高版本,那么你可以使用窗口函數作為一種本地化的GROUP BY; 你需要窗口兩次:一次搞清楚name
/ thedate
對,再挑出一個id
(以防萬一你有相同的多行name
和thedate
匹配最早其中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
,你就擁有了你的對象。
如果您將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 }
如果您非常確定不會有重復的name
/ min(thedate)
對,那么8.3兼容的解決方案可能是您最好的選擇; 但是,如果會有很多重復項,那么您希望數據庫盡可能多地完成工作,以避免創建數千個您將要丟棄的AR對象。
也許其他人比我更強大的PostgreSQL-Fu會出現並提供更好的東西。
我不關心當多個名稱存在時檢索哪一行(對於所有列都是如此)並且表具有該結構,您可以簡單地執行查詢
SELECT * FROM table_name GROUP BY `name` ORDER BY `date`
或者在Rails中
TableClass.group(:name).order(:date)
我知道這個問題是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}
獲取名稱和最短日期列表,然后將其連接回原始表格以獲取您正在尋找的行集。
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.