Trouvez des lignes avec plusieurs champs en double avec Active Record, Rails & Postgres

103

Quelle est la meilleure façon de rechercher des enregistrements avec des valeurs en double sur plusieurs colonnes à l'aide de Postgres et Activerecord?

J'ai trouvé cette solution ici :

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

Mais cela ne semble pas fonctionner avec postgres. J'obtiens cette erreur:

PG :: GroupingError: ERROR: la colonne "parts.id" doit apparaître dans la clause GROUP BY ou être utilisée dans une fonction d'agrégation

newUserNameHere
la source
3
En SQL standard, j'utiliserais une auto-jointure, quelque chose comme select a.id, b.id, name, email FROM user a INNER JOIN user b USING (name, email) WHERE a.id > b.id. Je ne sais pas comment exprimer cela dans ActiveRecord-speak.
Craig Ringer

Réponses:

224

Version testée et fonctionnelle

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

En outre, c'est un peu sans rapport mais pratique. Si vous voulez voir combien de fois chaque combinaison a été trouvée, mettez .size à la fin:

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

et vous obtiendrez un jeu de résultats qui ressemble à ceci:

{[nil, nil]=>512,
 ["Joe", "[email protected]"]=>23,
 ["Jim", "[email protected]"]=>36,
 ["John", "[email protected]"]=>21}

J'ai pensé que c'était plutôt cool et je ne l'avais jamais vu auparavant.

Merci à Taryn, ce n'est qu'une version modifiée de sa réponse.

newUserNameHere
la source
7
J'ai dû passer un tableau explicite à select()as in: User.select([:first,:email]).group(:first,:email).having("count(*) > 1").countpour travailler.
Rafael Oliveira
4
ajoutant les .countdonnePG::UndefinedFunction: ERROR: function count
Magne
1
Vous pouvez essayer User.select ([: first,: email]). Group (: first,: email) .having ("count (*)> 1"). Map.count
Serhii Nadolynskyi
3
J'essaie la même méthode, mais j'essaie également d'obtenir le User.id, en l'ajoutant à la sélection et le groupe renvoie un tableau vide. Comment puis-je retourner le modèle utilisateur entier, ou au moins inclure le: id?
Ashbury
5
utiliser à la .sizeplace de.count
Charles Hamel
32

Cette erreur se produit car POSTGRES vous oblige à placer des colonnes de regroupement dans la clause SELECT.

essayer:

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

(note: non testé, vous devrez peut-être le modifier)

MODIFIÉ pour supprimer la colonne d'identifiant

Taryn Est
la source
7
Cela ne fonctionnera pas; la idcolonne ne fait pas partie du groupe, vous ne pouvez donc pas la référer à moins de l'agréger (par exemple array_agg(id)ou json_agg(id))
Craig Ringer
9

Si vous avez besoin des modèles complets, essayez ce qui suit (basé sur la réponse de @ newUserNameHere).

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

Cela renverra les lignes où l'adresse e-mail de la ligne n'est pas unique.

Je ne connais pas un moyen de le faire sur plusieurs attributs.

Ben Aubin
la source
`` `User.where (email: User.select (: email) .group (: email) .having (" count (*)> 1 "))` `` `
chet corey
Merci qui fonctionne très bien :) On dirait aussi que le dernier .select(:email)est redondant. Je pense que c'est un peu plus propre, mais je peux me tromper. User.where(email: User.select(:email).group(:email).having("count(*) > 1"))
chet corey du
2

Obtenez tous les doublons avec une seule requête si vous utilisez 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
Itsnikolay
la source
-1

Basé sur la réponse ci-dessus par @newUserNameIci, je crois que la bonne façon d'afficher le décompte pour chacun est

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

res.each {|r| puts r.attributes } ; nil
Nuno Costa
la source