Rails ví dụ SQL thô


239

Làm thế nào tôi có thể chuyển đổi mã này sang sql thô và sử dụng trong đường ray? Bởi vì khi tôi triển khai mã này trong heroku, có lỗi hết thời gian yêu cầu. Tôi nghĩ rằng điều này sẽ nhanh hơn nếu tôi sử dụng sql thô.

@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
Tại sao bạn nghĩ SQL thô sẽ nhanh hơn? Làm thế nào để bạn biết vấn đề SQL của nó?
John Naegle

Nếu không nhìn thấy kế hoạch truy vấn, tôi sẽ đoán rằng vấn đề bạn đang gặp phải là sắp xếp theo created_at. Bạn có thể đang thực hiện quét seq trên toàn bộ các bảng đó (oh và cũng đưa vào bảng dự án). Thực hiện hai trong số đó một lần phương thức bộ điều khiển trên một bảng lớn và DB không đủ năng lực (cơ sở dữ liệu heroku được điều chỉnh một cách tổng quát và tương đối kém đối với $$ bạn chi tiêu), bạn có khả năng bị hết thời gian. Nếu bạn không thực hiện nhiều thao tác chèn vào các bảng này, bạn có thể thực hiện một chỉ mục được sắp xếp: devcenter.heroku.com/articles/postgresql-indexes#sort-indexes
Jim Wrubel

1
Chà, sẽ luôn nhanh hơn khi cắt tay sql (nếu bạn biết bạn đang làm gì). Rails làm cho một số sql thực sự khó chịu. Nó hoạt động tốt nhưng để nói chung, nó phải ... nói chung. Điều đó nói rằng Jim có khả năng đúng .. tạo lập chỉ mục sẽ tốt hơn. Hãy thử chạy truy vấn của bạn trong pgadmin (so với db của bạn)
baash05

Câu trả lời:


427

Bạn có thể làm được việc này:

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

records_array sau đó sẽ là kết quả của truy vấn sql của bạn trong một mảng mà bạn có thể lặp qua.


52
lưu ý phụ: records_arraysẽ có các loại khác nhau cho các bộ điều hợp cơ sở dữ liệu khác nhau. Nếu bạn đang sử dụng PG, nó sẽ là một thể hiện của PG::Result, không Array.
tybro0103

58
và sau đó bạn cần gọi đối tượng valuesnày PG::Resultđể nhận được mảng kết quả
jobwat 26/03 '

3
@ baash05 Ý của bạn là gì khi "ủng hộ việc truy cập nó thông qua lớp học." - điều này nói về cách truy cập vào bảng mà không cần mô hình nào đó
Yo Ludke

1
Trên thực tế không có đề cập đến việc không sử dụng một mô hình trong OP. Chỉ cần làm thế nào để thực hiện sql thô. PaymentDetail.execute (sql) hoạt động.
baash05

20
Tôi thích ActiveRecord::Base.connection.exec_queryhơn vì nó trả về một ActiveRecords::Resultđối tượng có các phương thức tiện dụng như .columns.rowsđể truy cập các tiêu đề và giá trị. Mảng băm từ .executecó thể gây rắc rối để xử lý và cho tôi kết quả dư thừa khi tôi chạy với mệnh đề SUM GROUP BY.
Francio Coleues

181

Tôi biết điều này đã cũ ... Nhưng tôi đã gặp vấn đề tương tự ngày hôm nay và tìm ra giải pháp:

Model.find_by_sql

Nếu bạn muốn khởi tạo kết quả:

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

Nếu bạn chỉ muốn băm các giá trị:

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

Đối tượng kết quả:

select_alltrả về một resultđối tượng Bạn có thể làm những điều kỳ diệu với nó.

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

Nguồn:

  1. ActiveRecord - Findinig bằng SQL .
  2. Ruby on Rails - Kết quả ghi hoạt động .

9
#find_by_sqlchính xác là những gì tôi muốn, cảm ơn bạn rất nhiều.
Shelvacu

#find_by_sql là nó !!
Steven L.

28

Bạn có thể thực hiện truy vấn thô bằng cách sử dụng ActiveRecord. Và tôi sẽ đề nghị đi với khối 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)

Xin chào .. có cách nào để truyền tham số cho truy vấn này không?
Akshay Goyalty

@AkshayGidel bạn có thể sử dụng phép nội suy chuỗi ruby ​​bình thường trong khối. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck

2
Trong khi bạn có thể làm điều đó có thể khiến bạn gặp phải khả năng tiêm SQL
Deepak Mahakale

26

Bạn có thể thực hiện SQL trực tiếp để có một truy vấn duy nhất cho cả hai bảng. Tôi sẽ cung cấp một ví dụ truy vấn được khử trùng để hy vọng mọi người không đặt các biến trực tiếp vào chính chuỗi (nguy hiểm tiêm SQL), mặc dù ví dụ này không chỉ định nhu cầu về nó:

@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

Chỉnh sửa : như Huy đã nói, một cách đơn giản là ActiveRecord::Base.connection.execute("..."). Một cách khác là ActiveRecord::Base.connection.exec_query('...').rows. Và bạn có thể sử dụng các câu lệnh được chuẩn bị riêng, ví dụ: nếu sử dụng postgres, câu lệnh được chuẩn bị có thể được thực hiện với raw_connection, chuẩn bị và exec_prepared như được mô tả trong https://stackoverflow.com/a/13806512/178651

Bạn cũng có thể đặt các đoạn SQL thô vào các truy vấn quan hệ ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html và trong các liên kết, phạm vi, v.v. Bạn có thể có thể xây dựng cùng một SQL với các truy vấn quan hệ ActiveRecord và có thể làm những điều tuyệt vời với ARel như Ernie đề cập trong http://erniemiller.org/2010/03/11/advified-activerecord-3-queries-with-arel/ . Và, tất nhiên có các ORM, đá quý khác, v.v.

Nếu điều này sẽ được sử dụng nhiều và việc thêm các chỉ mục sẽ không gây ra các vấn đề về hiệu suất / tài nguyên khác, hãy xem xét việc thêm một chỉ mục trong DB cho Payment_details.created_at và cho Payment_errors.created_at.

Nếu nhiều hồ sơ và không phải tất cả các hồ sơ cần hiển thị cùng một lúc, hãy xem xét sử dụng phân trang:

Nếu bạn cần phân trang, hãy xem xét việc tạo chế độ xem trong DB trước tiên được gọi là Payment_records kết hợp các bảng Payment_details và Payment_errors, sau đó có một mô hình cho chế độ xem (sẽ chỉ đọc). Một số DB hỗ trợ các khung nhìn cụ thể hóa, có thể là một ý tưởng tốt cho hiệu suất.

Đồng thời xem xét thông số kỹ thuật phần cứng hoặc VM trên máy chủ Rails và máy chủ DB, cấu hình, dung lượng ổ đĩa, tốc độ mạng / độ trễ / v.v., độ gần, v.v. Và xem xét đưa DB lên máy chủ / VM khác với ứng dụng Rails nếu bạn chưa, v.v. .


Vì người hỏi sắp xếp theo ngày, tôi nghĩ rằng phân trang sẽ không giúp được gì? Tôi không phải là người sâu sắc nhất về SQL nhưng sự hiểu biết của tôi là truy vấn trong bài viết gốc sẽ cần tìm nạp toàn bộ bảng để sắp xếp nó - chỉ sau đó nó mới có thể giới hạn kết quả. Tôi nghi ngờ phần lớn của kế hoạch truy vấn là trong mệnh đề thứ tự.
Jim Wrubel

@JimWrubel đã làm rõ đoạn văn về phân trang - cách diễn đạt hơi khó hiểu.
Gary S. Weaver

2

Tôi muốn làm việc với exec_querycácActiveRecord lớp học, bởi vì nó sẽ trả về các bản đồ của truy vấn chuyển vào đối tượng, vì vậy nó được rất thực tế và hiệu quả để lặp với các đối tượng khi đối tượng là liệu SQL.

Thí dụ:

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

và trả về truy vấn đầy đủ này:

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

Để chỉ nhận danh sách các giá trị

p values.rows

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

Để chỉ nhận các cột trường

p values.columns

["id", "name"]

0

Bạn cũng có thể trộn SQL thô với các điều kiện ActiveRecord, ví dụ nếu bạn muốn gọi một hàm trong một điều kiện:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.