Al estilo SQL-y
Primero, resolvamos el problema en SQL, para que la sintaxis específica de Rails no nos engañe.
Esta pregunta SO es un paralelo bastante claro:Encontrar duplicados valores en una tabla SQL
La respuesta de KM (segunda desde arriba, sin marcar, en este momento) cumple con sus criterios de devolver todos los registros duplicados junto con sus ID. He modificado KM's SQL para que coincida con su mesa...
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 dentro de INNER JOIN ( )
es esencialmente lo que ya has generado. Una tabla agrupada de títulos y conteos duplicados. El truco es JOIN
ing a las movies
sin modificar tabla, que excluirá cualquier película que no tenga coincidencias en la consulta de duplicados.
¿Por qué es tan difícil de generar en Rails? La parte más complicada es que, porque estamos JOIN
ing movies
a movies
, tenemos que crear alias de tabla (m
y dupes
en mi consulta anterior).
Lamentablemente, Rails no proporciona ninguna forma clara de declarar estos alias. Algunas referencias:
- Problemas de Rails GitHub mencionando "join" y "alias". Miseria.
- Pregunta SO:Consulta ActiveRecord con tabla con alias nombres
Afortunadamente, dado que tenemos el SQL a mano, podemos usar .find_by_sql
método...
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")
Porque estamos llamando a Movie.find_by_sql
, ActiveRecord asume que nuestro SQL escrito a mano se puede agrupar en Movie
objetos. No masajea ni genera nada, lo que nos permite hacer nuestros alias.
Este enfoque tiene sus defectos. Devuelve una matriz y no una relación ActiveRecord, lo que significa que no se puede encadenar con otros ámbitos. Y, en la documentación de find_by_sql
método
, obtenemos desánimo extra...
Un camino sobre rieles
Realmente, ¿qué está haciendo el SQL arriba? Está obteniendo una lista de nombres que aparecen más de una vez. Luego, compara esa lista con la tabla original. Entonces, hagámoslo usando Rails.
titles_with_multiple = Movie.group(:title).having("count(title) > 1").count.keys
Movie.where(title: titles_with_multiple)
Llamamos .keys
porque la primera consulta devuelve un hash. Las claves son nuestros títulos. El where()
El método puede tomar una matriz, y le hemos entregado una matriz de títulos. Ganador.
Se podría argumentar que una línea de Ruby es más elegante que dos. Y si esa línea de Ruby tiene una cadena impía de SQL incrustada, ¿qué tan elegante es realmente?
¡Espero que esto ayude!