107 votes

ActiveRecord Requête Union

J'ai écrit un couple de requêtes complexes (au moins pour moi) avec RoR de l'interface de requête:

watched_news_posts = Post.joins(:news => :watched).where(:watched => {:user_id => id})
watched_topic_posts = Post.joins(:post_topic_relationships => {:topic => :watched}).where(:watched => {:user_id => id})

Ces deux requêtes fonctionnent très bien par eux-mêmes. Les deux retour de courrier objets. Je tiens à associer à ces postes en un seul ActiveRelation. Car il pourrait y avoir des centaines de milliers de postes à un certain point, ce qui doit être fait au niveau base de données. Si il s'agissait d'une requête MySQL, je pourrais simplement de l'utilisateur de l' UNION de l'opérateur. Quelqu'un sait si je peux faire quelque chose de similaire avec RoR de l'interface de requête?

109voto

Tim Lowrimore Points 408

Voici un petit module que j'ai écrit qui permet à l'UNION de plusieurs étendues. Il renvoie également les résultats d'une instance de ActiveRecord::Relation.

module ActiveRecord::UnionScope
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def union_scope(*scopes)
      id_column = "#{table_name}.id"
      sub_query = scopes.map { |s| s.select(id_column).to_sql }.join(" UNION ")
      where "#{id_column} IN (#{sub_query})"
    end
  end
end

Voici l'essentiel: https://gist.github.com/tlowrimore/5162327

Edit:

Comme demandé, voici un exemple de la façon dont UnionScope œuvres:

class Property < ActiveRecord::Base
  include ActiveRecord::UnionScope

  # some silly, contrived scopes
  scope :active_nearby,     -> { where(active: true).where('distance <= 25') }
  scope :inactive_distant,  -> { where(active: false).where('distance >= 200') }

  # A union of the aforementioned scopes
  scope :active_near_and_inactive_distant, -> { union_scope(active_nearby, inactive_distant) }
end

86voto

Elliot Nelson Points 4212

J'ai aussi rencontré ce problème, et maintenant, ma stratégie est de générer du SQL (à la main ou à l'aide de to_sql sur une étendue existante), puis de le coller dans l' from de la clause. Je ne peux pas garantir qu'elle est plus efficace que votre méthode acceptée, mais il est relativement facile sur les yeux et vous donne une normale ARel objet.

watched_news_posts = Post.joins(:news => :watched).where(:watched => {:user_id => id})
watched_topic_posts = Post.joins(:post_topic_relationships => {:topic => :watched}).where(:watched => {:user_id => id})

Post.from("(#{watched_news_posts.to_sql} UNION #{watched_topic_posts.to_sql}) AS posts")

11voto

LandonSchropp Points 3103

Basé sur les Olives de réponse, je n'ai trouvé d'autre solution à ce problème. Il se sent un peu comme un hack, mais elle renvoie une instance d' ActiveRelation, ce qui est ce que je recherchais en premier lieu.

Post.where('posts.id IN 
      (
        SELECT post_topic_relationships.post_id FROM post_topic_relationships
          INNER JOIN "watched" ON "watched"."watched_item_id" = "post_topic_relationships"."topic_id" AND "watched"."watched_item_type" = "Topic" WHERE "watched"."user_id" = ?
      )
      OR posts.id IN
      (
        SELECT "posts"."id" FROM "posts" INNER JOIN "news" ON "news"."id" = "posts"."news_id" 
        INNER JOIN "watched" ON "watched"."watched_item_id" = "news"."id" AND "watched"."watched_item_type" = "News" WHERE "watched"."user_id" = ?
      )', id, id)

Je serais toujours reconnaissant si quelqu'un a des suggestions pour optimiser ce ou d'améliorer les performances, parce que c'est essentiellement l'exécution de trois requêtes et se sent un peu redondant.

6voto

Richard Wan Points 11

Comment au sujet de...

def union(scope1, scope2)
  ids = scope1.pluck(:id) + scope2.pluck(:id)
  where(id: ids.uniq)
end

6voto

Olives Points 3925

Pourriez-vous utiliser un OU à la place d'un SYNDICAT?

Ensuite, vous pourriez faire quelque chose comme:

Post.joins(:news => :watched, :post_topic_relationships => {:topic => :watched})
.where("watched.user_id = :id OR topic_watched.user_id = :id", :id => id)

(Puisque vous êtes rejoint le regardé table deux fois, je ne suis pas trop sûr de ce que les noms de tables seront pour la requête)

Puisqu'il y a beaucoup de jointures, il pourrait aussi être assez lourd sur la base de données, mais il pourrait être en mesure d'être optimisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X