Найдите строки с несколькими повторяющимися полями с помощью Active Record, Rails и Postgres

103

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

Я нашел это решение здесь :

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

Но, похоже, это не работает с postgres. Я получаю такую ​​ошибку:

PG :: GroupingError: ОШИБКА: столбец "parts.id" должен появиться в предложении GROUP BY или использоваться в агрегатной функции

newUserNameHere
источник
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.
Craig Ringer

Ответы:

225

Протестированная и рабочая версия

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}

Думал, что это было круто, и раньше этого не видел.

Благодарим Тарин, это всего лишь измененная версия ее ответа.

newUserNameHere
источник
7
Мне пришлось передать явный массив select()as in: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countдля работы.
Рафаэль Оливейра
4
добавление .countдаетPG::UndefinedFunction: ERROR: function count
Magne
1
Вы можете попробовать User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Сергей Надолинский
3
Я пробую тот же метод, но также пытаюсь получить User.id, добавляя его в select, а группа возвращает пустой массив. Как я могу вернуть всю модель User или хотя бы включить: id?
Эшбери
6
использовать .sizeвместо.count
Charles Hamel
33

Эта ошибка возникает из-за того, что POSTGRES требует, чтобы вы поместили столбцы группировки в предложение SELECT.

пытаться:

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

(примечание: не тестировалось, возможно, придется его настроить)

ИЗМЕНИТЬ, чтобы удалить столбец идентификатора

Тарин Восток
источник
7
Это не сработает; idстолбец не является частью группы, так что вы не можете передать его , если вы не агрегировать его (например , array_agg(id)или json_agg(id))
Craig Ringer
10

Если вам нужны полные модели, попробуйте следующее (на основе ответа @ 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 "))` ``
чет Кори
Спасибо, что отлично работает :) Также кажется, что последнее .select(:email)избыточно. Я думаю, что это немного чище, но я могу ошибаться. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
Чет Кори
Спасибо за быстрое решение.
RanaAlie
3

Получите все дубликаты одним запросом, если вы используете 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

Основываясь на ответе @newUserName выше , я считаю, что правильный способ показать количество для каждого -

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

res.each {|r| puts r.attributes } ; nil
Нуну Коста
источник