使用 ActiveRecord、 Rails 和 Postgres 查找具有多个重复字段的行

使用 Postgres 和 Activerecord 在多个列中查找具有重复值的记录的最佳方法是什么?

我找到了这个解决方案 给你:

User.find(:all, :group => [:first, :email], :having => "count(*) > 1" )

但似乎对 postgres 不起作用,我得到了这个错误:

PG::GroupingError: ERROR: column "parts.id" must appear in the GROUP BY clause or be used in an aggregate function

80104 次浏览

发生此错误是因为 POSTGRES 要求您在 SELECT 子句中放置分组列。

try:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").all

(注意: 未经测试,可能需要进行调整)

编辑删除 id 列

测试及工作版本

User.select(:first,:email).group(:first,:email).having("count(*) > 1")

Also, this is a little unrelated but handy. If you want to see how times each combination was found, put .size at the end:

User.select(:first,:email).group(:first,:email).having("count(*) > 1").size

你会得到一个像这样的结果:

{[nil, nil]=>512,
["Joe", "test@test.com"]=>23,
["Jim", "email2@gmail.com"]=>36,
["John", "email3@gmail.com"]=>21}

我觉得很酷,以前从没见过。

这要归功于塔林,这只是她答案的一个修改版本。

Based on the 上面的回答 by @newUserNameHere I believe the right way to show the count for each is

res = User.select('first, email, count(1)').group(:first,:email).having('count(1) > 1')


res.each {|r| puts r.attributes } ; nil

如果需要完整的模型,请尝试以下操作(基于@newUserNameHere 的答案)。

User.where(email: User.select(:email).group(:email).having("count(*) > 1").select(:email))

这将返回行的电子邮件地址不唯一的行。

I'm not aware of a way to do this over multiple attributes.

如果使用 PostgreSQL,则获得所有带有 单个查询的副本:

def duplicated_users
duplicated_ids = User
.group(:first, :email)
.having("COUNT(*) > 1")
.select('unnest((array_agg("id"))[2:])')


User.where(id: duplicated_ids)
end


irb> duplicated_users

在原始 SQL 中运行良好:

# select array_agg(id) from attendances group by event_id, user_id having count(*) > 1;
array_agg
---------------
{3712,3711}
{8762,8763}
{7421,7420}
{13478,13477}
{15494,15493}

构建在@itsnikolay 的 上面的回答上,但是创建一个可以传递任何 ActiveRecord 作用域的方法

#pass in a scope, and list of columns to group by
# map(&:dupe_ids) to see your list
def duplicate_row_ids(ar_scope, attrs)
ar_scope
.group(attrs)
.having("COUNT(*) > 1")
.select('array_agg("id") as dupe_ids')
end


#initial scope to narrow where you want to look for dupes
ar_scope = ProductReviews.where( product_id: "194e676b-741e-4143-a0ce-10cf268290bb", status: "Rejected")
#pass the scope, and list of columns to group by
results = duplicate_row_ids(ar_scope, [:nickname, :overall_rating, :source, :product_id, :headline, :status])
#get your list
id_pairs = results.map &:dupe_ids
#each entry is an array
#then go through your pairs and take action

我努力通过接受的答案得到正确的 User模型返回,方法如下:

User
.group(:first, :email)
.having("COUNT(*) > 1")
.select('array_agg("id") as ids')
.map(&:ids)
.map { |group| group.map { |id| User.find(id) } }

这将返回适当的模型,您可以通过以下方式进行交互:

[
[User#1, User#2],
[User#35, User#59],
]