-
Notifications
You must be signed in to change notification settings - Fork 564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
insert_all/upsert_implementation using MERGE #869
base: main
Are you sure you want to change the base?
insert_all/upsert_implementation using MERGE #869
Conversation
|
||
sql = +"" | ||
sql << "SET IDENTITY_INSERT #{insert.model.quoted_table_name} ON;" if includes_primary_key | ||
sql << "MERGE INTO #{insert.model.quoted_table_name} WITH (UPDLOCK, HOLDLOCK) AS target" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe HOLDLOCK is equivalent to SERIALIZABLE. I'd prefer the SQL standard term. Unless HOLDLOCK is usual in the SQL Server community?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check that. Thanks!
@@ -140,6 +140,37 @@ def default_insert_value(column) | |||
private :default_insert_value | |||
|
|||
def build_insert_sql(insert) # :nodoc: | |||
if insert.skip_duplicates? | |||
# Do we have a unique_by index? Use index columns | |||
conflict_columns = if (unique_by = insert.send(:insert_all).unique_by) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we not do insert.insert_all
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest we cache insert_all in a local variable rather than have to look it up each time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
insert_all is private, it's not possible to do insert.insert_all
.
Thanks for the suggestion but I'm still not paying attention to things like this. As I said, the PR is in an early stage. I have plans to improve the code (refactor) but right now I'm focused on produce the SQL that cover all tests.
sql = +"" | ||
sql << "SET IDENTITY_INSERT #{insert.model.quoted_table_name} ON;" if includes_primary_key | ||
sql << "MERGE INTO #{insert.model.quoted_table_name} WITH (UPDLOCK, HOLDLOCK) AS target" | ||
sql << " USING (SELECT DISTINCT * FROM (#{insert.values_list}) AS t1 (#{insert.send(:columns_list)})) AS source" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of the standard adapters uniquify insert rows. I don't think we need to do it here either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uniquify was the solution to this test/scenario
Book.insert_all [
{ author_id: 8, name: "Refactoring" },
{ author_id: 8, name: "Refactoring" }
]
I don't like it but it works so I move on until all test pass.
Then I found the failing test (still stuck with it)
Book.insert_all [
{ id: 200, author_id: 8, name: "Refactoring" },
{ id: 201, author_id: 8, name: "Refactoring" }
]
My impression is that the solution to this will let me remove the uniquify.
end | ||
sql << ";" | ||
sql << "SET IDENTITY_INSERT #{insert.model.quoted_table_name} OFF;" if includes_primary_key | ||
return sql |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repeatedly appending to a string is somewhat slow. Faster in that case is to append strings to an array and then join the array at the end. Not sure how much difference that would make, but it would be just as easy to read, I think, and would probably be noticeable if we are dealing with many rows.
I think Would a gem (e.g. |
This PR resolves the issue #859. Since it adds support to
insert_on_duplicate_skip
it also resolves #847.The PR is in early stages. I'm exploring adding
supports_insert_on_duplicate_skip
first (insert_all). Upsert_all is still pending.At this stage this test is falling with
ActiveRecord::RecordNotUnique: TinyTds::Error: Cannot insert duplicate key row in object 'dbo.books' with unique index 'index_books_on_author_id_and_name'. The duplicate key value is (8, Refactoring).
The test produces the following query
SQL Server computes the source and target join and then applies the conditions to decide if a record from the joined table matches or not. In this case, both records are inserted.
I'm having doubts if implementing insert_all using merge is possible.
upsert_all seems more challenging. Besides this problem,
WHEN MATCHED
can only update a row once.For reference, table schema is