Rails ตัวอย่าง SQL ดิบ


239

ฉันจะแปลงรหัสนี้เป็น sql raw และใช้ในรางได้อย่างไร เพราะเมื่อฉันปรับใช้รหัสนี้ใน heroku มีข้อผิดพลาดการหมดเวลาคำขอฉันคิดว่ามันจะเร็วขึ้นถ้าฉันใช้ raw sql

@payments = PaymentDetail.joins(:project).order('payment_details.created_at desc')
@payment_errors = PaymentError.joins(:project).order('payment_errors.created_at desc')

@all_payments = (@payments + @payment_errors)

3
ทำไมคุณถึงคิดว่า SQL ดิบจะเร็วขึ้น? คุณรู้ปัญหาของ SQL ได้อย่างไร
John Naegle

หากไม่เห็นแผนการสืบค้นฉันจะเดาว่าปัญหาที่คุณกำลังสั่งซื้อโดย created_at คุณอาจจะทำการสแกน seq ในตารางทั้งหมด (โอ้และนำเข้าไปในตารางโครงการด้วย) การทำสองวิธีในวิธีการควบคุมครั้งเดียวบนโต๊ะขนาดใหญ่และฐานข้อมูล underpowered (ฐานข้อมูล heroku นั้นได้รับการปรับโดยทั่วไปและค่อนข้างต่ำสำหรับเงินดอลลาร์ที่คุณใช้ไป) คุณมีโอกาสหมดเวลา หากคุณไม่ได้ทำการแทรกจำนวนมากลงในตารางเหล่านี้คุณสามารถทำดัชนีเรียงลำดับ: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel

1
มันจะเร็วกว่าที่จะตัด sql (ถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่) Rails ทำให้ SQL ที่น่ารังเกียจ มันใช้งานได้ดี แต่ต้องเป็นแบบทั่วไปต้อง ... เป็นแบบทั่วไป ที่กล่าวว่า Jim มีแนวโน้มที่ถูกต้อง .. การสร้างดัชนีจะดีกว่า ลองเรียกใช้คิวรีของคุณใน pgadmin (เทียบกับฐานข้อมูลของคุณ)
baash05

คำตอบ:


427

คุณสามารถทำได้:

sql = "Select * from ... your sql query here"
records_array = ActiveRecord::Base.connection.execute(sql)

records_array จะเป็นผลมาจากการสืบค้น sql ของคุณในอาเรย์ซึ่งคุณสามารถทำซ้ำได้


52
หมายเหตุ: records_arrayจะเป็นประเภทที่แตกต่างกันสำหรับอะแดปเตอร์ฐานข้อมูลที่แตกต่างกัน หากคุณกำลังใช้ PG ก็จะเป็นตัวอย่างของการไม่ได้PG::Result Array
tybro0103

58
แล้วคุณจะต้องเรียกวัตถุvaluesนี้PG::Resultเพื่อรับอาร์เรย์ผลลัพธ์
jobwat

3
@ baash05 คุณหมายความว่าอย่างไร "ชอบการเข้าถึงผ่านชั้นเรียน" - สิ่งนี้พูดเกี่ยวกับวิธีการเข้าถึงตารางโดยไม่มีแบบจำลอง ariond มัน
Yo Ludke

1
จริงๆแล้วไม่มีการกล่าวถึงการไม่ใช้โมเดลใน OP เพียงแค่วิธีการดำเนินการ sql ดิบ การชำระเงินรายละเอียดดำเนินการ (sql)
baash05

20
ฉันชอบActiveRecord::Base.connection.exec_queryดีกว่าเพราะมันคืนActiveRecords::Resultวัตถุซึ่งมีวิธีการที่สะดวกเช่น.columnsและ.rowsเข้าถึงส่วนหัวและค่า การเรียงลำดับของแฮ็ช.executeอาจทำให้ลำบากในการจัดการและให้ผลลัพธ์ซ้ำซ้อนเมื่อฉันใช้ประโยค SUM GROUP BY
Francio Rodrigues

181

ฉันรู้ว่ามันเก่า ... แต่วันนี้ฉันมีปัญหาเดียวกันและพบวิธีแก้ไข:

Model.find_by_sql

หากคุณต้องการยกตัวอย่างผลลัพธ์:

Client.find_by_sql("
  SELECT * FROM clients
  INNER JOIN orders ON clients.id = orders.client_id
  ORDER BY clients.created_at desc
")
# => [<Client id: 1, first_name: "Lucas" >, <Client id: 2, first_name: "Jan">...]

Model.connection.select_all('sql').to_hash

หากคุณต้องการแฮชของค่า:

Client.connection.select_all("SELECT first_name, created_at FROM clients
   WHERE id = '1'").to_hash
# => [
  {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
  {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
]

วัตถุผลลัพธ์:

select_allส่งคืนresultวัตถุ คุณสามารถทำสิ่งมหัศจรรย์กับมัน

result = Post.connection.select_all('SELECT id, title, body FROM posts')
# Get the column names of the result:
result.columns
# => ["id", "title", "body"]

# Get the record values of the result:
result.rows
# => [[1, "title_1", "body_1"],
      [2, "title_2", "body_2"],
      ...
     ]

# Get an array of hashes representing the result (column => value):
result.to_hash
# => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
      {"id" => 2, "title" => "title_2", "body" => "body_2"},
      ...
     ]

# ActiveRecord::Result also includes Enumerable.
result.each do |row|
  puts row['title'] + " " + row['body']
end

แหล่งที่มา:

  1. ActiveRecord - Findinig โดย SQL
  2. Ruby on Rails - ใช้งานผลลัพธ์บันทึก

9
#find_by_sqlเป็นสิ่งที่ฉันต้องการขอบคุณมาก
Shelvacu

#find_by_sql ใช่แล้ว !!
Steven L.

28

ActiveRecordคุณสามารถดำเนินการค้นหาโดยใช้วัตถุดิบ และฉันจะแนะนำให้ไปกับบล็อก SQL

query = <<-SQL 
  SELECT * 
  FROM payment_details
  INNER JOIN projects 
          ON projects.id = payment_details.project_id
  ORDER BY payment_details.created_at DESC
SQL

result = ActiveRecord::Base.connection.execute(query)

สวัสดี .. มีวิธีการส่งผ่านพารามิเตอร์ไปยังแบบสอบถามนี้หรือไม่?
Akshay Goyal

@AkshayGoyal คุณสามารถใช้การแก้ไขสตริงทับทิมปกติภายในบล็อก SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck

2
ในขณะที่คุณสามารถทำเช่นนั้นมันอาจทำให้คุณมีความเป็นไปได้ของการฉีด SQL
Deepak Mahakale

26

คุณสามารถกำหนดให้ SQL มีคิวรีเดี่ยวสำหรับทั้งสองตารางได้ ฉันจะให้ตัวอย่างแบบสอบถามแบบ sanitized เพื่อหวังว่าจะป้องกันไม่ให้ผู้คนใส่ตัวแปรลงในสตริงโดยตรง (SQL injection อันตราย) แม้ว่าตัวอย่างนี้ไม่ได้ระบุความต้องการ:

@results = []
ActiveRecord::Base.connection.select_all(
  ActiveRecord::Base.send(:sanitize_sql_array, 
   ["... your SQL query goes here and ?, ?, ? are replaced...;", a, b, c])
).each do |record|
  # instead of an array of hashes, you could put in a custom object with attributes
  @results << {col_a_name: record["col_a_name"], col_b_name: record["col_b_name"], ...}
end

แก้ไข : เป็น Huy ActiveRecord::Base.connection.execute("...")กล่าวว่าเป็นวิธีที่ง่ายคือ ActiveRecord::Base.connection.exec_query('...').rowsอีกวิธีหนึ่งคือ และคุณสามารถใช้คำสั่งที่เตรียมไว้ดั้งเดิมเช่นหากใช้ postgres คำสั่งที่เตรียมไว้สามารถทำได้ด้วย raw_connection เตรียมและ exec_prepared ดังที่อธิบายไว้ในhttps://stackoverflow.com/a/13806512/178651

คุณยังสามารถใส่ raw SQL fragments ลงในเคียวรีเชิงสัมพันธ์ ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html และในการเชื่อมโยงขอบเขต ฯลฯ คุณอาจสร้าง SQL เดียวกันกับคิวรีเชิงสัมพันธ์ ActiveRecord และสามารถทำสิ่งดีๆได้ด้วย Arel เป็นเออร์นี่กล่าวถึงในhttp://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ และแน่นอนว่ามี ORM อื่น ๆ อัญมณีและอื่น ๆ

หากสิ่งนี้จะถูกนำไปใช้เป็นจำนวนมากและการเพิ่มดัชนีจะไม่ทำให้เกิดปัญหาเกี่ยวกับประสิทธิภาพ / ทรัพยากรอื่น ๆ ให้ลองเพิ่มดัชนีใน DB สำหรับ payment_details.created_at และสำหรับ payment_errors.created_at

หากบันทึกจำนวนมากและไม่ใช่ระเบียนทั้งหมดจำเป็นต้องแสดงขึ้นมาพร้อมกันให้ลองใช้การแบ่งหน้า:

หากคุณต้องการให้เลขหน้าลองพิจารณาสร้างมุมมองในฐานข้อมูลแรกที่เรียกว่า payment_records ซึ่งรวมตาราง payment_details และ payment_errors จากนั้นมีโมเดลสำหรับมุมมอง (ซึ่งจะเป็นแบบอ่านอย่างเดียว) บางฐานข้อมูลสนับสนุนมุมมองที่ปรากฏขึ้นซึ่งอาจเป็นความคิดที่ดีสำหรับประสิทธิภาพ

พิจารณาฮาร์ดแวร์หรือ VM specs บนเซิร์ฟเวอร์ Rails และเซิร์ฟเวอร์ DB, config, พื้นที่ดิสก์, ความเร็วเครือข่าย / latency / etc., proximity ฯลฯ และพิจารณาวาง DB บนเซิร์ฟเวอร์ / VM ที่แตกต่างจากแอพ Rails หากคุณไม่ได้ทำ ฯลฯ .


เนื่องจากผู้ถามเรียงลำดับตามวันที่ฉันคิดว่าการให้เลขหน้าไม่ช่วยอะไร ฉันไม่ได้ลึกที่สุดใน SQL แต่ความเข้าใจของฉันคือแบบสอบถามในการโพสต์เดิมจะต้องดึงตารางทั้งหมดเพื่อจัดเรียง - เท่านั้นแล้วมันสามารถ จำกัด ผลลัพธ์ ฉันสงสัยว่าแผนแบบสอบถามจำนวนมากอยู่ในส่วนคำสั่งซื้อ
Jim Wrubel

@JimWrubel ชี้แจงย่อหน้าเกี่ยวกับการให้เลขหน้า - ถ้อยคำต่างออกไปเล็กน้อย
Gary S. Weaver

2

ผมต้องการที่จะทำงานร่วมกับexec_queryของActiveRecordชั้นเพราะมันกลับทำแผนที่ของแบบสอบถามเปลี่ยนเป็นวัตถุจึงได้รับมากในทางปฏิบัติและการผลิตเพื่อย้ำกับวัตถุเมื่อวัตถุดิบ SQL

ตัวอย่าง:

values = ActiveRecord::Base.connection.exec_query("select * from clients")
p values

และส่งคืนข้อความค้นหาที่สมบูรณ์นี้:

[{"id": 1, "name": "user 1"}, {"id": 2, "name": "user 2"}, {"id": 3, "name": "user 3"}]

เพื่อรับรายการค่าเท่านั้น

p values.rows

[[1, "user 1"], [2, "user 2"], [3, "user 3"]]

เพื่อให้ได้คอลัมน์คอลัมน์เท่านั้น

p values.columns

["id", "name"]

0

คุณสามารถผสม raw SQL กับเงื่อนไข ActiveRecord ได้เช่นถ้าคุณต้องการเรียกใช้ฟังก์ชันในเงื่อนไข:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.