2015-10-27 2 views
6

Я следующий запрос возвращения дублирующие названия, но :id является nil:Возвращенные повторяющихся записей (ActiveRecord, Postgres)

Movie.select(:title).group(:title).having("count(*) > 1") 

[#<Movie:0x007f81f7111c20 id: nil, title: "Fargo">, 
#<Movie:0x007f81f7111ab8 id: nil, title: "Children of Men">, 
#<Movie:0x007f81f7111950 id: nil, title: "The Martian">, 
#<Movie:0x007f81f71117e8 id: nil, title: "Gravity">] 

Я попытался добавить :id в избранных и группы, но он возвращает пустой массив. Как я могу вернуть весь фильм, а не только названия?

ответ

12

SQL--й путь

Во-первых, давайте просто решить эту проблему в SQL, так что Rails-специфический синтаксис Безразлично Не обманывай нас.

Этот SO вопрос довольно четкая параллель: Finding duplicate values in a SQL Table

Ответ от КМ (второй сверху, не отмеченных на данный момент) соответствует вашим критериям возвращения всех дублирующих записей вместе с их идентификаторами. Я изменил SQL км, чтобы соответствовать ваш стол ...

SELECT 
    m.id, m.title 
FROM 
    movies m 
INNER JOIN (
    SELECT 
    title, COUNT(*) AS CountOf 
    FROM 
    movies 
    GROUP BY 
    title 
    HAVING COUNT(*)>1 
) dupes 
ON 
    m.title=dupes.title 

Часть внутри INNER JOIN (), по существу, что вы сгенерировали уже. Сгруппированная таблица дублированных названий и подсчетов. Трюк - это JOIN. Это немодифицированная таблица movies, которая исключает любые фильмы, которые не имеют совпадений в запросе обмана.

Почему так сложно создавать в Rails? Самое сложное в том, что, поскольку мы JOIN ing movies до movies, мы должны создать псевдонимы таблиц (m и dupes в моем запросе выше).

К сожалению, это Rails не предоставляет никаких чистых способов объявления этих псевдонимов. Некоторые ссылки:

К счастью, так как мы получили SQL в руки, мы можем использовать метод .find_by_sql ...

Movie.find_by_sql("SELECT m.id, m.title FROM movies m INNER JOIN (SELECT title, COUNT(*) FROM movies GROUP BY title HAVING COUNT(*)>1) dupes ON m.first=.first") 

Потому что мы называем Movie.find_by_sql, ActiveRecord предполагает, что наш ручной SQL можно объединить в объекты Movie. Он не массирует и не генерирует ничего, что позволяет нам делать наши псевдонимы.

Этот подход имеет свои недостатки. Он возвращает массив, а не ActiveRecord Relation, что означает, что он не может быть привязан к другим областям. И, in the documentation for the find_by_sql method, мы получаем дополнительное уныние ...

This should be a last resort because using, for example, MySQL specific terms will lock you to using that particular database engine or require you to change your call if you switch engines.

А Rails-й путь

Действительно, что это SQL делать выше? Он получает список имен, которые появляются более одного раза. Затем он сопоставляет этот список с исходной таблицей. Итак, давайте просто сделаем это с помощью Rails.

titles_with_multiple = Movie.group(:title).having("count(title) > 1").count.keys 

Movie.where(title: titles_with_multiple) 

Мы называем .keys, поскольку первый запрос возвращает хэш. Ключи - наши названия. Метод where() может принимать массив, и мы передали ему массив названий. Победитель.

Вы можете утверждать, что одна строка Ruby более элегантна, чем две. И если эта строка Ruby содержит в себе нечестивую строку SQL, насколько она элегантна?

Надеюсь, это поможет!

+0

Удивительный ответ, суперинформативный! Очень крутой трюк с массивом в .where(), я бы сделал неуклюжий каждый цикл. – Ashbury

+0

Рад, что я мог бы помочь! :) –

0

Вы можете попробовать добавить id в вашем select:

Movie.select([:id, :title]).group(:title).having("count(title) > 1") 
+1

Я хотел бы упомянуть, я попробовал это, но я получаю «PG :: GroupingError: ОШИБКА: колонка„movies.id“должен появиться в GROUP BY оговорки или использоваться в агрегатной функции» – Ashbury

+0

Может быть, вы должны добавить свой идентификатор в вашей группе тоже. Я обновил его. – akbarbin

+1

Это возвращает пустой массив, я думаю, потому что он ищет дубликат: id. :(Спасибо, хотя. – Ashbury

Смежные вопросы