Rails contoh SQL mentah

239

Bagaimana saya bisa mengubah kode ini menjadi sql mentah dan digunakan di rel? Karena Ketika saya menggunakan kode ini di heroku, ada kesalahan batas waktu permintaan. Saya pikir ini akan lebih cepat jika saya menggunakan sql mentah.

@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)
Johnny Cash
sumber
3
Menurut Anda mengapa SQL mentah lebih cepat? Bagaimana Anda tahu ini masalah SQL?
John Naegle
Tanpa melihat rencana kueri, saya akan menebak bahwa masalah yang Anda hadapi adalah pemesanan oleh Created_at. Anda mungkin melakukan pemindaian seq di seluruh tabel tersebut (oh dan membawa tabel proyek juga). Melakukan dua dari mereka dalam satu kali metode pengontrol di atas meja besar dan DB yang kurang bertenaga (database heroku disetel secara umum, dan relatif kurang bertenaga untuk $$ yang Anda habiskan), Anda cenderung mendapatkan batas waktu. Jika Anda tidak melakukan banyak sisipan ke dalam tabel ini, Anda bisa melakukan indeks yang diurutkan: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel
1
Yah itu akan selalu lebih cepat untuk memotong tangan sql (jika Anda tahu apa yang Anda lakukan). Rails membuat beberapa sql benar-benar jahat. Ini bekerja dengan baik tetapi untuk menjadi umum itu harus ... menjadi umum. Yang mengatakan Jim mungkin benar .. membuat pengindeksan akan lebih baik. Coba jalankan kueri Anda dalam pgadmin (terhadap db Anda)
baash05

Jawaban:

427

Kamu bisa melakukan ini:

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

records_array kemudian akan menjadi hasil dari kueri sql Anda dalam sebuah array yang bisa Anda ulangi.

Huy
sumber
52
catatan: records_arrayakan ada berbagai jenis untuk adaptor basis data yang berbeda. Jika Anda menggunakan PG, itu akan menjadi contoh PG::Result, bukan Array.
tybro0103
58
dan kemudian Anda harus memanggil objek valuesini PG::Resultuntuk mendapatkan array hasil
jobwat
3
@ baash05 Apa yang Anda maksud dengan "mendukung mengaksesnya melalui kelas." - ini berbicara tentang cara mengakses tabel tanpa model di sebelahnya
Yo Ludke
1
Sebenarnya tidak disebutkan tidak menggunakan model di OP. Hanya bagaimana mengeksekusi sql mentah. PaymentDetail.execute (sql) berfungsi.
baash05
20
Saya suka ActiveRecord::Base.connection.exec_querylebih baik karena mengembalikan ActiveRecords::Resultobjek yang memiliki metode praktis seperti .columnsdan .rowsuntuk mengakses header dan nilai. Array hash dari .executedapat merepotkan untuk ditangani dan memberi saya hasil yang berlebihan ketika saya berlari dengan klausa SUM GROUP BY.
Francio Rodrigues
181

Saya tahu ini sudah tua ... Tapi saya mengalami masalah yang sama hari ini dan menemukan solusi:

Model.find_by_sql

Jika Anda ingin membuat instance hasil:

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

Jika Anda hanya ingin hash nilai:

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"}
]

Objek hasil:

select_allmengembalikan resultobjek. Anda dapat melakukan hal-hal ajaib dengan itu.

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

Sumber:

  1. ActiveRecord - Findinig oleh SQL .
  2. Ruby on Rails - Hasil Rekaman Aktif .
João Paulo Motta
sumber
9
#find_by_sqlpersis apa yang saya inginkan, terima kasih banyak.
Shelvacu
#find_by_sql itu !!
Steven L.
28

Anda dapat menjalankan kueri mentah menggunakan ActiveRecord. Dan saya akan menyarankan untuk pergi dengan blok 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)
Deepak Mahakale
sumber
Hai .. apakah ada cara untuk mengirimkan parameter ke permintaan ini?
Akshay Goyal
@AkshayGoyal Anda dapat menggunakan interpolasi string ruby ​​normal di dalam blok. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck
2
Meskipun Anda dapat melakukannya, hal itu dapat memaparkan Anda pada kemungkinan injeksi SQL
Deepak Mahakale
26

Anda bisa melakukan SQL langsung untuk memiliki satu permintaan untuk kedua tabel. Saya akan memberikan contoh kueri yang disanitasi agar mudah-mudahan orang tidak menempatkan variabel langsung ke dalam string itu sendiri (bahaya injeksi SQL), meskipun contoh ini tidak menentukan kebutuhan untuk itu:

@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

Sunting : seperti kata Huy, cara sederhana adalah ActiveRecord::Base.connection.execute("..."). Cara lain adalah ActiveRecord::Base.connection.exec_query('...').rows. Dan Anda dapat menggunakan pernyataan siap asli, misalnya jika menggunakan postgres, pernyataan siap dapat dilakukan dengan raw_connection, mempersiapkan, dan exec_prepared seperti yang dijelaskan dalam https://stackoverflow.com/a/13806512/178651

Anda juga dapat memasukkan fragmen SQL mentah ke dalam kueri relasional ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html dan dalam asosiasi, cakupan, dll. Anda mungkin dapat membangun SQL yang sama dengan kueri relasional ActiveRecord dan dapat melakukan hal-hal keren dengan ARel sebagai Ernie menyebutkan di http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . Dan, tentu saja ada ORM lain, permata, dll.

Jika ini akan banyak digunakan dan menambahkan indeks tidak akan menyebabkan masalah kinerja / sumber daya lainnya, pertimbangkan untuk menambahkan indeks dalam DB untuk payment_details.created_at dan untuk payment_errors.created_at.

Jika banyak catatan dan tidak semua catatan perlu muncul sekaligus, pertimbangkan untuk menggunakan pagination:

Jika Anda perlu membuat paginasi, pertimbangkan untuk membuat tampilan dalam DB pertama yang disebut payment_records yang menggabungkan tabel payment_details dan payment_errors, kemudian miliki model untuk tampilan (yang akan hanya-baca). Beberapa DB mendukung pandangan terwujud, yang mungkin merupakan ide bagus untuk kinerja.

Juga pertimbangkan spesifikasi perangkat keras atau VM pada server Rails dan server DB, konfigurasi, ruang disk, kecepatan jaringan / latensi / dll., Kedekatan, dll. Dan pertimbangkan untuk menempatkan DB pada server / VM yang berbeda dari aplikasi Rails jika Anda belum, dll. .

Gary S. Weaver
sumber
Karena penanya mengurutkan berdasarkan tanggal, saya pikir pagination tidak akan membantu? Saya bukan yang terdalam pada SQL tetapi pemahaman saya adalah permintaan di posting asli akan perlu mengambil seluruh tabel untuk mengurutkannya - hanya dengan demikian dapat membatasi hasilnya. Saya menduga sebagian besar rencana permintaan ada di klausa urutan.
Jim Wrubel
@JimWrubel mengklarifikasi paragraf tentang pagination - kata-katanya sedikit tidak masuk akal.
Gary S. Weaver
2

Saya ingin bekerja dengan exec_querypara ActiveRecordkelas, karena ia mengembalikan pemetaan query berubah menjadi objek, sehingga mendapat sangat praktis dan produktif untuk iterate dengan objek ketika subjek Raw SQL.

Contoh:

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

dan kembalikan permintaan lengkap ini:

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

Untuk hanya mendapatkan daftar nilai

p values.rows

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

Untuk mendapatkan kolom kolom saja

p values.columns

["id", "name"]
Darlan Dieterich
sumber
0

Anda juga dapat mencampur SQL mentah dengan kondisi ActiveRecord, misalnya jika Anda ingin memanggil fungsi dalam kondisi:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
tsauerwein
sumber