2015-10-27 6 views
6

Ho la seguente interrogazione tornando titoli duplicati, ma :id è nil:restituire i record duplicati (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">] 

Ho provato ad aggiungere :id alla selezione e gruppo, ma restituisce un array vuoto. Come posso restituire l'intero record del film, non solo i titoli?

risposta

12

Un modo SQL-y

In primo luogo, facciamo solo risolvere il problema in SQL, in modo che la sintassi delle guide specifico doesn ci ingannano.

Questa domanda SO è un abbastanza chiaro parallelo: Finding duplicate values in a SQL Table

La risposta da KM (secondo dall'alto, non spuntate, al momento) corrisponde ai tuoi criteri di tornare tutti i record duplicati con i loro ID. Ho modificato SQL di KM per abbinare vostro tavolo ...

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 

La parte all'interno del INNER JOIN () è essenzialmente quello che hai generato già. Una tabella raggruppata di titoli e conteggi duplicati. Il trucco è JOIN nella tabella movies non modificata, che escluderà tutti i film che non hanno corrispondenze nella query dei duplicati.

Perché è così difficile da generare in Rails? La parte più difficile è che, dal momento che siamo JOIN ing movies a movies, dobbiamo creare alias di tabella (m e dupes nella mia query sopra).

Purtroppo, Rails non fornisce alcun metodo pulito per dichiarare questi alias. Alcuni riferimenti:

Per fortuna, dal momento che abbiamo la SQL nella mano, possiamo utilizzare il metodo .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") 

perché stiamo chiamando Movie.find_by_sql, ActiveRecord presume che il nostro SQL scritto a mano possa essere raggruppato negli oggetti Movie. Non massaggia o genera nulla, che ci permette di fare i nostri pseudonimi.

Questo approccio ha i suoi difetti. Restituisce un array e non un rapporto ActiveRecord, il che significa che non può essere incatenato con altri ambiti. E, in the documentation for the find_by_sql method, otteniamo ulteriore scoraggiamento ...Way

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.

A Rails-y

realtà, ciò che sta facendo l'SQL di cui sopra? Sta ottenendo una lista di nomi che appaiono più di una volta. Quindi, confronta quella lista con la tabella originale. Quindi, facciamolo usando Rails.

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

Movie.where(title: titles_with_multiple) 

Noi chiamiamo .keys perché la prima query restituisce un hash. Le chiavi sono i nostri titoli. Il metodo where() può prendere un array e gli abbiamo consegnato una serie di titoli. Vincitore.

Si potrebbe obiettare che una riga di Ruby è più elegante di due. E se quella riga di Ruby ha una stringa empia di SQL incorporata al suo interno, quanto è elegante in realtà?

Spero che questo aiuti!

+0

Una risposta sorprendente, super informativa! Un trucco molto interessante con array in .where(), avrei fatto un grosso giro ogni ciclo. – Ashbury

+0

Contento di poterti aiutare! :) –

0

Si può provare ad aggiungere id nel vostro select:

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

Avrei dovuto accennare che ho provato ma ottengo "PG :: GroupingError: ERRORE: colonna" movies.id "deve apparire nella clausola GROUP BY o essere utilizzato in una funzione di aggregazione" – Ashbury

+0

Forse devi aggiungere il tuo id anche nel tuo gruppo. L'ho aggiornato. – akbarbin

+1

Che restituisce una matrice vuota, penso perché cerca duplicato: id. :(Grazie però – Ashbury