Я привязываю для запуска запроса на обновление с подзапросом в базе данных MySQL с использованием ruby. Я использую ruby 1.9.3 и рельсы 4.1.Как написать запрос AREL UpdateManager для MySQL, который использует подзапрос
Запрос Я пытаюсь создать, как показано ниже:
UPDATE `items`
SET
`items`.`status_id` = 12
WHERE
`items`.`id` IN (SELECT DISTINCT
`items`.`id`
FROM
`items`
LEFT OUTER JOIN
`statuses` ON `items`.`status_id` = `statuses`.`id`
LEFT OUTER JOIN
`resources` ON `items`.`resource_id` = `resources`.`id`
WHERE
`statuses`.`title` LIKE 'On Loan'
AND `items`.`duedate` < '2015-04-24'
AND `items`.`return_date` IS NULL
ORDER BY `items`.`duedate`)
я могу производить этот запрос в рубин, используя AREL с кодом ниже:
# Declare Arel objects
i = Item.arel_table
s = Status.arel_table
r = Resource.arel_table
# This is the AREL query that returns the data
overdues = i.project(i[:id]).
join(s, Arel::Nodes::OuterJoin).on(i[:status_id].eq(s[:id])).
join(r, Arel::Nodes::OuterJoin).on(i[:resource_id].eq(r[:id])).
where(s[:title].matches("On Loan").
and(i[:duedate].lt(DateTime.now.to_date)).
and(i[:return_date].eq(nil))
).
order(i[:duedate])
# Note: You can't chain distinct, otherwise "overdues" becomes a string with the value "DISTINCT".
overdues.distinct
# This creates the update...
u = Arel::UpdateManager.new i.engine
u.table(i)
u.set([[i[:status_id], 10]]).where(i[:id].in(overdues))
Это не работает и возвращает сообщение об ошибке:
ActiveRecord :: StatementInvalid: Mysql2 :: Ошибка: вы не можете указать элементы таблицы назначения для обновления в предложении FROM:
Я попытался использовать AR "update_all", но он производит тот же SQL и, следовательно, ту же ошибку.
Item.where(i[:id].in(overdues)).update_all(:status_id => (Status.find_by(:title => "Overdue").id))
Сделав некоторые исследования, я обнаружил, что вы не можете запустить обновление с подзапроса, который ссылается на таблицу, которую необходимо обновить в MySQL. Я видел несколько сообщений на этом сайте и в более широком Интернете, где подробно разбираются детали.
В одном из утверждений говорится, что обновление должно использовать соединение вместо подзапроса. Посмотрев на код, стоящий за менеджером обновлений, он не имеет «соединения», поэтому я не могу этого сделать.
Другой говорит, что запускайте это в двух частях, но я не вижу, как это происходит, потому что AREL и AciveRecord выполняют целые действия.
Единственный способ, которым я могу это сделать, - наложение на таблицу и добавление дополнительного выбора (см. Ниже). Это не здорово, но было бы полезно посмотреть, можно ли это сделать.
UPDATE `items`
SET `status_id` = 10
WHERE `items`.`id` IN (
SELECT x.id
FROM
(SELECT DISTINCT `items`.`id`
FROM `items`
LEFT OUTER JOIN `statuses` ON `items`.`status_id` = `statuses`.`id`
LEFT OUTER JOIN `resources` ON `items`.`resource_id` = `resources`.`id`
WHERE `statuses`.`title` LIKE 'On Loan'
AND `items`.`duedate` < '2015-04-24'
AND `items`.`return_date` IS NULL
ORDER BY `items`.`duedate`) x
);
Если я не могу получить эту работу я мог бы принять два других подхода:
1) Я мог бы просто жестко закодировать SQL, но я хочу использовать ActiveRecord и ссылки на модели, чтобы сохранить его агностик базы данных.
2) Другой способ - вернуть экземпляр всех записей и пропустить их через отдельные обновления. Это будет проблемой производительности, но я могу принять это, потому что это фоновая работа, которая не будет обновлять больше, чем несколько записей в день.
Update
У меня есть запрос AREL ниже, производит подзапрос в формате мне нужно.
x = Arel::Table.new('x')
overdues = Item.select(x[:id]).from(
Item.select(Item.arel_table[:id]).where(
Status.arel_table[:title].matches("On Loan").and(
Item.arel_table[:duedate].lt(DateTime.now.to_date).and(
Item.arel_table[:return_date].eq(nil))
)
).joins(
Item.arel_table.join(Status.arel_table, Arel::Nodes::OuterJoin).on(
Item.arel_table[:status_id].eq(Status.arel_table[:id])
).join_sources
).joins(
Item.arel_table.join(Resource.arel_table, Arel::Nodes::OuterJoin).on(
Item.arel_table[:resource_id].eq(Resource.arel_table[:id])
).join_sources
).order(Item.arel_table[:duedate]).uniq.as('x')
)
К сожалению, он возвращает ошибку, когда я использую ее в своем заявлении о обновлении.
TypeError: Cannot visit Item::ActiveRecord_Relation