طريقة نظيفة للعثور على كائنات ActiveRecord بواسطة المعرف بالترتيب المحدد

StackOverflow https://stackoverflow.com/questions/801824

سؤال

أريد الحصول على مجموعة من كائنات ActiveRecord مع إعطاء مجموعة من المعرفات.

لقد افترضت ذلك

Object.find([5,2,3])

سأعيد مصفوفة تحتوي على الكائن 5، والكائن 2، ثم الكائن 3 بهذا الترتيب، ولكن بدلاً من ذلك أحصل على مصفوفة مرتبة ككائن 2، والكائن 3، ثم الكائن 5.

قاعدة ActiveRecord العثور على طريقة API يذكر أنه لا ينبغي عليك توقع ذلك بالترتيب المقدم (لا تقدم الوثائق الأخرى هذا التحذير).

تم تقديم أحد الحلول المحتملة في البحث عن طريق مجموعة من المعرفات بنفس الترتيب؟, ، ولكن لا يبدو أن خيار الطلب صالح لـ SQLite.

يمكنني كتابة بعض أكواد الياقوت لفرز الكائنات بنفسي (إما بسيطة إلى حد ما وسيئة القياس أو تحجيم أفضل وأكثر تعقيدًا)، ولكن هل هناك طريقة أفضل؟

هل كانت مفيدة؟

المحلول

وانها ليست من الخلية وغيرها من الأمور بالواجبات نوع من تلقاء نفسها، انها أنها لا فرزها. عند استدعاء Model.find([5, 2, 3])، وSQL التي تم إنشاؤها شيئا مثل:

SELECT * FROM models WHERE models.id IN (5, 2, 3)

وهذا لا يحدد هذا الأمر، مجرد مجموعة من السجلات التي تريد إرجاعها. وتبين أن عموما الخلية سيعود الصفوف قاعدة بيانات من أجل 'id'، ولكن ليس هناك ضمان لذلك.

والطريقة الوحيدة للحصول على قاعدة بيانات لإرجاع السجلات في ترتيب مضمونة لإضافة شرط النظام. إذا سوف يكون دائما إرجاع السجلات الخاصة بك في ترتيب معين، ثم يمكنك إضافة عمود الفرز لديسيبل والقيام Model.find([5, 2, 3], :order => 'sort_column'). إذا لم تكن هذه هي الحالة، سيكون لديك لتفعل الفرز في التعليمات البرمجية:

ids = [5, 2, 3]
records = Model.find(ids)
sorted_records = ids.collect {|id| records.detect {|x| x.id == id}} 

نصائح أخرى

بناءً على تعليقي السابق لـ Jeroen van Dijk، يمكنك القيام بذلك بشكل أكثر كفاءة وفي سطرين باستخدام each_with_object

result_hash = Model.find(ids).each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
ids.map {|id| result_hash[id]}

كمرجع هنا هو المعيار الذي استخدمته

ids = [5,3,1,4,11,13,10]
results = Model.find(ids)

Benchmark.measure do 
  100000.times do 
    result_hash = results.each_with_object({}) {|result,result_hash| result_hash[result.id] = result }
    ids.map {|id| result_hash[id]}
  end
end.real
#=>  4.45757484436035 seconds

الآن الآخر

ids = [5,3,1,4,11,13,10]
results = Model.find(ids)
Benchmark.measure do 
  100000.times do 
    ids.collect {|id| results.detect {|result| result.id == id}}
  end
end.real
# => 6.10875988006592

تحديث

يمكنك القيام بذلك في أغلب الأحيان باستخدام عبارات الترتيب والحالة، إليك طريقة الفصل التي يمكنك استخدامها.

def self.order_by_ids(ids)
  order_by = ["case"]
  ids.each_with_index.map do |id, index|
    order_by << "WHEN id='#{id}' THEN #{index}"
  end
  order_by << "end"
  order(order_by.join(" "))
end

#   User.where(:id => [3,2,1]).order_by_ids([3,2,1]).map(&:id) 
#   #=> [3,2,1]

ويبدو الخلية وغيرها من الامور نظام إدارة DB نوع من تلقاء نفسها. أعتقد أنه يمكنك تجاوز ذلك بهذا الأمر:

ids = [5,2,3]
@things = Object.find( ids, :order => "field(id,#{ids.join(',')})" )

وحل المحمولة سيكون لاستخدام عبارة حالة SQL في طلبك BY. يمكنك استخدام الى حد كبير أي تعبير في ORDER BY وحالة يمكن استخدام جدول البحث inlined. على سبيل المثال، فإن SQL كنت بعد تبدو هذه:

select ...
order by
    case id
    when 5 then 0
    when 2 then 1
    when 3 then 2
    end

وهذا من السهل جدا أن تولد مع قليلا من روبي:

ids = [5, 2, 3]
order = 'case id ' + (0 .. ids.length).map { |i| "when #{ids[i]} then #{i}" }.join(' ') + ' end'

ويفترض سبق أن كنت تعمل مع الأرقام أو بعض القيم الآمنة الأخرى في ids. إذا كان هذا ليس هو الحال ثم كنت تريد استخدام connection.quote أو أحد أكتيفيريكورد SQL المطهر أساليب لأقتبس صحيح ids الخاص بك.

وبعد ذلك استخدام سلسلة order كشرط الطلب الخاص بك:

Object.find(ids, :order => order)

وأو في العالم الحديث:

Object.where(:id => ids).order(order)

وهذا هو مطول بعض الشيء ولكن يجب أن تعمل نفس الشيء مع أي قاعدة بيانات SQL وليس من الصعب أن لإخفاء بشاعة.

وكما أجبت هنا ، أنا فقط أصدرت جوهرة (<لأ href = "https://github.com / بانوراما افتتاحية / order_as_specified "يختلط =" نوفولو noreferrer "> order_as_specified ) الذي يسمح لك أن تفعل SQL ترتيب الأصلي مثل هذا:

Object.where(id: [5, 2, 3]).order_as_specified(id: [5, 2, 3])

ومجرد اختبار، وأنه يعمل في SQLite.

كتب

وجوستين فايس و<لأ href = "http://www.justinweiss.com/blog/2015/04/20/how-to-select-database-records-in-an-arbitrary-order/" يختلط = "نوفولو"> المادة بلوق حول هذه المشكلة قبل يومين فقط.

ويبدو أن نهج جيد لقول قاعدة بيانات حول ترتيب المفضل وتحميل كافة السجلات مرتبة في هذا النظام مباشرة من قاعدة البيانات. مثال من له <لأ href = "http://www.justinweiss.com/blog/2015/04/20/how-to-select-database-records-in-an-arbitrary-order/" يختلط = "نوفولو" > بلوق المقالة :

# in config/initializers/find_by_ordered_ids.rb
module FindByOrderedIdsActiveRecordExtension
  extend ActiveSupport::Concern
  module ClassMethods
    def find_ordered(ids)
      order_clause = "CASE id "
      ids.each_with_index do |id, index|
        order_clause << "WHEN #{id} THEN #{index} "
      end
      order_clause << "ELSE #{ids.length} END"
      where(id: ids).order(order_clause)
    end
  end
end

ActiveRecord::Base.include(FindByOrderedIdsActiveRecordExtension)

وهذا يسمح لك لكتابة:

Object.find_ordered([2, 1, 3]) # => [2, 1, 3]

وإليك performant لل(البعثرة البحث، وليس O (ن) بحث مجموعة كما كشف في!) بطانة واحدة، كوسيلة من وسائل:

def find_ordered(model, ids)
  model.find(ids).map{|o| [o.id, o]}.to_h.values_at(*ids)
end

# We get:
ids = [3, 3, 2, 1, 3]
Model.find(ids).map(:id)          == [1, 2, 3]
find_ordered(Model, ids).map(:id) == ids

و(أكثر كفاءة على الأرجح) وهناك طريقة أخرى للقيام بذلك في روبي:

ids = [5, 2, 3]
records_by_id = Model.find(ids).inject({}) do |result, record| 
  result[record.id] = record
  result
end
sorted_records = ids.map {|id| records_by_id[id] }

إليك أبسط شيء يمكن أن يأتي مع:

ids = [200, 107, 247, 189]
results = ModelObject.find(ids).group_by(&:id)
sorted_results = ids.map {|id| results[id].first }
@things = [5,2,3].map{|id| Object.find(id)}

وربما هذا هو أسهل طريقة، على افتراض لم يكن لديك الكثير من الأشياء العثور عليها، لأنه يتطلب رحلة إلى قاعدة بيانات لكل معرف.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top