2017-09-18 1 views
1

Étant donné deux modèles ActiveRecord:Comment compter les enregistrements de sous-requête dans la clause where en utilisant ActiveRecord?

class Foo < ApplicationRecord 
    has_many :bars 
end 

class Bar < ApplicationRecord 
    belongs_to :foo 
end 

avec Bar ayant un attribut color avec des valeurs possibles red et green, je voudrais obtenir le foos, qui ont plus rouge bars, que le vert bars.

Jusqu'à présent, j'ai pu cette achive comme ceci:

red_bars_sql = Bar.select('COUNT(*)').where(color: 'red').where('foo.id = bar.foo_id').to_sql 
green_bars_sql = Bar.select('COUNT(*)').where(color: 'green').where('foo.id = bar.foo_id').to_sql 

Foo.where("(#{red_bars_sql}) > (green_bars_sql)") 

Ceci effectue assez bien.

Je voudrais savoir s'il existe une autre approche à ce problème. En outre, est-il pour mettre en œuvre cette approche actuelle avec moins de SQL et plus ActiveRecord (éviter le hack de interpolant sql dans la clause where de Foo).

J'ai essayé quelque chose le long des lignes:

class Foo < ApplicationRecord 
    has_many :bars 
    has_many :reds, -> { reds }, class_name: 'Bar' 
    has_many :greens, -> { greens }, class_name: 'Bar' 
end 

class Bar < ApplicationRecord 
    belongs_to :foo 

    scope :reds, -> { where(color: 'red') } 
    scope :greens, -> { where(color: 'green') } 
end 

puis essayé de regroupement (Foo.joins(:reds, :greens).group(...).having(...)), mais je ne construit correctement et il ne fonctionne pas.

Répondre

1

Avec SQL cela peut être fait comme une requête avec quelque chose comme ceci (par exemple avec PostgreSQL, d'autres blocs de données ont quelque chose de similaire pour les agrégations conditionnelles):

SELECT 
    foo.* 
FROM foos 
INNER JOIN bars ON foos.id = bars.foo_id 
GROUP BY foos.id 
HAVING 
    COUNT(CASE WHEN bars.color = 'red' THEN 1 END) 
    > COUNT(CASE WHEN bars.color = 'green' THEN 1 END) 

Si vous préférez AR il contiendra encore beaucoup de SQL, mais est censé être quelque chose comme:

Foo.joins(:bars).group(:id) 
    .having(
    "COUNT(CASE WHEN bars.color = 'red' THEN 1 END) " \ 
     "> COUNT(CASE WHEN bars.color = 'green' THEN 1 END)" 
) 
+0

C'est exactement ce que je cherchais. Merci beaucoup! :) –