ฉันต้องการเรียกใช้การอัปเดตหนึ่งไฟล์ sql ด้านล่าง:
update table set f1=? where f2=? and f3=?
SQL นี้จะถูกเรียกใช้ActiveRecord::Base.connection.executeงาน แต่ฉันไม่รู้ว่าจะส่งค่าพารามิเตอร์ไดนามิกไปยังเมธอดอย่างไร
มีใครให้ความช่วยเหลือฉันได้ไหม
ฉันต้องการเรียกใช้การอัปเดตหนึ่งไฟล์ sql ด้านล่าง:
update table set f1=? where f2=? and f3=?
SQL นี้จะถูกเรียกใช้ActiveRecord::Base.connection.executeงาน แต่ฉันไม่รู้ว่าจะส่งค่าพารามิเตอร์ไดนามิกไปยังเมธอดอย่างไร
มีใครให้ความช่วยเหลือฉันได้ไหม
คำตอบ:
ดูเหมือนว่า Rails API จะเปิดเผยวิธีการทำสิ่งนี้โดยทั่วไป คุณสามารถลองเข้าถึงการเชื่อมต่อพื้นฐานและใช้วิธีการเช่นสำหรับ MySQL:
st = ActiveRecord::Base.connection.raw_connection.prepare("update table set f1=? where f2=? and f3=?")
st.execute(f1, f2, f3)
st.close
ฉันไม่แน่ใจว่ามีการแบ่งส่วนอื่น ๆ ให้ทำเช่นนี้หรือไม่ (การเชื่อมต่อเปิดทิ้งไว้ ฯลฯ ) ฉันจะติดตามรหัส Rails สำหรับการอัปเดตปกติเพื่อดูว่ามันทำอะไรนอกเหนือจากแบบสอบถามจริง
การใช้แบบสอบถามที่เตรียมไว้สามารถช่วยคุณประหยัดเวลาในฐานข้อมูลได้เล็กน้อย แต่ถ้าคุณไม่ทำสิ่งนี้เป็นล้านครั้งติดต่อกันคุณอาจจะดีกว่าเพียงแค่สร้างการอัปเดตด้วยการแทนที่ Ruby ปกติเช่น
ActiveRecord::Base.connection.execute("update table set f1=#{ActiveRecord::Base.sanitize(f1)}")
หรือใช้ ActiveRecord เหมือนที่ผู้แสดงความคิดเห็นกล่าว
field=#{value}เนื่องจากจะทำให้คุณเปิดกว้างต่อการโจมตีด้วยการฉีด SQL หากคุณไปตามเส้นทางนั้นให้ตรวจสอบโมดูลActiveRecord :: ConnectionAdapters :: Quoting
prepareข้อความใน Mysql2
executeเป็นวิธีที่อันตรายและอาจทำให้หน่วยความจำหรือทรัพยากรอื่น ๆ รั่วไหล
ActiveRecord::Base.connectionมีquoteวิธีการที่รับค่าสตริง (และอ็อบเจ็กต์คอลัมน์เป็นทางเลือก) ดังนั้นคุณสามารถพูดสิ่งนี้:
ActiveRecord::Base.connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{ActiveRecord::Base.connection.quote(baz)}
EOQ
สังเกตว่าคุณอยู่ในการโอนย้าย Rails หรือวัตถุ ActiveRecord คุณสามารถย่อให้สั้นลงเป็น:
connection.execute(<<-EOQ)
UPDATE foo
SET bar = #{connection.quote(baz)}
EOQ
UPDATE:ตามที่ @kolen ชี้ให้เห็นคุณควรใช้exec_updateแทน สิ่งนี้จะจัดการการเสนอราคาให้คุณและหลีกเลี่ยงการรั่วไหลของหน่วยความจำ ลายเซ็นทำงานแตกต่างกันเล็กน้อยแม้ว่า:
connection.exec_update(<<-EOQ, "SQL", [[nil, baz]])
UPDATE foo
SET bar = $1
EOQ
ที่นี่พารามิเตอร์สุดท้ายคืออาร์เรย์ของสิ่งที่เป็นตัวแทนของพารามิเตอร์การผูก ในแต่ละทูเปิลรายการแรกคือประเภทคอลัมน์และรายการที่สองคือค่า คุณสามารถnilระบุประเภทคอลัมน์และ Rails มักจะทำในสิ่งที่ถูกต้อง
นอกจากนี้ยังมีexec_query, exec_insertและexec_deleteขึ้นอยู่กับสิ่งที่คุณต้องการ
executeเป็นวิธีที่อันตรายและอาจทำให้หน่วยความจำหรือทรัพยากรอื่น ๆ รั่วไหล
on duplicate key updateทุกที่
ON CONFLICT DO UPDATEหากต้องการ สำหรับ SQL ที่ไม่ใช่ดิบอัญมณีนี้ดูมีประโยชน์: github.com/jesjos/active_record_upsert
คุณควรใช้สิ่งต่างๆเช่น:
YourModel.update_all(
ActiveRecord::Base.send(:sanitize_sql_for_assignment, {:value => "'wow'"})
)
ที่จะทำเคล็ดลับ การใช้เมธอดActiveRecord :: Base # sendเพื่อเรียกใช้sanitize_sql_for_assignmentทำให้ Ruby (อย่างน้อยรุ่น 1.8.7) ข้ามข้อเท็จจริงที่ว่าsanitize_sql_for_assignmentเป็นวิธีการป้องกัน
ในบางครั้งจะเป็นการดีกว่าที่จะใช้ชื่อของคลาสหลักแทนชื่อตาราง:
# 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)
เหตุใดจึงใช้ Raw 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เช่นเดียวกับด้านบน:
นี่คือเคล็ดลับที่ฉันเพิ่งใช้ในการเรียกใช้ไฟล์ raw 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
และคล้ายกับวิธีที่ AR เชื่อมโยงการสืบค้นของตัวเอง
ฉันจำเป็นต้องใช้ raw sql เพราะฉันล้มเหลวในการรับ composite_primary_keys ให้ทำงานกับ activerecord 2.3.8 ดังนั้นในการเข้าถึงตาราง sqlserver 2000 ด้วยคีย์หลักแบบผสมจำเป็นต้องมี Raw 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
หากมีวิธีแก้ไขที่ดีกว่าโปรดแบ่งปัน
ใน Rails 3.1 คุณควรใช้อินเทอร์เฟซแบบสอบถาม:
update และ update_all คือการดำเนินการที่คุณต้องการ
ดูรายละเอียดที่นี่: http://m.onkey.org/active-record-query-interface