Updating Multiple Rows in DB in Rails, each with Unique values











up vote
1
down vote

favorite












I have extensively researched this, and I can't seem to find the answer I need.



I am familiar with Rails transactions, but a transaction in this case would execute several queries and I would rather not do that.



In a single query, how can I update the same column on multiple rows with unique values?



Ex:



update_hash =  {1: 'Bandits on the High Road', 2: 'Broccoli: The Menace'}
Books.where(<id_is_in_update_hash_keys>).each do |b|
matching_hash_key = b.id
new_title = update_hash[:matching_hash_key].value
# problem here because each update is a query
b.update(title: new_title)
end


Of course, I could wrap it in a transaction, but 10k books still call 10k queries. I use Postgresql, but I don't know the correct, idiomatic way to update that field for multiple objects in a single query. The data has been pre-vetted so there will never be a need to run validations.



If anyone knows either the Rails code to execute, or more likely the Postgresql query that I need to generate, I would be very grateful.










share|improve this question






















  • You may look for some gems that will wrap what's been said in this question
    – Marcin Kołodziej
    Nov 5 at 2:06















up vote
1
down vote

favorite












I have extensively researched this, and I can't seem to find the answer I need.



I am familiar with Rails transactions, but a transaction in this case would execute several queries and I would rather not do that.



In a single query, how can I update the same column on multiple rows with unique values?



Ex:



update_hash =  {1: 'Bandits on the High Road', 2: 'Broccoli: The Menace'}
Books.where(<id_is_in_update_hash_keys>).each do |b|
matching_hash_key = b.id
new_title = update_hash[:matching_hash_key].value
# problem here because each update is a query
b.update(title: new_title)
end


Of course, I could wrap it in a transaction, but 10k books still call 10k queries. I use Postgresql, but I don't know the correct, idiomatic way to update that field for multiple objects in a single query. The data has been pre-vetted so there will never be a need to run validations.



If anyone knows either the Rails code to execute, or more likely the Postgresql query that I need to generate, I would be very grateful.










share|improve this question






















  • You may look for some gems that will wrap what's been said in this question
    – Marcin Kołodziej
    Nov 5 at 2:06













up vote
1
down vote

favorite









up vote
1
down vote

favorite











I have extensively researched this, and I can't seem to find the answer I need.



I am familiar with Rails transactions, but a transaction in this case would execute several queries and I would rather not do that.



In a single query, how can I update the same column on multiple rows with unique values?



Ex:



update_hash =  {1: 'Bandits on the High Road', 2: 'Broccoli: The Menace'}
Books.where(<id_is_in_update_hash_keys>).each do |b|
matching_hash_key = b.id
new_title = update_hash[:matching_hash_key].value
# problem here because each update is a query
b.update(title: new_title)
end


Of course, I could wrap it in a transaction, but 10k books still call 10k queries. I use Postgresql, but I don't know the correct, idiomatic way to update that field for multiple objects in a single query. The data has been pre-vetted so there will never be a need to run validations.



If anyone knows either the Rails code to execute, or more likely the Postgresql query that I need to generate, I would be very grateful.










share|improve this question













I have extensively researched this, and I can't seem to find the answer I need.



I am familiar with Rails transactions, but a transaction in this case would execute several queries and I would rather not do that.



In a single query, how can I update the same column on multiple rows with unique values?



Ex:



update_hash =  {1: 'Bandits on the High Road', 2: 'Broccoli: The Menace'}
Books.where(<id_is_in_update_hash_keys>).each do |b|
matching_hash_key = b.id
new_title = update_hash[:matching_hash_key].value
# problem here because each update is a query
b.update(title: new_title)
end


Of course, I could wrap it in a transaction, but 10k books still call 10k queries. I use Postgresql, but I don't know the correct, idiomatic way to update that field for multiple objects in a single query. The data has been pre-vetted so there will never be a need to run validations.



If anyone knows either the Rails code to execute, or more likely the Postgresql query that I need to generate, I would be very grateful.







ruby-on-rails postgresql activerecord






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 5 at 1:10









JapanRob

7529




7529












  • You may look for some gems that will wrap what's been said in this question
    – Marcin Kołodziej
    Nov 5 at 2:06


















  • You may look for some gems that will wrap what's been said in this question
    – Marcin Kołodziej
    Nov 5 at 2:06
















You may look for some gems that will wrap what's been said in this question
– Marcin Kołodziej
Nov 5 at 2:06




You may look for some gems that will wrap what's been said in this question
– Marcin Kołodziej
Nov 5 at 2:06












1 Answer
1






active

oldest

votes

















up vote
1
down vote



accepted










With PostgreSQL it's possible with a query like this one:



update_hash = { 1: 'Bandits on the High Road', 2: 'Broccoli: The Menace' }
values = update_hash.map { |k, v| "(#{k}, #{ActiveRecord::Base.connection.quote(v)})" }.join(', ')

query = "
UPDATE books T
SET title = uv.new_title
FROM (VALUES #{values}) AS uv (id, new_title)
WHERE T.id = uv.id::int"

ActiveRecord::Base.connection.execute(query)





share|improve this answer





















  • Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
    – JapanRob
    Nov 6 at 11:19












  • @JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
    – Ilya Konyukhov
    Nov 6 at 15:18













Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














 

draft saved


draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53147082%2fupdating-multiple-rows-in-db-in-rails-each-with-unique-values%23new-answer', 'question_page');
}
);

Post as a guest
































1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
1
down vote



accepted










With PostgreSQL it's possible with a query like this one:



update_hash = { 1: 'Bandits on the High Road', 2: 'Broccoli: The Menace' }
values = update_hash.map { |k, v| "(#{k}, #{ActiveRecord::Base.connection.quote(v)})" }.join(', ')

query = "
UPDATE books T
SET title = uv.new_title
FROM (VALUES #{values}) AS uv (id, new_title)
WHERE T.id = uv.id::int"

ActiveRecord::Base.connection.execute(query)





share|improve this answer





















  • Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
    – JapanRob
    Nov 6 at 11:19












  • @JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
    – Ilya Konyukhov
    Nov 6 at 15:18

















up vote
1
down vote



accepted










With PostgreSQL it's possible with a query like this one:



update_hash = { 1: 'Bandits on the High Road', 2: 'Broccoli: The Menace' }
values = update_hash.map { |k, v| "(#{k}, #{ActiveRecord::Base.connection.quote(v)})" }.join(', ')

query = "
UPDATE books T
SET title = uv.new_title
FROM (VALUES #{values}) AS uv (id, new_title)
WHERE T.id = uv.id::int"

ActiveRecord::Base.connection.execute(query)





share|improve this answer





















  • Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
    – JapanRob
    Nov 6 at 11:19












  • @JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
    – Ilya Konyukhov
    Nov 6 at 15:18















up vote
1
down vote



accepted







up vote
1
down vote



accepted






With PostgreSQL it's possible with a query like this one:



update_hash = { 1: 'Bandits on the High Road', 2: 'Broccoli: The Menace' }
values = update_hash.map { |k, v| "(#{k}, #{ActiveRecord::Base.connection.quote(v)})" }.join(', ')

query = "
UPDATE books T
SET title = uv.new_title
FROM (VALUES #{values}) AS uv (id, new_title)
WHERE T.id = uv.id::int"

ActiveRecord::Base.connection.execute(query)





share|improve this answer












With PostgreSQL it's possible with a query like this one:



update_hash = { 1: 'Bandits on the High Road', 2: 'Broccoli: The Menace' }
values = update_hash.map { |k, v| "(#{k}, #{ActiveRecord::Base.connection.quote(v)})" }.join(', ')

query = "
UPDATE books T
SET title = uv.new_title
FROM (VALUES #{values}) AS uv (id, new_title)
WHERE T.id = uv.id::int"

ActiveRecord::Base.connection.execute(query)






share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 5 at 3:47









Ilya Konyukhov

2,128618




2,128618












  • Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
    – JapanRob
    Nov 6 at 11:19












  • @JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
    – Ilya Konyukhov
    Nov 6 at 15:18




















  • Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
    – JapanRob
    Nov 6 at 11:19












  • @JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
    – Ilya Konyukhov
    Nov 6 at 15:18


















Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
– JapanRob
Nov 6 at 11:19






Suuuuper quick addendum before I accept -> If I try to do this with a timestamp in the value (for example, deleted_at manual setting) it says that ActiveRecord::StatementInvalid: PG::DatatypeMismatch: ERROR: column "deleted_at" is of type timestamp without time zone but expression is of type text. Is that something I can fix?
– JapanRob
Nov 6 at 11:19














@JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
– Ilya Konyukhov
Nov 6 at 15:18






@JapanRob Right, every value from VALUES table in the query like this is considered as of text type, that's why it may need to be converted to be inserted into the table. For timestamps you could do this conversion: SET ..., deleted_at = to_timestamp(uv.deleted_at, 'YYYY-MM-DD HH24:MI:SS OF').
– Ilya Konyukhov
Nov 6 at 15:18




















 

draft saved


draft discarded



















































 


draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53147082%2fupdating-multiple-rows-in-db-in-rails-each-with-unique-values%23new-answer', 'question_page');
}
);

Post as a guest




















































































這個網誌中的熱門文章

Hercules Kyvelos

Tangent Lines Diagram Along Smooth Curve

Yusuf al-Mu'taman ibn Hud