Знайдіть рядки з декількома повторюваними полями з Active Record, Rails & Postgres


103

Який найкращий спосіб знайти записи з повторюваними значеннями в кількох стовпцях за допомогою Postgres та Activerecord?

Я знайшов це рішення тут :

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

Але це, здається, не працює з постгресами. Я отримую цю помилку:

PG :: GroupingError: ПОМИЛКА: стовпець "parts.id" повинен відображатися в пункті GROUP BY або використовуватись у сукупній функції


3
У звичайному SQL я використовував би самозаєднання, щось подібне select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Не знаю, як це виразити в ActiveRecord-говоріть.
Крейг Рінгер

Відповіді:


223

Тестована та робоча версія

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

Крім того, це трохи не пов'язано, але зручно. Якщо ви хочете побачити, як було знайдено кожну комбінацію, в кінці поставте .size:

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}

Думав, що це було досить круто і раніше його не бачив.

Заслуга Тарині, це лише перероблена версія її відповіді.


7
Мені довелося передати масив explict to select()як: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countщоб працювати.
Рафаель Олівейра

4
додавання .countподарунківPG::UndefinedFunction: ERROR: function count
Magne

1
Ви можете спробувати User.select ([: перший,: email]). Group (: спочатку,: email) .having ("count (*)> 1"). Map.count
Сергій Надолинський

3
Я намагаюся тим самим методом, але намагаюся отримати і User.id, додавши його до вибору і група повертає порожній масив. Як я можу повернути всю модель користувача або принаймні включити: id?
Ешбері

5
використовувати .sizeзамість.count
Чарльз Хамель

32

Ця помилка виникає через те, що POSTGRES вимагає розмістити стовпчики групування в пункті SELECT.

спробуйте:

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

(Примітка: не перевірена, можливо, вам доведеться її підправити)

ВЕДЕНО, щоб видалити стовпчик ідентифікатора


7
Це не буде працювати; idстовпець не є частиною групи, так що ви не можете передати його , якщо ви не агрегувати його (наприклад , array_agg(id)чи json_agg(id))
Craig Ringer

9

Якщо вам потрібні повні моделі, спробуйте наступне (на основі відповіді @ newUserNameHere).

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

Це поверне рядки, де адреса електронної пошти рядка не є унікальною.

Я не знаю, як це зробити через декілька атрибутів.


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

Дякую, що чудово працює :) Також здається, що останній .select(:email)є зайвим. Я думаю, що це трохи чистіше, але я можу помилитися. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey

2

Отримайте всі дублікати з одним запитом, якщо ви використовуєте 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

-1

Виходячи з відповіді , поданої вище @newUserNameHere, я вважаю, що правильний спосіб показати кількість кожного

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

res.each {|r| puts r.attributes } ; nil
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.