ActiveRecord.find (array_of_ids), ऑर्डर को संरक्षित करना


98

जब आप Something.find(array_of_ids)रेल में करते हैं , तो परिणामी सरणी का क्रम के आदेश पर निर्भर नहीं करता है array_of_ids

क्या आदेश को खोजने और संरक्षित करने का कोई तरीका है?

एटीएम I मैन्युअल रूप से आईडी के आदेश के आधार पर रिकॉर्ड को सॉर्ट करता है, लेकिन यह एक प्रकार का लंगड़ा है।

UPD: यदि :orderपरम और कुछ प्रकार के एसक्यूएल क्लॉज का उपयोग करके ऑर्डर निर्दिष्ट करना संभव है , तो कैसे?


जवाबों:


71

जवाब केवल mysql के लिए है

Mysql में एक समारोह है जिसे FIELD कहा जाता है ()

यहां बताया गया है कि आप इसे .find () में कैसे उपयोग कर सकते हैं:

>> ids = [100, 1, 6]
=> [100, 1, 6]

>> WordDocument.find(ids).collect(&:id)
=> [1, 6, 100]

>> WordDocument.find(ids, :order => "field(id, #{ids.join(',')})")
=> [100, 1, 6]

For new Version
>> WordDocument.where(id: ids).order("field(id, #{ids.join ','})")

अपडेट: इसे रेल्स 6.1 रेल्स सोर्स कोड में हटा दिया जाएगा


क्या आप FIELDS()Postgres में समकक्ष के रूप में जानते हैं ?
Trung Lê

3
मैंने पोस्टग्रुप में ऐसा करने के लिए एक plpgsql फ़ंक्शन लिखा है - omarqureshi.net/articles/2010-6-10-find-in-set-for-postgresql
उमर कुरैशी

25
यह अब काम नहीं करता है। अधिक हाल की Object.where(id: ids).order("field(id, #{ids.join ','})")
रेलों के लिए

2
यह Gunchars 'की तुलना में बेहतर समाधान है क्योंकि यह पेजिनेशन को नहीं तोड़ेगा।
पगडियारियो

.ids मेरे लिए ठीक काम करता है, और बहुत तेज़
एक्टिवरकॉर्ड

79

अजीब तरह से, किसी ने कुछ इस तरह का सुझाव नहीं दिया है:

index = Something.find(array_of_ids).group_by(&:id)
array_of_ids.map { |i| index[i].first }

के रूप में कुशल के रूप में यह एसक्यूएल बैकेंड ऐसा करने के अलावा हो जाता है।

संपादित करें: मेरे अपने जवाब में सुधार करने के लिए, आप इसे इस तरह भी कर सकते हैं:

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values

#index_byऔर क्रमशः ऐरे और हैश के लिए ActiveSupport#slice में बहुत आसान अतिरिक्त हैं।


तो आपका संपादन कार्य करने लगता है, लेकिन यह मुझे परेशान करता है कि क्या हैश में नर्वस ऑर्डर की गारंटी नहीं है? इसलिए जब आप स्लाइस को कॉल करते हैं और हैश बैक "री-ऑर्डर" प्राप्त करते हैं, तो यह वास्तव में हैश रिटर्न मूल्यों पर निर्भर करता है, जिसमें यह कुंजी जोड़ा गया था। ऐसा लगता है कि कार्यान्वयन विस्तार पर निर्भर करता है जो बदल सकता है।
जॉन

2
@, रूबी 1.9 में आदेश की गारंटी दी गई है और प्रत्येक अन्य कार्यान्वयन जो इसे पालन करने की कोशिश करता है। 1.8 के लिए, रेल (ActiveSupport) हैश वर्ग को उसी तरह से व्यवहार करने के लिए पैच करती है, इसलिए यदि आप रेल का उपयोग कर रहे हैं, तो आपको ठीक होना चाहिए।
गनचर

स्पष्टीकरण के लिए धन्यवाद, सिर्फ दस्तावेज में पाया गया।
जॉन

13
इसके साथ समस्या यह है कि यह एक संबंध के बजाय एक सरणी देता है।
वेलिजर हिस्त्रोव

3
महान, हालांकि, वन-लाइनर मेरे लिए काम नहीं करता है (रेल 4.1)
बेस्सी

44

जैसा कि माइक वुडहाउस ने अपने जवाब में कहा , यह तब होता है क्योंकि हुड के तहत, रेल एक क्वेरी के साथ SQL क्वेरी का उपयोग कर रही है WHERE id IN... clauseताकि सभी रिकॉर्ड को एक क्वेरी में पुनः प्राप्त किया जा सके। यह प्रत्येक आईडी को व्यक्तिगत रूप से प्राप्त करने की तुलना में तेज़ है, लेकिन जैसा कि आपने देखा कि यह आपके द्वारा पुनर्प्राप्त किए जा रहे रिकॉर्ड के क्रम को संरक्षित नहीं करता है।

इसे ठीक करने के लिए, आप रिकॉर्ड को देखते समय उपयोग की गई आईडी की मूल सूची के अनुसार आवेदन स्तर पर रिकॉर्ड को सॉर्ट कर सकते हैं।

एक सरणी को दूसरे सरणी के तत्वों के अनुसार क्रमबद्ध करने के लिए कई उत्कृष्ट उत्तरों के आधार पर , मैं निम्नलिखित समाधान सुझाता हूं:

Something.find(array_of_ids).sort_by{|thing| array_of_ids.index thing.id}

या अगर आपको कुछ तेज़ चाहिए (लेकिन यकीनन कुछ कम पढ़ने योग्य है) तो आप ऐसा कर सकते हैं:

Something.find(array_of_ids).index_by(&:id).values_at(*array_of_ids)

3
दूसरा समाधान (index_by के साथ) मेरे लिए विफल हो रहा है, सभी शून्य परिणामों का उत्पादन कर रहा है।
बेन व्हीलर

22

यह postgresql ( स्रोत ) के लिए काम करता है - और एक ActiveRecord संबंध देता है

class Something < ActiveRecrd::Base

  scope :for_ids_with_order, ->(ids) {
    order = sanitize_sql_array(
      ["position((',' || id::text || ',') in ?)", ids.join(',') + ',']
    )
    where(:id => ids).order(order)
  }    
end

# usage:
Something.for_ids_with_order([1, 3, 2])

अन्य स्तंभों के लिए भी बढ़ाया जा सकता है, उदाहरण के लिए nameस्तंभ के लिए, उपयोग करें position(name::text in ?)...


आप सप्ताह के मेरे हीरो हैं। धन्यवाद!
ntdb

4
ध्यान दें कि यह केवल तुच्छ मामलों में काम करता है, आप अंततः एक ऐसी स्थिति के खिलाफ चलेंगे जहां आपकी आईडी सूची में अन्य आईडी के भीतर निहित है (उदाहरण के लिए यह 11 में 1 मिलेगा)। इसके आस-पास का एक तरीका यह है कि अल्पविराम को स्थिति की जाँच में जोड़ें, और फिर सम्मिलित होने के लिए एक अंतिम अल्पविराम जोड़ें, जैसे: आदेश = sanitize_sql_array (["स्थिति (',' || clients.id :: text || ') || 'इन;) ", इडसजॉइन (', ') +', '])
आयरिशड्यूबग्यू

अच्छी बात है, @IrishDubGuy! मैं आपके सुझाव के आधार पर अपना उत्तर अपडेट करूंगा। धन्यवाद!
जिंजरलीम

मेरे लिए जंजीर काम नहीं करती। यहाँ टेबल का नाम आईडी से पहले जोड़ा जाना चाहिए: इस तरह से टेक्स्ट: ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] मेरे लिए काम करने वाला पूर्ण संस्करण: scope :for_ids_with_order, ->(ids) { order = sanitize_sql_array( ["position((',' || somethings.id::text || ',') in ?)", ids.join(',') + ','] ) where(:id => ids).order(order) } धन्यवाद @gingerlime @IrishDubGuy
user1136228

मुझे लगता है कि जब आप कुछ जोड़ते हैं तो आपको तालिका का नाम जोड़ने की आवश्यकता होती है ... जब आप शामिल होते हैं तो यह ActiveRecord scopes के साथ काफी सामान्य है।
जिंजरलीम

19

जैसा कि मैंने यहाँ उत्तर दिया , मैंने अभी एक रत्न जारी किया ( order_as_specified ) जो आपको इस तरह से देशी SQL ऑर्डर करने की अनुमति देता है:

Something.find(array_of_ids).order_as_specified(id: array_of_ids)

जहां तक ​​मैं परीक्षण करने में सक्षम रहा हूं, यह सभी RDBMSes में मूल रूप से काम करता है, और यह एक ActiveRecord संबंध देता है जिसे जंजीर बनाया जा सकता है।


1
यार तुम तो कमाल हो। धन्यवाद!
स्वराेल

5

एसक्यूएल में संभव नहीं है जो सभी मामलों में दुर्भाग्य से काम करेगा, आपको या तो माणिक में प्रत्येक रिकॉर्ड या ऑर्डर के लिए एकल खोज लिखने की आवश्यकता होगी, हालांकि मालिकाना तकनीक का उपयोग करके इसे काम करने का एक तरीका है:

पहला उदाहरण:

sorted = arr.inject([]){|res, val| res << Model.find(val)}

बहुत प्रभावी है

दूसरा उदाहरण:

unsorted = Model.find(arr)
sorted = arr.inject([]){|res, val| res << unsorted.detect {|u| u.id == val}}

हालांकि बहुत कुशल नहीं है, मैं मानता हूं कि यह काम लगभग DB-agnostic और स्वीकार्य है यदि आपके पास छोटी मात्रा में पंक्तियां हैं।
Trung Lê

इसके लिए इंजेक्शन का उपयोग न करें, यह एक मानचित्र है:sorted = arr.map { |val| Model.find(val) }
22

पहले वाला धीमा है। मैं इस तरह के नक्शे के साथ दूसरे के साथ सहमत हूँ:sorted = arr.map{|id| unsorted.detect{|u|u.id==id}}
kuboon

2

@Gunchars उत्तर बहुत अच्छा है, लेकिन यह रेल 2.3 में बॉक्स से बाहर काम नहीं करता है क्योंकि हैश वर्ग का आदेश नहीं दिया गया है। एक सरल काम है असंख्य श्रेणी का विस्तार करना 'index_by वर्कअनुमेय वर्ग ऑर्डरडैश क्लास का उपयोग करना है:

module Enumerable
  def index_by_with_ordered_hash
    inject(ActiveSupport::OrderedHash.new) do |accum, elem|
      accum[yield(elem)] = elem
      accum
    end
  end
  alias_method_chain :index_by, :ordered_hash
end

अब @Gunchars का तरीका काम करेगा

Something.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values

बक्शीश

module ActiveRecord
  class Base
    def self.find_with_relevance(array_of_ids)
      array_of_ids = Array(array_of_ids) unless array_of_ids.is_a?(Array)
      self.find(array_of_ids).index_by(&:id).slice(*array_of_ids).values
    end
  end
end

फिर

Something.find_with_relevance(array_of_ids)

2

Model.pluck(:id)रिटर्न मानते हुए [1,2,3,4]और आप का आदेश चाहते हैं[2,4,1,3]

ORDER BY CASE WHENएसक्यूएल क्लॉज का उपयोग करने के लिए अवधारणा है । उदाहरण के लिए:

SELECT * FROM colors
  ORDER BY
  CASE
    WHEN code='blue' THEN 1
    WHEN code='yellow' THEN 2
    WHEN code='green' THEN 3
    WHEN code='red' THEN 4
    ELSE 5
  END, name;

रेल में, आप एक समान संरचना का निर्माण करने के लिए अपने मॉडल में एक सार्वजनिक विधि पाकर इसे प्राप्त कर सकते हैं:

def self.order_by_ids(ids)
  if ids.present?
    order_by = ["CASE"]
    ids.each_with_index do |id, index|
      order_by << "WHEN id='#{id}' THEN #{index}"
    end
    order_by << "END"
    order(order_by.join(" "))
  end
else
  all # If no ids, just return all
end

फिर करो:

ordered_by_ids = [2,4,1,3]

results = Model.where(id: ordered_by_ids).order_by_ids(ordered_by_ids)

results.class # Model::ActiveRecord_Relation < ActiveRecord::Relation

इस बारे में अच्छी बात है। परिणाम ActiveRecord संबंधों के रूप में वापस कर रहे हैं (अनुमति देता है आप की तरह विधियों का उपयोग करने last, count, where, pluck, आदि)


2

एक रत्न है find_with_order जो आपको मूल SQL क्वेरी का उपयोग करके इसे कुशलतापूर्वक करने की अनुमति देता है।

और यह दोनों का समर्थन करता है Mysqlऔर PostgreSQL

उदाहरण के लिए:

Something.find_with_order(array_of_ids)

यदि आप संबंध चाहते हैं:

Something.where_with_order(:id, array_of_ids)

1

हुड के तहत, findआईडी की एक सरणी के SELECTसाथ एक WHERE id IN...खंड उत्पन्न होगा , जो आईडी के माध्यम से लूपिंग से अधिक कुशल होना चाहिए।

इसलिए अनुरोध डेटाबेस के लिए एक यात्रा में संतुष्ट है, लेकिन SELECTबिना ORDER BYखंडों के अनसोल्ड हैं। ActiveRecord इसे समझता है, इसलिए हम अपना विस्तार findइस प्रकार करते हैं:

Something.find(array_of_ids, :order => 'id')

यदि आपके एरे में आईडी का क्रम मनमाना और महत्वपूर्ण है (अर्थात आप आइआर के अनुक्रम के बावजूद अपने एरे से मिलान करने के लिए वापस लौटी पंक्तियों का क्रम चाहते हैं) तो मुझे लगता है कि आप परिणामों को पोस्ट-प्रोसेस करके सबसे अच्छा सर्वर होंगे कोड - आप एक :orderखंड का निर्माण कर सकते हैं, लेकिन यह दृढ़ता से जटिल होगा और सभी इरादे प्रकट नहीं करेगा।


ध्यान दें कि विकल्प हैश किए गए हैं। (दूसरा तर्क, इस उदाहरण में :order => id)
ओओदो जूल

1

हालाँकि मैं इसे CHANGELOG में कहीं भी उल्लिखित नहीं देखता, लेकिन ऐसा लगता है कि संस्करण की रिलीज़ के साथ इस कार्यक्षमता को बदल दिया गया था5.2.0

यहाँ पर डॉक्स को टैग के साथ अद्यतन करने के लिए प्रतिबद्ध है , 5.2.0हालांकि ऐसा लगता है कि इसे संस्करण में भी वापस भेज दिया गया है5.0



-4

खोजने में एक ऑर्डर क्लॉज है (: ऑर्डर => '...') जो रिकॉर्ड लाने पर ऐसा करता है। आप यहां से भी मदद ले सकते हैं।

लिंक पाठ

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.