Last active
August 29, 2015 14:18
-
-
Save ippeiukai/23eee3d5ad8c0ab4515f to your computer and use it in GitHub Desktop.
bulk update of records in rails with UPDATE SET FROM (VALUES ...) AS WHERE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# NOTE assumes the relation is very very simple | |
def bulk_update(relation, values, where_columns, set_columns) | |
return if values.empty? | |
connection = relation.connection | |
base_class = relation.base_class | |
if relation.arel.where_sql.present? | |
relation_ids = relation.ids | |
end | |
quote_column_name_fn = connection.method(:quote_column_name) | |
quote_column_value_fn = ->(value, col_name) { base_class.quote_value(value, base_class.column_for_attribute(col_name)) } | |
destination_table_name_quoted = relation.quoted_table_name | |
temporary_table_name_quoted = quote_column_name_fn.('tmp_data') | |
temporary_table_columns = [*where_columns, *set_columns] | |
temporary_table_def = "#{temporary_table_name_quoted}(#{temporary_table_columns.map("e_column_name_fn).join(', ')})" | |
temporary_table_values_list = values.map do |record| | |
record.to_h.values_at(*temporary_table_columns).map.with_index do |val, i| | |
# cast is to make sure temporary table is correctly typed (e.g. timestamp without explicit type coercion is text) | |
"CAST(#{quote_column_value_fn.(val, temporary_table_columns[i])} AS #{base_class.column_for_attribute(temporary_table_columns[i]).sql_type})" | |
end | |
end | |
set_column_names_quoted = set_columns.map("e_column_name_fn) | |
set_fields = set_column_names_quoted.map { |col| "#{col} = #{temporary_table_name_quoted}.#{col}" }.join(', ') | |
where_column_names_quoted = where_columns.map("e_column_name_fn) | |
where_cond = where_column_names_quoted.map { |col| "#{destination_table_name_quoted}.#{col} = #{temporary_table_name_quoted}.#{col}" }.join(' AND ') | |
if relation_ids | |
relation_cond = "#{destination_table_name_quoted}.#{quote_column_name_fn.(relation.primary_key)} IN (#{relation_ids.map {|val| quote_column_value_fn.(val, relation.primary_key) }.join(', ')})" | |
end | |
connection.update(<<-SQL) | |
UPDATE #{destination_table_name_quoted} | |
SET #{set_fields} | |
FROM | |
(VALUES | |
#{temporary_table_values_list.map { |vals| "(#{vals.join(', ')})" }.join(', ')} | |
) AS #{temporary_table_def} | |
WHERE | |
#{[where_cond, relation_cond].compact.join(' AND ')} | |
SQL | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
maybe this http://stackoverflow.com/a/19033152/4212945