diff --git a/lib/active_record/connection_adapters/sqlserver/database_statements.rb b/lib/active_record/connection_adapters/sqlserver/database_statements.rb
index a3869cdc2..db70d5eff 100644
--- a/lib/active_record/connection_adapters/sqlserver/database_statements.rb
+++ b/lib/active_record/connection_adapters/sqlserver/database_statements.rb
@@ -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)
+              [unique_by.columns]
+            else
+              # Compare against every unique constraint (primary key included).
+              # Discard constraints that are not fully included on insert.keys. Prevents invalid queries.
+              # Example: ignore unique index for columns ["name"] if insert keys is ["description"]
+              insert_all = insert.send(:insert_all)
+
+              (insert_all.send(:unique_indexes).map(&:columns) + [insert_all.primary_keys]).select do |columns|
+                columns.to_set.subset?(insert.keys)
+              end
+            end
+            includes_primary_key = (insert.send(:insert_all).primary_keys.to_set & insert.keys).present?
+
+            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"
+            sql << " ON (#{conflict_columns.map { |columns| columns.map { |column| "target.#{quote_column_name(column)} = source.#{quote_column_name(column)}" }.join(" AND ") }.join(") OR (")})"
+            sql << " WHEN NOT MATCHED BY TARGET THEN"
+            sql << " INSERT (#{insert.send(:columns_list)}) VALUES (#{insert.keys.map { |column| "source.#{quote_column_name(column)}" }.join(", ")})"
+            if returning = insert.send(:insert_all).returning
+              sql << " OUTPUT " << returning.map { |column| "INSERTED.#{quote_column_name(column)}" }.join(", ")
+            end
+            sql << ";"
+            sql << "SET IDENTITY_INSERT #{insert.model.quoted_table_name} OFF;" if includes_primary_key
+            return sql
+          end
+
           sql = +"INSERT #{insert.into}"
 
           if returning = insert.send(:insert_all).returning
diff --git a/lib/active_record/connection_adapters/sqlserver_adapter.rb b/lib/active_record/connection_adapters/sqlserver_adapter.rb
index c30909aee..1f963f827 100644
--- a/lib/active_record/connection_adapters/sqlserver_adapter.rb
+++ b/lib/active_record/connection_adapters/sqlserver_adapter.rb
@@ -169,7 +169,7 @@ def supports_insert_returning?
       end
 
       def supports_insert_on_duplicate_skip?
-        false
+        true
       end
 
       def supports_insert_on_duplicate_update?
diff --git a/test/cases/coerced_tests.rb b/test/cases/coerced_tests.rb
index bc221e98d..2129507af 100644
--- a/test/cases/coerced_tests.rb
+++ b/test/cases/coerced_tests.rb
@@ -1555,3 +1555,8 @@ class ReloadModelsTest < ActiveRecord::TestCase
   # `activesupport/lib/active_support/testing/isolation.rb` exceeds what Windows can handle.
   coerce_tests! :test_has_one_with_reload if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
 end
+
+class InsertAllTest < ActiveRecord::TestCase
+  # Skip this until upsert is supported
+  coerce_tests! :test_insert
+end
diff --git a/test/cases/insert_all_test_sqlserver.rb b/test/cases/insert_all_test_sqlserver.rb
new file mode 100644
index 000000000..17d0949d0
--- /dev/null
+++ b/test/cases/insert_all_test_sqlserver.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "cases/helper_sqlserver"
+require "models/book"
+
+class InsertAllTestSQLServer < ActiveRecord::TestCase
+  fixtures :books
+
+  # Issue https://github.com/rails-sqlserver/activerecord-sqlserver-adapter/issues/847
+  it "execute insert_all with a single element" do
+    assert_difference "Book.count", +1 do
+      Book.insert_all [{ name: "Rework", author_id: 1 }]
+    end
+  end
+end