Rails3: выполнение SQL с подстановкой хеша, например .where () - PullRequest
2 голосов
/ 12 мая 2011

С такой простой моделью

class Model < ActiveRecord::Base
   # ...
end

мы можем делать такие запросы

Model.where(["name = :name and updated_at >= :D", \
  { :D => (Date.today - 1.day).to_datetime, :name => "O'Connor" }])

Где значения в хэше будут подставлены в окончательный оператор SQL с надлежащим экранированием в зависимости от базового механизма базы данных.

Я хотел бы знать подобную функцию для выполнения SQL, такую ​​как:

ActiveRecord::Base.connection.execute( \
    ["update models set name = :name, hired_at = :D where id = :id;"], \
    { :id => 73465, :D => DateTime.now, :name => "O'My God" }] \
  ) # THIS CODE IS A FANTASY. NOT WORKING.

(Пожалуйста, не пытайтесь решить пример с загрузкой объекта Model, изменением и затем сохранением! Этот пример является только иллюстрацией для функции, которую я хотел бы иметь / знать. Сконцентрируйтесь на предмете!)

Первоначальная проблема заключается в том, что я хочу вставить большой объем данных (много тысяч строк) в базу данных. Я хочу использовать некоторые функции абстракции SQL платформы ActiveRecord, но я не хочу использовать объекты моделей, основанные на ActiveRecord :: Base, потому что они чертовски медленны! (8 запросов в секунду для моей текущей проблемы.)

Ответы [ 5 ]

3 голосов
/ 04 ноября 2011

Расширение решения @peufeu конкретным примером кода для массовой вставки:

users_places = []
users_values = []
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
params[:users].each do |user|
    users_places << "(?,?,?,?)"
    users_values << user[:name] << user[:punch_line] << timestamp << timestamp
end

bulk_insert_users_sql_arr = ["INSERT INTO users (name, punch_line, created_at, updated_at) VALUES #{users_places.join(", ")}"] + users_values
begin
    sql = ActiveRecord::Base.send(:sanitize_sql_array, bulk_insert_users_sql_arr)
    ActiveRecord::Base.connection.execute(sql)
rescue
    "something went wrong with the bulk insert sql query"
end

Вот ссылка на метод sanitize_sql_array в ActiveRecord :: Base , он генерирует правильную строку запроса, экранируя одинарные кавычки в строках. Например, punch_line «Не позволяйте им вас унизить» станет «Не позволяйте им вас унизить».

3 голосов
/ 12 мая 2011
query = ActiveRecord::Base.connection.raw_connection.prepare("INSERT INTO users (name) VALUES(:name)")
query.execute(:name => 'test_name')
query.close
1 голос
/ 03 сентября 2014

Недавно я столкнулся с подобной проблемой, когда пытался вставить записи 100K + в базу данных MySQL для приложения Rails 4 с использованием gem mysql2.Данные включали символы, которые должны были быть продезинфицированы перед вставкой.

Решение, которое я закончил, заключалось в слегка измененной версии варианта 3, описанной в https://www.coffeepowered.net/2009/01/23/mass-inserting-data-in-rails-without-killing-your-performance/

Вот соответствующиеблок кода по приведенной выше ссылке:

TIMES = 10000
inserts = []
TIMES.times do
  inserts.push "(3.0, '2009-01-23 20:21:13', 2, 1)"
end
sql = "INSERT INTO user_node_scores (`score`, `updated_at`, `node_id`, `user_id`) VALUES #{inserts.join(", ")}"

Внесенное мной изменение использовало открытый метод ActiveRecord :: Base.sanitize () для значений, которые требовали его.

inserts = []
created = Time.now.strftime "%Y-%m-%d %H:%M:%S"
params[:audits].each do |audit|
  inserts.push "(#{audit.user_id), #{created}," + ActiveRecord::Base.sanitize(audit.comment) + ", #{audit.status})"
end
sql = "INSERT INTO user_audits (`user_id`, `created_at`, `comment`, `status`) VALUES #{inserts.join(", ")}"
1 голос
/ 12 мая 2011

Да, вы можете сделать необработанный SQL, но посмотрите на гем ar-extensions, который помогает при пакетной вставке:

https://github.com/zdennis/ar-extensions

Вот пост о нем и другие различные техники:

http://www.coffeepowered.net/2009/01/23/mass-inserting-data-in-rails-without-killing-your-performance/

1 голос
/ 12 мая 2011

Для INSERT, пакетирование их с помощью длинного предложения VALUES (как показано по ссылке Саймона) является самым быстрым способом (если вы не хотите сгенерировать текстовый файл и загрузить его в свою базу данных с помощью MySQL LOAD DATA INFILE). Но вы должны быть очень осторожны с экранированием ваших текстовых значений (что не сделано в примере).

Я спрашивал "какую базу данных вы используете", потому что это имеет значение для массовых обновлений.

Например, вы можете сделать это на postgres (и я считаю, что SQL Server изменяет «columnX» на «colX»):

UPDATE foo 
JOIN (VALUES (1,2),(3,4),... long list) v ON (foo.id=v.column1)
SET foo.bar = v.column2

И вы можете обновить загрузку строк с помощью одного оператора, очень быстро.

Если вам не требуется, чтобы Ruby выполнял какое-то специфическое для Ruby волшебство с вашими данными, самый быстрый способ перенести данные из одной БД в другую - это экспортировать в виде текстового файла (CSV или табуляция), загрузите его в другой БД (LOAD DATA INFILE на MySQL), возможно, во временной таблице, и массовый процесс с использованием SQL.

РЕДАКТИРОВАТЬ: Вот как я делаю это в Python:

sql = [ "INSERT INTO foo (column list) VALUES " ]
values = []

for tuple in tuple_list:
     append "(?,?,?,?)" to sql
     extend values list with tuple

Затем соедините sql в строку, и вы получите «INSERT INTO foo (список столбцов) VALUES (?,?,?,?), (?,?,?,?), (?,?,?,?) "с" (?,?,?,?) "повторяется столько раз, сколько строк нужно вставить.

Тогда «values» содержит список (a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3) с a, bn, cn, dn, которые вы хотите вставить для строки n. Каждый соответствует заполнителю в строке sql.

Затем передайте это обычной функции «выполнить запрос с параметрами», которая будет обрабатывать кавычки и экранирование как обычно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...