Raylarda dinamik bağlama ile ham güncelleme sql nasıl çalıştırılır


102

Aşağıdaki gibi bir ham sql güncellemesi yürütmek istiyorum:

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

Bu SQL tarafından çalıştırılacak ActiveRecord::Base.connection.execute, ancak dinamik parametre değerlerini yönteme nasıl aktaracağımı bilmiyorum.

Birisi bana bu konuda yardım edebilir mi?


Bunu neden ham SQL kullanarak yapmak istiyorsunuz, ActiveRecord'un amacı bundan kaçınmanıza yardımcı olmaktır ...
Andrew

AR kullanıyorsam öncelikle AR'nin find metodu ile id alanı ile Model Object almalı, ardından güncelleme işlemi yapmalıyım. Dolayısıyla, işlemler açısından bir UPDATE AR veritabanı ile iki sql'ye ihtiyaç duyar; Öte yandan, AR'nin güncelleme yönteminin dinamik bağlama kullandığından emin değilim. bu nedenle, güncelleme işlemi için db ile bir etkileşim için dinamik bağlamalı ham sql kullanmak istiyorum, ancak? 'yi değiştirmek için parametreleri nasıl geçireceğimi bilmiyorum. AR'ye göre sql'de.
ywenbo

27
Bunu yapmanın birçok geçerli nedeni var. Birincisi, sorgu normal Ruby yöntemine çevrilemeyecek kadar karmaşık olabilir ... ikincisi, parametreler% veya tırnak işaretleri gibi özel karakterlere sahip olabilir ve bu durumdan kaçmak baş belası olabilir ..
Henley Chiu

3
@Andrew, AR'nin sunduğu "rahatlığı" kabul etmektense ham mysql işlevlerini kullanmak daha iyidir.
Yeşil

4
@ Yeşil, uygulamanızı MySQL'den PostgreSQL'e veya başka bir şeye taşımak istemezseniz değil. Bir ORM'nin en önemli noktalarından biri, uygulamanızı taşınabilir hale getirmektir.
Andrew

Yanıtlar:


103

Rails API, bunu genel olarak yapmak için yöntemler ortaya koyuyor gibi görünmüyor. Altta yatan bağlantıya erişmeyi ve bunun yöntemlerini kullanmayı deneyebilirsiniz, örneğin MySQL için:

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

Bunu yapmanın başka sonuçları olup olmadığından emin değilim (açık bırakılan bağlantılar vb.). Gerçek sorgu dışında ne yaptığını görmek için normal bir güncelleme için Rails kodunu izlerdim.

Hazır sorguları kullanmak, veritabanında size küçük bir zaman kazandırabilir, ancak bunu arka arkaya milyonlarca kez yapmadığınız sürece, güncellemeyi normal Ruby ikamesi ile oluşturmak muhtemelen daha iyi olur, örn.

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

veya yorum yapanların söylediği gibi ActiveRecord kullanarak.


26
field=#{value}Sizi SQL enjeksiyon saldırılarına tamamen açık bıraktığından , önerilen "normal Ruby değiştirme" yöntemine dikkat edin . Bu yoldan giderseniz, ActiveRecord :: ConnectionAdapters :: Quoting modülüne bakın.
Paul Annesley

4
Not, mysql2 hazırlanmış deyimleri desteklemez Lütfen, ayrıca bkz: stackoverflow.com/questions/9906437/...
Reto

1
@reto Yine de yakınlar gibi görünüyor: github.com/brianmario/mysql2/pull/289
Brian Deterling

3
prepareMysql2
Green

1
Belgelere göre: "Not: veritabanı bağlayıcınıza bağlı olarak, bu yöntem tarafından döndürülen sonuç manuel olarak bellek yönetilebilir. Bunun yerine # exec_query sarmalayıcısını kullanmayı düşünün." . executetehlikeli bir yöntemdir ve bellek veya diğer kaynak sızıntılarına neden olabilir.
kolen

33

ActiveRecord::Base.connectionquotebir dize değeri (ve isteğe bağlı olarak sütun nesnesi) alan bir yönteme sahiptir . Yani şunu söyleyebilirsin:

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

Bir Rails geçişi veya ActiveRecord nesnesindeyseniz, bunu şu şekilde kısaltabilirsiniz:

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

GÜNCELLEME: @kolen'in belirttiği gibi, exec_updatebunun yerine kullanmalısınız . Bu, sizin için alıntıları halledecek ve ayrıca bellek sızıntısını önleyecektir. İmza biraz daha farklı çalışıyor:

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

Burada son parametre, bağlama parametrelerini temsil eden bir tuple dizisidir. Her demette, ilk giriş sütun türüdür ve ikincisi değerdir. nilSütun türü için verebilirsiniz ve Rails genellikle doğru olanı yapar.

Orada da exec_query, exec_insertve exec_deleteneye ihtiyacınız bağlı.


3
Belgelere göre: "Not: veritabanı bağlayıcınıza bağlı olarak, bu yöntem tarafından döndürülen sonuç manuel olarak bellek yönetilebilir. Bunun yerine # exec_query sarmalayıcısını kullanmayı düşünün." . executetehlikeli bir yöntemdir ve bellek veya diğer kaynak sızıntılarına neden olabilir.
kolen

1
Vay iyi yakaladın! İşte belgeler . Ayrıca Postgres adaptörü buraya sızacak gibi görünüyor .
Paul A Jungwirth

AR bize on duplicate key updateher yerde hiçbir şey
bırakmazsa

1
@Shrinath Bu soru ham SQL yazmakla ilgili ON CONFLICT DO UPDATEolduğundan, isterseniz sorgu da yapabilirsiniz . Ham olmayan SQL için bu mücevher kullanışlı görünüyor: github.com/jesjos/active_record_upsert
Paul A Jungwirth

4

Şuna benzer bir şey kullanmalısınız:

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

Bu hile yapar. Kullanılması ActiveRecord :: Base # gönderme çağırmak için yöntem sanitize_sql_for_assignment Yakut (en azından 1.8.7 sürümü) gerçeğini atlamak yapar sanitize_sql_for_assignment aslında bir korumalı bir yöntemdir.


2

Bazen tablo adı yerine ana sınıfın adını kullanmak daha iyi olur:

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

Örneğin, "Kişi" temel sınıfı, alt sınıflar (ve veritabanı tabloları) "Müşteri" ve "Satıcı" Bunun yerine şunu kullanmak:

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

Temel sınıf nesnesini şu şekilde kullanabilirsiniz:

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

1

Bunun için neden ham SQL kullanıyorsunuz?

Bunun için bir modeliniz varsa şunları kullanın 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

Bunun için bir modeliniz yoksa (sadece tablo), miras alacak bir dosya ve model oluşturabilirsiniz.ActiveRecord::Base

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

ve yine whereyukarıdakiyle aynı şeyi kullanın :


2
Bu çok daha az verimli, değil mi? Bu, bir seçim için bir güncelleme bildirimi takas etmez, kayıtları ray belleğine getirmez, her kaydı güncelleştirmez ve her kayıt için bir güncelleme bildirimi geri göndermez. Kayıt başına 1 güncellemeye karşı 1 güncelleme ve çok fazla bant genişliği vb. AR bunu optimize ediyor mu, değil mi? Ben öyle olduğunu sanmıyorum.
Jimi Kimble

0

İşte yakın zamanda ham sql'yi bağlamalarla çalıştırmak için üzerinde çalıştığım bir numara:

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

bunu nerede ApplicationRecordtanımlar:

# 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

ve bu AR'nin kendi sorgularını bağlama şekline benzer.


-11

Ham sql kullanmam gerekiyordu çünkü composite_primary_keys'i activerecord 2.3.8 ile çalışacak şekilde elde edemedim. Bu nedenle sqlserver 2000 tablosuna bileşik birincil anahtarla erişmek için ham sql gerekiyordu.

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

Daha iyi bir çözüm varsa lütfen paylaşın.


11
sql enjeksiyonu burada mümkündür!
mystdeim

-21

Rails 3.1'de sorgu arayüzünü kullanmalısınız:

  • yeni (öznitelikler)
  • oluştur (öznitelikler)
  • oluştur! (öznitelikler)
  • bul (id_or_array)
  • yok (id_or_array)
  • Hepsini yok et
  • sil (id_or_array)
  • hepsini sil
  • güncelleme (kimlikler, güncellemeler)
  • update_all (güncellemeler)
  • var mı?

update ve update_all, ihtiyacınız olan işlemdir.

Ayrıntılara buradan bakın: http://m.onkey.org/active-record-query-interface

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.