कच्चे अद्यतन एसक्यूएल को रेलों में गतिशील बंधन के साथ कैसे निष्पादित किया जाए


98

मैं नीचे की तरह एक अद्यतन कच्चे sql निष्पादित करना चाहता हूं:

update table set f1=? where f2=? and f3=?

इस एसक्यूएल द्वारा निष्पादित किया जाएगा ActiveRecord::Base.connection.execute, लेकिन मुझे पता नहीं है कि विधि में गतिशील पैरामीटर मान कैसे पारित किया जाए।

क्या कोई मुझे इस पर कोई मदद दे सकता है?


कच्चे SQL का उपयोग करके आप ऐसा क्यों करना चाहते हैं, ActiveRecord की बात आपको इससे बचने में मदद करने के लिए है ...
एंड्रयू

यदि मैं AR का उपयोग करता हूं, तो सबसे पहले मुझे AR फ़ील्ड की आईडी विधि के साथ मॉडल ऑब्जेक्ट प्राप्त करना चाहिए, फिर अपडेट ऑपरेशन करना होगा। इसलिए संचालन के दृष्टिकोण से एक अद्यतन एआर को डेटाबेस के साथ दो वर्गमीटर की आवश्यकता होती है; दूसरी ओर मुझे यकीन नहीं है कि AR का अपडेट तरीका डायनामिक बाइंडिंग का उपयोग करता है। इसलिए मैं अद्यतन संचालन के लिए db के साथ एक इंटरैक्शन के लिए डायनामिक बाइंडिंग के साथ कच्चे sql का उपयोग करना चाहता हूं, लेकिन मुझे नहीं पता कि इसे बदलने के लिए पैरामीटर कैसे पास करें? ए.आर. द्वारा sql में।
य्वेनबो

27
ऐसा करने के कई वैध कारण हैं। सबसे पहले, क्वेरी नियमित रूप से रूबी तरीके का उपयोग करने के लिए अनुवाद करने के लिए बहुत जटिल हो सकती है ... दूसरा, मापदंडों में% या उद्धरण जैसे विशेष वर्ण हो सकते हैं, और यह बचने के लिए गधे में एक दर्द है ..
हेनले चीयू

3
@ और, एआर प्रदान करता है कि "सुविधा" को स्वीकार करने की तुलना में कच्चे mysql कार्यों का उपयोग करना बेहतर है।
ग्रीन

4
@ अगर आप कभी भी अपने ऐप को MySQL से PostgreSQL या कुछ और में स्थानांतरित नहीं करना चाहते हैं। ORM के प्रमुख बिंदुओं में से एक आपके ऐप को पोर्टेबल बनाना है।
एंड्रयू

जवाबों:


102

ऐसा नहीं लगता है कि रेल एपीआई ने ऐसा करने के लिए उदारतापूर्वक तरीकों को उजागर किया है। आप अंतर्निहित कनेक्शन तक पहुँचने की कोशिश कर सकते हैं और इसके तरीकों का उपयोग कर सकते हैं, जैसे MySQL के लिए:

st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close

मुझे यकीन नहीं है कि ऐसा करने के लिए अन्य रामबाण हैं (कनेक्शन खुला छोड़ दिया है, आदि)। मैं एक सामान्य अपडेट के लिए रेल कोड का पता लगाऊंगा, यह देखने के लिए कि यह वास्तविक क्वेरी से अलग क्या है।

तैयार किए गए प्रश्नों का उपयोग करके आप डेटाबेस में बहुत कम समय बचा सकते हैं, लेकिन जब तक आप इसे एक लाख बार नहीं कर रहे हैं, आप शायद सामान्य रूबी प्रतिस्थापन के साथ अपडेट का निर्माण कर रहे हैं, जैसे कि।

ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")

या टिप्पणीकारों की तरह ActiveRecord का उपयोग कर।


26
सुझाए गए "सामान्य रूबी प्रतिस्थापन" विधि का उपयोग करें field=#{value}क्योंकि यह आपको एसक्यूएल इंजेक्शन हमलों के लिए खुला छोड़ देता है। यदि आप उस रास्ते से नीचे जाते हैं, तो ActiveRecord :: ConnectionAdapters :: Quoting मॉड्यूल देखें।
पॉल एन्सले

3
कृपया ध्यान दें, mysql2 तैयार किए गए कथनों का समर्थन नहीं करता है, यह भी देखें: stackoverflow.com/questions/9906437/…
रेटो

1
@ ग्रेटो लग रहा है जैसे वे करीब हैं: github.com/brianmario/mysql2/pull/289
ब्रायन का पता लगाने

3
prepareमाईसक्ल 2 में कोई वक्तव्य नहीं
ग्रीन

1
प्रलेखन के अनुसार: "नोट: आपके डेटाबेस कनेक्टर के आधार पर, इस विधि द्वारा लौटाया गया परिणाम मैन्युअल रूप से मेमोरी प्रबंधित हो सकता है। इसके बजाय #exec_query आवरण का उपयोग करने पर विचार करें।" executeखतरनाक तरीका है और स्मृति या अन्य संसाधन लीक का कारण बन सकता है।
Kolen

32

ActiveRecord::Base.connectionएक quoteविधि है जो एक स्ट्रिंग मान लेती है (और वैकल्पिक रूप से कॉलम ऑब्जेक्ट)। तो आप यह कह सकते हैं:

ActiveRecord::Base.connection.execute(<<-EOQ)
  UPDATE  foo
  SET     bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ

ध्यान दें कि यदि आप एक रेल प्रवास या एक ActiveRecord ऑब्जेक्ट में हैं, तो आप इसे छोटा कर सकते हैं:

connection.execute(<<-EOQ)
  UPDATE  foo
  SET     bar = #{connection.quote(baz)}
EOQ

अद्यतन: जैसा कि @kolen बताते हैं, आपको exec_updateइसके बजाय उपयोग करना चाहिए । यह आपके लिए उद्धरण को संभाल लेगा और मेमोरी को लीक करने से भी बचाएगा। हस्ताक्षर हालांकि थोड़ा अलग तरीके से काम करता है:

connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
  UPDATE  foo
  SET     bar = $1
EOQ

यहां अंतिम पैरामंड बाइंड मापदंडों का प्रतिनिधित्व करने वाले ट्यूपल्स का एक सरणी है। प्रत्येक टपल में, पहली प्रविष्टि स्तंभ प्रकार है और दूसरा मान है। आप nilकॉलम प्रकार के लिए दे सकते हैं और रेल आमतौर पर सही काम करेंगे।

वहाँ भी कर रहे हैं exec_query, exec_insertऔर exec_delete, क्या आप की जरूरत पर निर्भर करता है।


3
प्रलेखन के अनुसार: "नोट: आपके डेटाबेस कनेक्टर के आधार पर, इस विधि द्वारा लौटाया गया परिणाम मैन्युअल रूप से मेमोरी प्रबंधित हो सकता है। इसके बजाय #exec_query आवरण का उपयोग करने पर विचार करें।" executeखतरनाक तरीका है और स्मृति या अन्य संसाधन लीक का कारण बन सकता है।
१४'१

1
वाह अच्छी पकड़! यहाँ प्रलेखन है । यह भी लगता है कि पोस्टग्रैज एडेप्टर यहां लीक होगा।
पॉल ए जुंगविर्थ

आश्चर्य है, अगर एआर हमें on duplicate key updateहर जगह कुछ नहीं के साथ छोड़ देता है
श्रीनाथ

1
@ श्रीनाथ चूंकि यह सवाल कच्ची एसक्यूएल लिखने के बारे में है, ON CONFLICT DO UPDATEआप चाहें तो क्वेरी बना सकते हैं। गैर-कच्चे एसक्यूएल के लिए यह मणि आसान लगती है: github.com/jesjos/active_record_upsert
पॉल ए जुंगविर्थ

4

आपको बस कुछ का उपयोग करना चाहिए:

YourModel.update_all(
  ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)

वह चाल चलेगा। का उपयोग करते हुए ActiveRecord :: बेस # भेजने आह्वान करने के लिए विधि sanitize_sql_for_assignment बनाता रूबी (कम से कम 1.8.7 संस्करण) तथ्य यह है कि छोड़ sanitize_sql_for_assignment वास्तव में एक संरक्षित तरीका है।


2

शायद ही कभी माता-पिता वर्ग के नाम के बजाय तालिका के नाम का बेहतर उपयोग होगा:

# Refers to the current class
self.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)

उदाहरण के लिए "व्यक्ति" आधार वर्ग, उपवर्ग (और डेटाबेस टेबल) "ग्राहक" और "विक्रेता" इसके बजाय का उपयोग कर:

Client.where(self.class.primary_key => id).update_all(created _at: timestamp)
Seller.where(self.class.primary_key => id).update_all(created _at: timestamp)

आप इस तरह से आधार वर्ग की वस्तु का उपयोग कर सकते हैं:

person.class.unscoped.where(self.class.primary_key => id).update_all(created _at: timestamp)

1

इसके लिए कच्चे SQL का उपयोग क्यों करें?

यदि आप एक मॉडल है, तो के लिए यह का उपयोग where:

f1 = 'foo'
f2 = 'bar'
f3 = 'buzz'
YourModel.where('f1 = ? and f2 = ?', f1, f2).each do |ym|
  # or where(f1: f1, f2: f2).each do (...)
  ym.update(f3: f3) 
end

यदि आपके पास इसके लिए कोई मॉडल नहीं है (केवल तालिका), तो आप एक फ़ाइल और मॉडल बना सकते हैं जो इनहेरिट करेगाActiveRecord::Base

class YourTable < ActiveRecord::Base
  self.table_name = 'your_table' # specify explicitly if needed
end

और फिर से whereऊपर जैसा उपयोग करें:


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

0

यहाँ एक चाल है जिसे मैंने हाल ही में बाइंड के साथ कच्चे sql को निष्पादित करने के लिए काम किया है:

binds = SomeRecord.bind(a_string_field: value1, a_date_field: value2) +
        SomeOtherRecord.bind(a_numeric_field: value3)
SomeRecord.connection.exec_query <<~SQL, nil, binds
  SELECT *
  FROM some_records
  JOIN some_other_records ON some_other_records.record_id = some_records.id
  WHERE some_records.a_string_field = $1
    AND some_records.a_date_field < $2
    AND some_other_records.a_numeric_field > $3
SQL

जहां ApplicationRecordइस परिभाषित करता है:

# Convenient way of building custom sql binds
def self.bind(column_values)
  column_values.map do |column_name, value|
    [column_for_attribute(column_name), value]
  end
end

और यह कैसे एआर अपने स्वयं के प्रश्नों को बांधता है के समान है।


-11

मुझे कच्चे sql का उपयोग करने की आवश्यकता थी क्योंकि मैं activerecord 2.3.8 के साथ काम करने के लिए कंपोजिट_प्रिमरी_की प्राप्त करने में विफल रहा। तो एक प्राथमिक प्राथमिक कुंजी के साथ sqlserver 2000 तालिका तक पहुंचने के लिए, कच्चे sql की आवश्यकता थी।

sql = "update [db].[dbo].[#{Contacts.table_name}] " +
      "set [COLUMN] = 0 " +
      "where [CLIENT_ID] = '#{contact.CLIENT_ID}' and CONTACT_ID = '#{contact.CONTACT_ID}'"
st = ActiveRecord::Base.connection.raw_connection.prepare(sql)
st.execute

यदि एक बेहतर समाधान उपलब्ध है, तो कृपया साझा करें।


10
यहां एसक्यूएल-इंजेक्शन संभव है!
१२:१६ पर मिस्टीम

-18

रेल 3.1 में, आपको क्वेरी इंटरफ़ेस का उपयोग करना चाहिए:

  • नई (विशेषताएँ)
  • बनाने (विशेषताएँ)
  • (गुण) बनाने के!
  • लगता है (id_or_array)
  • नष्ट (id_or_array)
  • destroy_all
  • हटाना (id_or_array)
  • सभी हटा दो
  • अपडेट (आईडी, अपडेट)
  • update_all (अपडेट)
  • मौजूद?

अपडेट और update_all आपके लिए आवश्यक ऑपरेशन हैं।

यहां देखें विवरण: http://m.onkey.org/active-record-query-interface

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