অ্যাক্টিভেকর্ড ক্যোয়ারী ইউনিয়ন


90

আমি রবির সাথে রেলের অনুসন্ধান ইন্টারফেসে কয়েকটি জটিল প্রশ্ন লিখেছি (কমপক্ষে আমার কাছে):

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})

এই উভয় প্রশ্নের নিজেরাই সূক্ষ্মভাবে কাজ করে। উভয়ই পোস্ট অবজেক্ট ফেরত দেয়। আমি এই পোস্টগুলিকে একটি এক্টিভ রিলেশনে সংযুক্ত করতে চাই। যেহেতু এক পর্যায়ে কয়েক হাজার পোস্ট হতে পারে তাই এটি ডাটাবেস পর্যায়ে করা দরকার। এটি যদি মাইএসকিউএল কোয়েরি হত তবে আমি সহজেই UNIONঅপারেটরটি ব্যবহার করতে পারি । আমি কি আরআর এর ক্যোয়ারী ইন্টারফেসের সাথে অনুরূপ কিছু করতে পারি তা কি কেউ জানেন?


আপনার সুযোগটি ব্যবহার করতে সক্ষম হওয়া উচিত । 2 টি স্কোপ তৈরি করুন এবং তারপরে তাদের উভয়ের মতো কল করুন Post.watched_news_posts.watched_topic_posts:user_idএবং এর মতো জিনিসের জন্য আপনাকে স্কোপগুলিতে প্যারামগুলি প্রেরণের দরকার হতে পারে :topic
জাবাবা

6
পরামর্শের জন্য ধন্যবাদ. দস্তাবেজগুলির মতে, "একটি সুযোগ একটি ডাটাবেস ক্যোয়ারিকে সংকীর্ণ করে তোলে"। আমার ক্ষেত্রে, আমি এমন পোস্টগুলি সন্ধান করছি না যা পর্যবেক্ষণ_নিউজ_পোস্ট এবং পর্যবেক্ষণ_টোটিক_পোস্ট উভয়ই। পরিবর্তে, আমি এমন পোস্ট খুঁজছি যা দেখেছি_নিউজ_পোস্টে বা দেখানো_উপটিক_পোস্টে রয়েছে, কোনও নকল ছাড়ার অনুমতি নেই। স্কোপগুলি সহ এখনও এটি কি সম্ভব?
ল্যান্ডনস্ক্রপ

4
বাক্সের বাইরে সত্যই সম্ভব নয়। ইউনিয়ন নামে গিথুব-এ একটি প্লাগইন রয়েছে তবে এটি ওল্ড-স্কুল সিনট্যাক্স (ক্লাস পদ্ধতি এবং হ্যাশ-স্টাইলের ক্যোয়ারী প্যারাম) ব্যবহার করে, যদি এটি আপনার সাথে দুর্দান্ত হয় তবে আমি বলতে চাই যে এটির সাথে যান ... অন্যথায় এটি দীর্ঘ পথে লিখুন আপনার সুযোগের মধ্যে সন্ধান_বাই_এসকিউএল।
jenjenut233

4
আমি jenjenut233 এর সাথে একমত এবং আমার মনে হয় আপনি এরকম কিছু করতে পারেন find_by_sql("#{watched_news_posts.to_sql} UNION #{watched_topic_posts.to_sql}")। আমি এটি পরীক্ষা করে দেখিনি, তাই আপনি যদি এটি চেষ্টা করেন তবে এটি কীভাবে হয় তা আমাকে জানান। এছাড়াও, সম্ভবত কিছু আর্ল কার্যকারিতা রয়েছে যা কাজ করবে।
ওগজ উইজার্ড

4
ওয়েল, আমি এসকিউএল কোয়েরি হিসাবে কোয়েরিগুলি আবার লিখেছি। তারা এখন কাজ করে তবে দুর্ভাগ্যক্রমে find_by_sqlঅন্যান্য চেইনযোগ্য ক্যোয়ারী ব্যবহার করা যাবে না যার অর্থ এখন আমার উইল_প্যাজিট ফিল্টার এবং কোয়েরিগুলিও আবার লিখতে হবে। অ্যাক্টিভেকর্ড কোনও অপারেশনকে সমর্থন করে না কেন union?
ল্যান্ডনস্ক্রপ

উত্তর:


93

এখানে আমি লিখেছিলাম একটি দ্রুত সামান্য মডিউল যা আপনাকে ইউনিয়ন একাধিক স্কোপগুলিতে অনুমতি দেয়। এটি অ্যাক্টিভেকর্ড :: রিলেশন এর উদাহরণ হিসাবে ফলাফলগুলিও ফেরত দেয়।

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

সংক্ষিপ্তসারটি এখানে: https://gist.github.com/tlowrimore/5162327

সম্পাদনা করুন:

অনুরোধ হিসাবে, ইউনিয়নস্কোপ কীভাবে কাজ করে তার একটি উদাহরণ এখানে:

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

4
উপরের তালিকাভুক্ত অন্যান্য অন্যান্যদের কাছে এটি সম্পূর্ণ সম্পূর্ণ উত্তর answer দুর্দান্ত কাজ!
gayyes

ব্যবহারের উদাহরণটি দুর্দান্ত হবে।
ciembor

অনুরোধ হিসাবে, আমি একটি উদাহরণ যুক্ত করেছি।
টিম লোরিমোর

4
সমাধানটি "প্রায়" সঠিক এবং আমি এটি একটি +1 দিয়েছি তবে আমি এখানে যে সমস্যার সমাধান করেছি তা ছড়িয়ে দিয়েছি: gist.github.com/lsiden/260167a4d3574a580d97
লরেন্স আই সিডেন

7
দ্রুত সতর্কতা: এই পদ্ধতিটি মাইএসকিউএলের সাথে পারফরম্যান্সের দৃষ্টিভঙ্গি থেকে অত্যন্ত সমস্যাযুক্ত, যেহেতু সাবকুইটি টেবিলে প্রতিটি রেকর্ডের জন্য নির্ভরশীল এবং কার্যকর করা হবে ( পারকোনা.com/blog/2010/10/25/mysql-limitations-part দেখুন -3-subqueries )।
shosti

71

আমিও এই সমস্যার মুখোমুখি হয়েছি এবং এখন আমার যাওয়ার কৌশলটি এসকিউএল তৈরি করা (হাতে বা to_sqlকোনও বিদ্যমান সুযোগে ব্যবহার করা) এবং তারপরে এটিকে ক্লজটিতে আটকে রাখা from। আমি আপনার গ্রহণযোগ্য পদ্ধতির চেয়ে এটি আরও দক্ষতার গ্যারান্টি দিতে পারি না তবে এটি চোখের তুলনায় তুলনামূলক সহজ এবং আপনাকে একটি সাধারণ আর্ল অবজেক্ট ফিরিয়ে দেয়।

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")

আপনি এটি দুটি ভিন্ন মডেলের সাথেও করতে পারেন, তবে আপনাকে এটি নিশ্চিত করতে হবে যে তারা উভয়ই ইউনিয়নের অভ্যন্তরে "একই দেখাচ্ছে" - আপনি selectউভয় প্রশ্নের উপর একই কলাম তৈরি করবেন তা নিশ্চিত করতে ব্যবহার করতে পারেন ।

topics = Topic.select('user_id AS author_id, description AS body, created_at')
comments = Comment.select('author_id, body, created_at')

Comment.from("(#{comments.to_sql} UNION #{topics.to_sql}) AS comments")

ধরুন আমাদের যদি দুটি ভিন্ন মডেল থাকে তবে দয়া করে আমাকে জানাবেন যে আনইনের জন্য কোয়েরি হবে।
চিত্রা

খুব সহায়ক উত্তর। ভবিষ্যতের পাঠকদের জন্য, চূড়ান্ত "এএস মন্তব্যসমূহ" অংশটি মনে রাখবেন কারণ অ্যাক্টিভরেকর্ড কোয়েরিটি 'নির্বাচন করুন' মন্তব্য "হিসাবে তৈরি করেন" "AS foo", চূড়ান্ত
স্কেল

4
এটি ঠিক আমি যা খুঁজছিলাম ছিল। অ্যাক্টিভেকর্ড :: #orআমার রেল 4 প্রকল্পে সমর্থন সম্পর্কিত সম্পর্ক বাড়িয়েছি । একই মডেলটি ধরে নিচ্ছেন:klass.from("(#{to_sql} union #{other_relation.to_sql}) as #{table_name}")
এম ওয়াইট

11

জলপাইয়ের উত্তরের ভিত্তিতে আমি এই সমস্যার আর একটি সমাধান নিয়ে এসেছি। এটি হ্যাকের মতো খানিকটা অনুভূত হয় তবে এটি একটি উদাহরণ দেয় ActiveRelationযা আমি প্রথম স্থানে ছিলাম।

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)

কারও কাছে এটির অনুকূলিতকরণ বা কর্মক্ষমতা উন্নত করার জন্য যদি কোনও পরামর্শ থাকে তবে আমি এটির প্রশংসা করব, কারণ এটি মূলত তিনটি অনুসন্ধান চালায় এবং কিছুটা অপ্রয়োজনীয় বোধ করে।


আমি কীভাবে এটি দিয়ে একই জিনিসটি করতে পারি: gist.github.com/2241307 যাতে এটি একটি অ্যারে ক্লাসের চেয়ে একটি সম্পর্কিত :: শ্রেণি তৈরি করে?
মার্ক

10

এছাড়াও আপনি ব্যবহার করতে পারে ব্রায়ান Hempel এর active_record_union মণি যে প্রসারিত ActiveRecordএকটি সঙ্গে unionসুযোগ জন্য পদ্ধতি।

আপনার জিজ্ঞাসাটি এরকম হবে:

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

আশা করি এটি শেষ পর্যন্ত ActiveRecordকোনও একদিনে মিশে যাবে ।


8

কেমন...

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

15
সতর্কতা অবলম্বন করুন যে এটি একের চেয়ে তিনটি অনুসন্ধান করবে কারণ প্রতিটি pluckকলই নিজের মধ্যে একটি ক্যোয়ারী।
জ্যাকবভেলিন

4
এটি একটি দুর্দান্ত সমাধান, এটি অ্যারে ফেরত দেয় না, কারণ আপনি ব্যবহার করতে পারেন .orderবা .paginateপদ্ধতিগুলি ... এটি
orm

স্কোপগুলি একই মডেলের হয় তবে দরকারী, তবে এটি প্লকের কারণে দুটি প্রশ্ন উত্পন্ন করবে gene
jmjm

6

আপনি কি কোনও ইউনিয়নের পরিবর্তে একটি ওআর ব্যবহার করতে পারেন?

তাহলে আপনি এর মতো কিছু করতে পারেন:

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

(যেহেতু আপনি দু'বার দেখা টেবিলের সাথে যোগ দিচ্ছেন, আমি সন্ধানের জন্য টেবিলের নামগুলি কী হবে তা খুব বেশি নিশ্চিত নই)

যেহেতু প্রচুর যোগদান হয় তাই এটি ডাটাবেসেও বেশ ভারী হতে পারে তবে এটি অনুকূলিত হতে পারে।


4
এত দেরীতে আপনার কাছে ফিরে আসার জন্য দুঃখিত, তবে আমি গত কয়েক দিন ছুটিতে ছিলাম। আমি আপনার উত্তরটি চেষ্টা করার সময় আমার যে সমস্যাটি হয়েছিল তা হ'ল দু'টি পৃথক প্রশ্নের পরিবর্তে তুলনা করা যেতে পারে তার পরিবর্তে দুটি পদ্ধতির সাথে যোগ করার পদ্ধতিটি যোগ করছিল তবে আপনার ধারণাটি দৃ was় ছিল এবং আমাকে অন্য ধারণা দিয়েছিল give সাহায্যের জন্য ধন্যবাদ.
ল্যান্ডনশ্রপ্প

বা ইউএনআইএন এর চেয়ে ধীর গতিতে নির্বাচন করুন, এর পরিবর্তে
ইউএনইউনের

5

তর্কসাপেক্ষভাবে, এটি পাঠযোগ্যতার উন্নতি করে তবে অগত্যা কার্য সম্পাদন নয়:

def my_posts
  Post.where <<-SQL, self.id, self.id
    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" 
    AND watched.user_id = ?
    UNION
    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" 
    AND watched.user_id = ?)
  SQL
end

এই পদ্ধতিটি একটি অ্যাক্টিভেকর্ড :: রিলেশন প্রদান করে, যাতে আপনি এটিকে এটি কল করতে পারেন:

my_posts.order("watched_item_type, post.id DESC")

আপনি কোথা থেকে পোষ্টস.আইডি পাচ্ছেন?
berto77

দুটি স্ব.আইডি প্যারামিটার রয়েছে কারণ এসকিউএল এ সেলফ.আইডি দু'বার উল্লেখ করা হয়েছে - দুটি প্রশ্ন চিহ্ন দেখুন।
রিচার্ডসুন

এটি কীভাবে ইউনিয়ন ক্যোয়ারী করবেন এবং একটি অ্যাক্টিভেকর্ড :: রিলেশন ফিরে পাবেন তার একটি দরকারী উদাহরণ। ধন্যবাদ
ফিটার ম্যান

এই জাতীয় এসডিএল ক্যোয়ারী উত্পন্ন করার জন্য আপনার কাছে কি কোনও সরঞ্জাম রয়েছে - আপনি বানান ভুল না করে কীভাবে এটি করেছিলেন?
বিকেএসপুরজন

2

একটি অ্যাক্টিভ_রেকার্ড_উনিওন মণি রয়েছে। সহায়ক হতে পারে

https://github.com/brianhempel/active_record_union

অ্যাক্টিভেকর্ড ইউনিয়ন দিয়ে, আমরা এটি করতে পারি:

বর্তমান ব্যবহারকারীর (খসড়া) পোস্ট এবং যে current_user.posts.union(Post.published) কোনও ব্যক্তির সমস্ত প্রকাশিত পোস্ট যা নিম্নলিখিত এসকিউএল এর সমান:

SELECT "posts".* FROM (
  SELECT "posts".* FROM "posts"  WHERE "posts"."user_id" = 1
  UNION
  SELECT "posts".* FROM "posts"  WHERE (published_at < '2014-07-19 16:04:21.918366')
) posts

1

আপনার কেবলমাত্র দুটি ক্যোয়ারী চালানোর জন্য আমি ফিরে এসেছি এবং রেকর্ডগুলির অ্যারেগুলি একত্রিত করব:

@posts = watched_news_posts + watched_topics_posts

বা, কমপক্ষে এটি পরীক্ষা করে দেখুন। আপনি কি মনে করেন রুবিতে অ্যারের সংমিশ্রণটি খুব বেশি ধীর হবে? সমস্যার আশ্বাস পাওয়ার জন্য প্রস্তাবিত প্রশ্নগুলির দিকে তাকানো, আমি বিশ্বাস করি না যে পারফরম্যান্সের পার্থক্যের সেই তাত্পর্য থাকবে।


প্রকৃতপক্ষে @ পোস্টগুলি = দেখেছেন_নিউজ_পোস্টগুলি & দেখেছে_টপিক্স_পোস্টগুলি করা ভাল যা এটি চৌরাস্তা হিসাবে ভাল এবং ডুপগুলি এড়াতে পারে।
জেফ্রি অ্যালান লি

4
আমি অ্যাক্টিভেরিলেশন এর আওতায় ছিলাম অলসভাবে এর রেকর্ডগুলি লোড করে। আপনি যদি রুবিতে অ্যারে ছেদ করেন তবে আপনি কি তা হারাবেন না?
ল্যান্ডনস্ক্রপ

দৃশ্যত ইউনিয়ন যা সম্পর্ক ফেরৎ পাগল মধ্যে Dev অধীনে, কিন্তু আমি জানি না কি সংস্করণ এটা হতে হবে।
জেফ্রি অ্যালান লি

4
পরিবর্তে এই রিটার্ন অ্যারে, এটির দুটি পৃথক ক্যোয়ারির ফলাফলগুলি মার্জ করে।
আলেক্সজগ

1

অনুরূপ ক্ষেত্রে আমি দুটি অ্যারে যোগ করে ব্যবহার করেছি Kaminari:paginate_array()। খুব সুন্দর এবং কাজের সমাধান। আমি ব্যবহার করতে পারিনি where(), কারণ order()একই টেবিলে আমার দুটি ফলাফলের সমষ্টি করতে হবে ।


1

কম সমস্যা এবং অনুসরণ করা সহজ:

    def union_scope(*scopes)
      scopes[1..-1].inject(where(id: scopes.first)) { |all, scope| all.or(where(id: scope)) }
    end

সুতরাং শেষ পর্যন্ত:

union_scope(watched_news_posts, watched_topic_posts)

4
আমি এটিকে কিছুটা বদলেছি: scopes.drop(1).reduce(where(id: scopes.first)) { |query, scope| query.or(where(id: scope)) }Thx!
E

0

এলিয়ট নেলসন উত্তরের জবাব দিয়েছিলেন, কিছু সম্পর্ক খালি থাকার ক্ষেত্রে বাদে। আমি এরকম কিছু করব:

def union_2_relations(relation1,relation2)
sql = ""
if relation1.any? && relation2.any?
  sql = "(#{relation1.to_sql}) UNION (#{relation2.to_sql}) as #{relation1.klass.table_name}"
elsif relation1.any?
  sql = relation1.to_sql
elsif relation2.any?
  sql = relation2.to_sql
end
relation1.klass.from(sql)

শেষ


0

হেরস আমি কীভাবে রেল অ্যাপ্লিকেশনটিতে নিজের রুবিতে ইউনিয়ন ব্যবহার করে এসকিউএল ক্যোয়ারিতে যোগদান করেছি।

আপনি নিজের কোডে অনুপ্রেরণা হিসাবে নীচে ব্যবহার করতে পারেন।

class Preference < ApplicationRecord
  scope :for, ->(object) { where(preferenceable: object) }
end

নীচে ইউনিয়ন যেখানে আমি একসাথে স্কোপগুলিতে যোগদান করেছি।

  def zone_preferences
    zone = Zone.find params[:zone_id]
    zone_sql = Preference.for(zone).to_sql
    region_sql = Preference.for(zone.region).to_sql
    operator_sql = Preference.for(Operator.current).to_sql

    Preference.from("(#{zone_sql} UNION #{region_sql} UNION #{operator_sql}) AS preferences")
  end
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.