Cách thực hiện cập nhật thô sql với liên kết động trong rails


98

Tôi muốn thực hiện một bản cập nhật raw sql như dưới đây:

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

SQL này sẽ được thực thi bởi ActiveRecord::Base.connection.execute, nhưng tôi không biết cách chuyển các giá trị tham số động vào phương thức.

Ai đó có thể cho tôi bất kỳ sự giúp đỡ về nó?


Tại sao bạn muốn làm sử dụng SQL liệu này, điểm của ActiveRecord là để giúp bạn tránh được điều đó ...
Andrew

nếu tôi sử dụng AR, trước tiên tôi nên lấy Model Object bằng phương pháp tìm của AR với trường id, sau đó thực hiện thao tác cập nhật. Vì vậy, từ quan điểm của hoạt động, một CẬP NHẬT AR cần hai sqls với cơ sở dữ liệu; mặt khác, tôi không chắc phương pháp cập nhật của AR sử dụng ràng buộc động. vì vậy tôi muốn sử dụng raw sql với liên kết động cho một lần tương tác với db cho hoạt động cập nhật, nhưng tôi không biết cách chuyển các tham số để thay thế? tính bằng sql bằng AR.
ywenbo

27
Có nhiều lý do hợp lệ để làm điều này. Đầu tiên, truy vấn có thể quá phức tạp để chuyển sang sử dụng theo cách Ruby thông thường ... thứ hai, các tham số có thể có các ký tự đặc biệt như% hoặc dấu ngoặc kép, và thật khó để thoát khỏi ...
Henley Chiu

3
@Andrew, tốt hơn là sử dụng các hàm mysql thô hơn là chấp nhận "sự tiện lợi" mà AR mang lại.
Màu xanh lá cây

4
@Green không nếu bạn muốn chuyển ứng dụng của mình từ MySQL sang PostgreSQL hoặc thứ gì đó khác. Một trong những điểm chính của ORM là làm cho ứng dụng của bạn có thể di động.
Andrew

Câu trả lời:


102

Có vẻ như Rails API chỉ đưa ra các phương pháp để thực hiện điều này một cách chung chung. Bạn có thể thử truy cập kết nối cơ bản và sử dụng các phương pháp của nó, ví dụ như đối với MySQL:

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

Tôi không chắc liệu có sự phân nhánh khác để thực hiện việc này hay không (các kết nối còn mở, v.v.). Tôi sẽ theo dõi mã Rails cho một bản cập nhật bình thường để xem nó đang làm gì ngoài truy vấn thực tế.

Sử dụng các truy vấn đã chuẩn bị có thể giúp bạn tiết kiệm một lượng nhỏ thời gian trong cơ sở dữ liệu, nhưng trừ khi bạn đang thực hiện việc này hàng triệu lần liên tiếp, có lẽ bạn nên xây dựng bản cập nhật bằng cách thay thế Ruby thông thường, ví dụ:

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

hoặc sử dụng ActiveRecord như những người bình luận đã nói.


26
Hãy cẩn thận với phương pháp "thay thế Ruby thông thường" được đề xuất bằng cách sử dụng field=#{value}vì điều này khiến bạn dễ bị tấn công SQL injection. Nếu bạn đi theo con đường đó, hãy kiểm tra mô-đun ActiveRecord :: ConnectionAdapters :: Trích dẫn .
Paul Annesley

3
Xin lưu ý, mysql2 không hỗ trợ các câu lệnh chuẩn bị sẵn, hãy xem thêm: stackoverflow.com/questions/9906437/…
reto vào

1
@reto Có vẻ như họ đã thân thiết: github.com/brianmario/mysql2/pull/289
Brian Deterling

3
Không có preparetuyên bố nào trong Mysql2
Green

1
Theo tài liệu: "Lưu ý: tùy thuộc vào trình kết nối cơ sở dữ liệu của bạn, kết quả trả về bằng phương pháp này có thể được quản lý bộ nhớ theo cách thủ công. Hãy xem xét sử dụng trình bao bọc #exec_query thay thế." . executelà phương pháp nguy hiểm và có thể gây rò rỉ bộ nhớ hoặc tài nguyên khác.
kolen

32

ActiveRecord::Base.connectionquotephương thức nhận giá trị chuỗi (và tùy chọn đối tượng cột). Vì vậy, bạn có thể nói điều này:

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

Lưu ý nếu bạn đang di chuyển Rails hoặc đối tượng ActiveRecord, bạn có thể rút ngắn nó thành:

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

CẬP NHẬT: Như @kolen đã chỉ ra, bạn nên sử dụng exec_updatethay thế. Điều này sẽ xử lý việc trích dẫn cho bạn và cũng tránh rò rỉ bộ nhớ. Tuy nhiên, chữ ký hoạt động hơi khác một chút:

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

Ở đây tham số cuối cùng là một mảng các bộ giá trị đại diện cho các tham số ràng buộc. Trong mỗi bộ, mục nhập đầu tiên là loại cột và mục nhập thứ hai là giá trị. Bạn có thể cung cấp nilcho loại cột và Rails thường sẽ làm đúng.

Ngoài ra còn có exec_query, exec_insertexec_delete, tùy thuộc vào những gì bạn cần.


3
Theo tài liệu: "Lưu ý: tùy thuộc vào trình kết nối cơ sở dữ liệu của bạn, kết quả trả về bằng phương pháp này có thể được quản lý bộ nhớ theo cách thủ công. Hãy xem xét sử dụng trình bao bọc #exec_query thay thế." . executelà phương pháp nguy hiểm và có thể gây rò rỉ bộ nhớ hoặc tài nguyên khác.
kolen

1
Wow bắt tốt! Đây là tài liệu . Ngoài ra, có vẻ như bộ điều hợp Postgres sẽ bị rò rỉ ở đây.
Paul A Jungwirth

Tự hỏi, nếu AR khiến chúng ta không có gì on duplicate key updateở khắp mọi nơi
Shrinath

1
@Shrinath vì câu hỏi này là về cách viết SQL thô, bạn có thể thực hiện truy vấn ON CONFLICT DO UPDATEnếu muốn. Đối với SQL phi nguyên đá quý này có vẻ tiện dụng: github.com/jesjos/active_record_upsert
Paul A Jungwirth

4

Bạn chỉ nên sử dụng một cái gì đó như:

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

Đó sẽ là thủ thuật. Sử dụng phương thức ActiveRecord :: Base # send để gọi sanitize_sql_for_assignment làm cho Ruby (ít nhất là phiên bản 1.8.7) bỏ qua thực tế rằng sanitize_sql_for_assignment thực sự là một phương thức được bảo vệ.


2

Đôi khi tốt hơn nên sử dụng tên của lớp cha thay vì tên của bảng:

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

Ví dụ: lớp cơ sở "Người", các lớp con (và bảng cơ sở dữ liệu) "Máy khách" và "Người bán" Thay vào đó sử dụng:

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

Bạn có thể sử dụng đối tượng của lớp cơ sở bằng cách này:

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

1

Tại sao sử dụng SQL thô cho việc này?

Nếu bạn có một mô hình cho nó, hãy sử dụng 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

Nếu bạn không có mô hình cho nó (chỉ có bảng), bạn có thể tạo một tệp và mô hình sẽ kế thừa từActiveRecord::Base

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

và sử dụng wherelại tương tự như trên:


2
Điều này quá kém hiệu quả, phải không. Điều này không giao dịch một câu lệnh cập nhật cho một lựa chọn, đưa các bản ghi vào bộ nhớ rails, cập nhật từng bản ghi và gửi lại một câu lệnh cập nhật cho mỗi bản ghi. 1 bản cập nhật so với 1 bản ghi và nhiều băng thông, v.v.? AR có tối ưu hóa điều này hay không? Tôi không nghĩ nó có.
Jimi Kimble,

0

Đây là một thủ thuật mà tôi đã thực hiện gần đây để thực thi sql thô với các liên kết:

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

nơi ApplicationRecordxác định điều này:

# 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

và điều đó tương tự như cách AR liên kết các truy vấn của chính nó.


-11

Tôi cần sử dụng sql thô vì tôi không thể tải composite_primary_keys hoạt động với activerecord 2.3.8. Vì vậy, để truy cập bảng sqlserver 2000 bằng khóa chính tổng hợp, cần có sql thô.

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

Nếu có giải pháp tốt hơn, xin vui lòng chia sẻ.


10
có thể tiêm sql ở đây!
mystdeim

-18

Trong Rails 3.1, bạn nên sử dụng giao diện truy vấn:

  • mới (thuộc tính)
  • tạo (thuộc tính)
  • tạo! (thuộc tính)
  • tìm (id_or_array)
  • tiêu diệt (id_or_array)
  • tiêu diệt tất cả
  • xóa (id_or_array)
  • xóa hết
  • cập nhật (id, bản cập nhật)
  • update_all (cập nhật)
  • tồn tại?

update và update_all là thao tác bạn cần.

Xem chi tiết tại đây: http://m.onkey.org/active-record-query-interface

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.