簡體   English   中英

如何使用ActiveRecord和Postgresql按列選擇唯一記錄

[英]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 (以防萬一你有相同的多行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 ,你就擁有了你的對象。

如果您將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.firstp.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM