Insert, Upsert nhiều bản ghi cùng lúc trong Rails 6
Bài đăng này đã không được cập nhật trong 5 năm
Chúng ta tiếp tục tìm hiểu về những thay đổi có được ở trong phiên bản Rail 6 này.
1. Hỗ trợ chèn số lượng lớn các bản ghi trong Rails 6
- Rails 6 đã thêm hỗ trợ cho việc insert nhiều bản ghi cùng lúc tương tự như việc update nhiều bản ghi cùng lúc( sử dụng
update_all
) hay xóa cùng lúc nhiều bản ghi được hỗ trợ bằng methoddelete_all
. - Có thể thực hiện insert hàng loạt các bản ghi bằng các method mới được thêm vào đó là:
insert_all
,insert_all!
vàupsert_all
. - Tất cả các phương thức mới này cho phép chèn nhiều bản ghi của model vào cơ sở dữ liệu. Sử dụng một câu lệnh truy vấn SQL INSERT duy nhất và được gửi đến cơ sở dữ liệu mà không khởi tạo Model hay chạy qua các callback hoặc validations (các bạn có thể tìm hiểu chi tiết hơn về luồng hoạt động của nó tại đây: Flow Insert_all)
- Trong quá trìnhinsert số lượng lớn các bản ghi, có thể phát sinh nhiều vấn đề như vi phạm primary key, vi phạm các unique index,... Vì thế, Rails đã tận dụng các tính năng dành riêng cho CSDL để bỏ qua hoặc upsert các bản ghi duplicate, tùy theo trường hợp.
- Bây giờ, ta hãy tìm hiểu chi tiết hơn về các phương thức
insert_all
,insert_all!
vàupsert_all
(tất cả đều được sử dụng để thực hiện insert số lượng lớn các bản ghi): - Ví dụ: Ta sẽ tạo một bảng Post với 2 unique index.
create_table :posts do |t|
t.string :title, null: false
t.string :author, null: false,
t.string :category, null: false
t.text :description
t.index :category, unique: true
t.index [:title, :author], unique: true
end
(chúng ta sẽ ngăn các bản ghi có các tiêu đề và cột tác giả trùng lặp với nhau.)
Thực hiện chèn số lượng lớn bằng cách bỏ qua các bản sao
- Giả sử chúng ta muốn chèn nhiều bài viết cùng một lúc vào CSDL. Có thể sẽ xuất hiện các bản ghi vi phạm các ràng buộc duy nhất của bảng, các bản ghi đó được coi là trùng lặp. Nói cách khác, các row hoặc bản ghi được xác định là duy nhất bởi mỗi unique index trên bảng.
- Để bỏ qua các row hoặc bản ghi trùng lặp và chèn phần còn lại vào các bản ghi cùng một lúc, chúng ta có thể sử dụng phương thức
ActiveRecord :: Persistence # insert_all.
- Đối với PostgreSQL
result = Post.insert_all(
[
{ id: 1,
title: 'Lost stars',
author: 'Adam Levine',
category: 'Ballad' },
{ id: 1, # duplicate 'id' here
title: 'Litte Flower',
author: 'Jonny',
category: 'R&B' },
{ id: 2,
title: 'Hello world',
author: 'Smith',
category: 'rails' },
{ id: 3,
title: 'Hello world',
author: 'Smith', # duplicate 'title' & 'author' here
category: 'ruby' },
{ id: 4,
title: 'Docter Strand',
author: 'Paul',
category: 'marvel' },
{ id: 5,
title: 'Fly high',
author: 'Amanda',
category: 'Ballad' }, # duplicate 'category' here
{ id: 6,
title: 'Freedome',
author: 'Lodder',
category: 'sky' }
]
)
# Bulk Insert (2.3ms) INSERT INTO "posts"("id","title","author","category") VALUES (1, 'Lost stars [...still...] 'sky') ON CONFLICT DO NOTHING RETURNING "id"
puts result.inspect
#<ActiveRecord::Result:0x00007fb6612a1ad8 @columns=["id"], @rows=[[1], [2], [4], [6]], @hash_rows=nil, @column_types={"id"=>#<ActiveModel::Type::Integer:0x00007fb65f420078 @precision=nil, @scale=nil, @limit=8, @range=-9223372036854775808...9223372036854775808>}>
puts Post.count
# 4
- Phương thức insert_all chấp nhận một đối số bắt buộc phải là một mảng chứa các hash với các thuộc tính của cùng một model.
- Lưu ý mệnh đề
ON CONFLICT DOHING
trong truy vấn INSERT(được hỗ trợ cho CDSL PostgreSQL và SQLite). Nó sẽ báo cho CSDL biết rằng khi nào xảy ra xung đột hoặc vi phạm ràng buộc khóa duy nhất trong việc chèn số lượng lớn các bản ghi, để nó có thể bỏ qua bản ghi xung đột và tiến hành chèn bản ghi tiếp theo.
-
Trong ví dụ trên, ta có 3 bản ghi vi phạm các ràng buộc duy nhất khác nhau được xác định trên bảng Post.
-
Một trong các bản ghi được chèn có thuộc tính
id: 1
bị trùng lặp vì vi phạm ràng buộc khóa chính duy nhất -
Một bản ghi khác có tiêu đề trùng lặp:
title: 'Hello world', author: 'Smith'
, nó vi phạm unique index được xác định trên các column title và author. -
Và một bản ghi có category trùng lặp:
category: 'Ballad'
vi phạm unique index được xác định trên colum category.
-
- Tất cả các bản ghi này vi phạm bất kỳ ràng buộc hoặc unique index nào đều bị bỏ qua và không được chèn vào cơ sở dữ liệu.
- Nếu thành công,
ActiveRecord::Persistence#insert_all
sẽ trả về một instance củaActiveRecord::Result
. Nội dung của kết quả khác nhau trên mỗi CSDL. Trong trường hợp CSDL PostgreSQL, kết quả này chứa thông tin về các bản ghi được chèn thành công như tên cột được chọn, giá trị của các cột,... - Đối với PostgreSQL, theo mặc định, phương thức
insert_all
sẽ nối thêm mệnh đềRETURNING "id"
vào truy vấn SQL trong đó id là khóa chính. Mệnh đề này yêu cầu CSDL trả vềid
của mọi bản ghi được chèn thành công. Bằng cách inspect kết quả, đặc biệt là các thuộc tính@columns=["id"], @rows=[[1], [2], [4], [6]]
, chúng ta có thể thấy rằng các bản ghi có id thuộc tính có giá trị1, 2, 4 và 6
đã được chèn thành công. - Vậy nếu chúng ta muốn xem nhiều thuộc tính hơn và không chỉ thuộc tính id của các bản ghi được chèn thành công trong kết quả thì sẽ làm thế nào?
- Chúng ta sẽ sử dụng tùy chọn
returning option
, nó chấp nhận một mảng các tên thuộc tính
result = Post.insert_all(
[
{ id: 1,
title: 'Lost stars',
author: 'Adam Levine',
category: 'Ballad' },
#...still...
],
returning: %w[ id title ] #option
)
# Bulk Insert (2.3ms) INSERT INTO "posts"("id","title","author","category") VALUES (1, 'Lost stars [...still...] 'sky') ON CONFLICT DO NOTHING RETURNING "id","title"
puts result.inspect
#<ActiveRecord::Result:0x00007f902a1196f0 @columns=["id", "title"], @rows=[[1, "Lost stars"], [2, "Hello world"], [4, "Docter Strand"], [6, "Freedome"]], @hash_rows=nil, @column_types={"id"=>#<ActiveModel::Type::Integer:0x00007f90290ca8d0 @precision=nil, @scale=nil, @limit=8, @range=-9223372036854775808...9223372036854775808>, "title"=>#<ActiveModel::Type::String:0x00007f9029978298 @precision=nil, @scale=nil, @limit=nil>}>
puts result.pluck("id", "title").inspect
#[[1, "Lost stars"], [2, "Hello world"], [4, "Docter Strand"], [6, "Freedome"]]
- Lưu ý cách truy vấn INSERT nối thêm mệnh đề
RETURNING "id","title"
và kết quả bây giờ sẽ chứa các thuộc tính id và title của các bản ghi được chèn thành công.
- Đối với SQLite
- Tương tự như PostgreSQL, các bản ghi vi phạm được bỏ qua trong việc chèn hàng loạt được thực hiện bằng
insert_all
. - Ví dụ:
result = Post.insert_all(
[
{ id: 1,
title: 'Lost stars',
author: 'Adam Levine',
category: 'Ballad' },
{ id: 1, # duplicate 'id' here
title: 'Litte Flower',
author: 'Jonny',
category: 'R&B' },
#..still..
]
)
# Bulk Insert (1.6ms) INSERT INTO "posts"("id","title","author","category") VALUES (1, 'Lost stars [...still...] 'sky') ON CONFLICT DO NOTHING
puts result.inspect
#<ActiveRecord::Result:0x00007fa9df448ff0 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
puts Post.pluck(:id, :title)
#[[1, "Lost stars"], [2, "Hello world"], [4, "Docter Strand"], [6, "Freedome"]]
puts Post.count
# 4
- Lưu ý rằng vì SQLite không hỗ trợ mệnh đề
RETURING
, nên nó không được thêm vào truy vấn SQL. Do đó,ActiveRecord::Result
trả về không chứa thêm được bất kỳ thông tin nào.
Post.insert_all(
[
{ id: 1,
title: 'Lost stars',
author: 'Adam Levine',
category: 'Ballad' },
#...still...
],
returning: %w[ id title ]
)
# ActiveRecord::ConnectionAdapters::SQLite3Adapter does not support :returning (ArgumentError)
- Đối với MySQL
- Các bản ghi vi phạm khóa chính, ràng buộc khóa duy nhất hoặc unique index được bỏ qua trong quá trình thao tác chèn hàng loạt được thực hiện bằng cách sử dụng
insert_all
trên CSDL MySQL.
result = Post.insert_all(
[
{ id: 1,
title: 'Lost stars',
author: 'Adam Levine',
category: 'Ballad' },
{ id: 1, # duplicate 'id' here
title: 'Litte Flower',
author: 'Jonny',
category: 'R&B' },
#..still..
]
)
# Bulk Insert (20.3ms) INSERT INTO `posts`(`id`,`title`,`author`,`category`) VALUES (1, 'Lost stars [...still...] 'sky') ON DUPLICATE KEY UPDATE `id`=`id`
puts result.inspect
#<ActiveRecord::Result:0x000055d6cfea7580 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
puts Post.pluck(:id, :title)
#[[1, "Lost stars"], [2, "Hello world"], [4, "Docter Strand"], [6, "Freedome"]]
puts Post.count
# 4
- Ở đây, mệnh đề
ON DUPLICATE KEY UPDATE 'id'='id'
trong truy vấn INSERT về cơ bản thực hiện tương tự như mệnh đềON CONFLICT DO NOTHING
được hỗ trợ bởi PostgreSQL và SQLite. - Tương tự như SQLite, MySQL không hỗ trợ mệnh đề
RETURING
, nên nó không được đưa vào truy vấn SQL và do đó, kết quả không có chứa thêm bất kỳ thông tin nào.
2. Thực hiện chèn hàng loạt bằng cách bỏ qua các bản sao trên một ràng buộc duy nhất được chỉ định nhưng sẽ raise exception nếu các bản ghi vi phạm các ràng buộc duy nhất khác
- Trong trường hợp đầu tiên, chúng ta đã skip các bản ghi vi phạm bất kỳ ràng buộc duy nhất nào. Trong một số trường hợp, chúng ta có thể muốn bỏ qua các bản sao gây ra chỉ bởi một unique index cụ thể nhưng sẽ hủy bỏ transaction nếu các bản ghi khác vi phạm bất kỳ ràng buộc duy nhất nào khác.
- Tùy chọn
unique_by option
của phương thứcinsert_all
cho phép xác định một ràng buộc duy nhất như vậy.
- Đối với PostgreSQL and SQLite
- Hãy cùng xét một ví dụ skip các bản ghi trùng lặp chỉ vi phạm chỉ mục duy nhất được chỉ định
:index_posts_on_title_and_master
bằng tùy chọnunique_by
. Các bản ghi trùng lặp không vi phạmindex_posts_on_title_and_master
index không được bỏ qua, và do đó sẽ raise lỗi.
-> PostgreSQL
result = Post.insert_all(
[
{ .... },
{ .... }, # duplicate 'id' here
{ .... },
{ .... }, # duplicate 'title' and 'author' here
{ .... },
{ .... }, # duplicate 'category' here
{ .... }
],
unique_by: :index_posts_on_title_and_author
)
# PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "posts_pkey" (ActiveRecord::RecordNotUnique)
# DETAIL: Key (id)=(1) already exists.
-> SQLite
# SQLite3::ConstraintException: UNIQUE constraint failed: articles.id (ActiveRecord::RecordNotUnique)
- Trong trường hợp này, chúng ta gặp lỗi
ActiveRecord::RecordNotUnique
do vi phạm ràng buộc khóa chính trên cột id. Nó đã không bỏ qua bản ghi thứ hai trong ví dụ ở trên đã vi phạm unique index trên id khóa chính vì tùy chọnunique_by
được chỉ định với một unique index khác. Khi xảy ra ngoại lệ, không có bản ghi nào tồn tại trong CSDL vìinsert_all
chỉ thực hiện một truy vấn SQL duy nhất. - Tùy chọn
unique_by
có thể được xác định bởi các cột hoặc tên unique index.
unique_by: :index_posts_on_title_and_author
# tương tự
unique_by: %i[ title author ]
# Vì thế,
unique_by: :category
# tương tự
unique_by: %i[ :category ]
# và nó cũng giống
unique_by: :index_posts_on_category
- Bây giờ, ta sẽ xóa (sửa) bản ghi có khóa chính trùng lặp và rồi chạy lại ví dụ trên.
-> PostgreSQL
result = Post.insert_all(
[
{ .... },
{ .... },
{ .... },
{ .... }, # duplicate 'title' and 'author' here
{ .... },
{ .... }, # duplicate 'category' here
{ .... }
],
unique_by: :index_posts_on_title_and_author
)
# PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_posts_on_slug" (ActiveRecord::RecordNotUnique)
# DETAIL: Key (category)=(Ballad) already exists.
-> SQLite
# SQLite3::ConstraintException: UNIQUE constraint failed: articles.slug (ActiveRecord::RecordNotUnique)
- Lỗi
ActiveRecord::RecordNotUnique
trong ví dụ trên cho biết ràng buộc duy nhất index_posts_on_category bị vi phạm. Bây giờ chúng tôi sẽ xóa (sửa) bản ghi có cùng category.
result = Post.insert_all(
[
{ .... },
{ .... },
{ .... },
{ .... }, # duplicate 'title' and 'author' here
{ .... },
{ .... },
{ .... }
],
unique_by: :index_posts_on_title_and_author
)
# Bulk Insert (2.5ms) INSERT INTO "posts"("id","title","author","category") VALUES (1, 'Lost stars [...still...] 'sky') ON CONFLICT ("title","author") DO NOTHING RETURNING "id"
puts result.inspect
#<ActiveRecord::Result:0x00007fada2069828 @columns=["id"], @rows=[[1], [7], [2], [4], [5], [6]], @hash_rows=nil, @column_types={"id"=>#<ActiveModel::Type::Integer:0x00007fad9fdb9df0 @precision=nil, @scale=nil, @limit=8, @range=-9223372036854775808...9223372036854775808>}>
- Ở đây, bản ghi thứ tư đã bị bỏ qua vì bản ghi đó vi phạm unique index:
index_posts_on_title_and_author
được chỉ định bởi tùy chọnunique_by
. - Tương tự, chúng ta có thể chỉ định một unique index khác bằng cách sử dụng tùy chọn
unique_by
. Ví dụ: nếu chúng ta chỉ địnhunique_by: :category
option, sau đó các bản ghi chứa các cột category trùng lặp sẽ bị bỏ qua nhưng sẽ raise exceptionActiveRecord::RecordNotUnique
nếu bất kỳ bản ghi nào vi phạm các ràng buộc duy nhất khác.
- Đối với MySQL
- Tùy chọn
unique_by
không được hỗ trợ khi CSDL là MySQL.
3.Raise exception nếu bất kỳ bản ghi nào khi insert vào trong DB vi phạm bất kỳ ràng buộc duy nhất nào
- Phương thức
insert_all!
không bao giờ bỏ qua bản ghi trùng lặp. Nếu một bản ghi vi phạm bất kỳ ràng buộc duy nhất nào, thì insert_all! Phương thức sẽ đơn giản đưa ra lỗiActiveRecord::RecordNotUnique.
- Khi cơ sở dữ liệu là PostgreSQL,
insert_all!
method có thể chấp nhận tùy chọnreturning option
, mà chúng ta đã nói tới ở phần trên. - Tùy chọn
unique_by
không được hỗ trợ bởi phương thứcinsert_all!
4. Thực hiện upserts số lượng lớn
- Trong các phần 1, 2 và 3 ở trên, chúng ta đã đề cập đến việc bỏ qua các bản sao hoặc đưa ra ngoại lệ nếu gặp phải một bản sao trong khi chèn hàng loạt. Đôi khi, chúng ta muốn cập nhật bản ghi hiện có khi xảy ra sự trùng lặp nếu không thì chèn bản ghi mới. Thao tác này được gọi là upsert vì nó sẽ cập nhật bản ghi hoặc nếu không có bản ghi để cập nhật, thì nó sẽ insert.
- upsert_all trong MySQL
result = Post.upsert_all(
[
{ id: 1, title: 'Lost stars', author: 'Adam Levine', category: 'Ballad' },
{ id: 1, .... }, # duplicate 'id' here
{ id: 2, .... },
{ id: 3, .... }, # duplicate 'title' and 'author' here
{ id: 4, .... },
{ id: 5, .... }, # duplicate 'category' here
{ id: 6, .... }
]
)
# Bulk Insert (26.3ms) INSERT INTO `posts`(`id`,`title`,`author`,`category`) VALUES (1, 'Lost stars [...still...] 'sky') ON DUPLICATE KEY UPDATE `title`=VALUES(`title`),`author`=VALUES(`author`),`category`=VALUES(`category`)
puts result.inspect
#<ActiveRecord::Result:0x000055a43c1fae10 @columns=[], @rows=[], @hash_rows=nil, @column_types={}>
puts Post.count
# 5
puts Post.all
#<ActiveRecord::Relation [#<Article id: 1, title: "Litte Flower", category: "R&B", author: "Jonny", description: nil>, #<Article id: 2, title: "Hello world", category: "rails", author: "Smith", description: nil>, #<Article id: 4, title: "Docter Strand", category: "marvel", author: "Paul", description: nil>, #<Article id: 5, title: "Fly high", category: "Fly high", author: "Amanda", description: nil>, #<Article id: 6, title: "Freedome", category: "sky", author: "Lodder", description: nil>]>
- Trong ví dụ bên trên:
- Hàng thứ hai trong mảng đầu vào có thuộc tính
id: 1
thay thế hàng đầu tiên(cũng có thuộc tínhid: 1
trùng lặp). - Hàng thứ tư có
id: 3
thay thế các thuộc tính của hàng thứ ba vì cả hai đều có các thuộc tính title vàauthor
của Post. - Các hàng còn lại không trùng lặp và đã được chèn mà không có bất kỳ vấn đề nào xảy ra.
- Lưu ý rằng các tùy chọn
return
vàunique_by
không được hỗ trợ trong phương thức upsert_all khi cơ sở dữ liệu là MySQL.
- upsert_all trong SQLite
result = Post.upsert_all(
[
{ id: 1, title: 'Lost stars', author: 'Adam Levine', category: 'Ballad' },
{ id: 1, title: 'Litte Flower', author: 'Jonny', category: 'R&B' }, # duplicate 'id' here
{ id: 2, title: 'Hello world', author: 'Smith', category: 'rails' },
{ id: 3, title: 'Hello world', author: 'Smith', category: 'ruby' }, # duplicate 'title' and 'author' here
{ id: 4, title: 'Docter Strand', author: 'Paul', category: 'marvel' },
{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, # duplicate 'category' here
{ id: 6, title: 'Freedome', author: 'Lodder', category: 'sky' }
]
)
# Bulk Insert (4.0ms) INSERT INTO "posts"("id","title","author","category") VALUES (1, 'Lost stars [...still...] 'sky') ON CONFLICT ("id") DO UPDATE SET "title"=excluded."title","author"=excluded."author","category"=excluded."category"
# SQLite3::ConstraintException: UNIQUE constraint failed: posts.title, posts.author (ActiveRecord::RecordNotUnique)
- Ở ví dụ trên, trong trường hợp SQLite, theo mặc định, bản ghi mới sẽ thay thế bản ghi hiện tại và bản ghi mới có cùng khóa chính. Nếu một bản ghi vi phạm bất kỳ ràng buộc duy nhất nào khác ngoài khóa chính, nó sẽ raise
ActiveRecord::RecordNotUnique.
- Mệnh đề
ON CONFLICT ("id") DO UPDATE
trong truy vấn SQL ở trên truyền đạt cùng một mục đích. Ở đó,upsert_all
trong SQLite không có hành vi giống như trong MySQL. - Vì vậy, như một giải pháp thay thế, chúng ta sẽ cần upsert các bản ghi với sự trợ giúp khi dùng
multiple upsert_all
với việc sử dụng tùy chọnunique_by
.
Post.upsert_all(
[
{ id: 1, title: 'Lost stars', author: 'Adam Levine', category: 'Ballad' },
{ id: 1, title: 'Litte Flower', author: 'Jonny', category: 'R&B' }, # duplicate 'id' here
],
unique_by: :id
)
Post.upsert_all(
[
{ id: 2, title: 'Hello world', author: 'Smith', category: 'rails' },
{ id: 3, title: 'Hello world', author: 'Smith', category: 'ruby' }, # duplicate 'title' and 'author' here
{ id: 4, title: 'Docter Strand', author: 'Paul', category: 'marvel' },
{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, # duplicate 'category' here
{ id: 6, title: 'Freedome', author: 'Lodder', category: 'sky' }
],
unique_by: %i[ title author ]
)
puts Post.count
# 5
puts Post.all
#<ActiveRecord::Relation [#<Article id: 1, title: "Litte Flower", category: "R&B", author: "Jonny", description: nil>, #<Article id: 2, title: "Hello world", category: "rails", author: "Smith", description: nil>, #<Article id: 4, title: "Docter Strand", category: "marvel", author: "Paul", description: nil>, #<Article id: 5, title: "Fly high", category: "Fly high", author: "Amanda", description: nil>, #<Article id: 6, title: "Freedome", category: "sky", author: "Lodder", description: nil>]>
- Ở đây, trước tiên chúng ta đã upsert all các bản ghi đã bị vi phạm chỉ mục khóa chính duy nhất trên cột id. Sau đó, chúng ta upsert thành công tất cả các bản ghi còn lại, vi phạm unique index trên cột title và author.
- upsert_all trong PostgreSQL
result = Post.upsert_all(
[
{ id: 1, title: 'Lost stars', author: 'Adam Levine', category: 'Ballad' },
{ id: 1, title: 'Litte Flower', author: 'Jonny', category: 'R&B' }, # duplicate 'id' here
{ id: 2, title: 'Hello world', author: 'Smith', category: 'rails' },
{ id: 3, title: 'Hello world', author: 'Smith', category: 'ruby' }, # duplicate 'title' and 'author' here
{ id: 4, title: 'Docter Strand', author: 'Paul', category: 'marvel' },
{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, # duplicate 'category' here
{ id: 6, title: 'Freedome', author: 'Lodder', category: 'sky' }
]
)
# PG::CardinalityViolation: ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time (ActiveRecord::StatementInvalid)
# HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
- Việc upsert_all hàng loạt không thành công trong ví dụ trên do ngoại lệ
ActiveRecord::StatementInvalid
không hợp lệ được gây ra bởi exception PG::CardinalityViolation. - Như một giải pháp thay thế, chúng ta có thể chia truy vấn
upsert_all
thành nhiều truy vấnupsert_all
với việc sử dụng tùy chọnunique_by
tương tự như cách chúng ta đã làm trong trường hợp SQLite trong phần 4.2 ở trên.
Post.insert_all(
[
{ id: 1, title: 'Lost stars', author: 'Adam Levine', category: 'Ballad' },
{ id: 2, title: 'Hello world', author: 'Smith', category: 'rails' },
{ id: 4, title: 'Docter Strand', author: 'Paul', category: 'marvel' },
{ id: 6, title: 'Freedome', author: 'Lodder', category: 'sky' }
]
)
Post.upsert_all(
[
{ id: 1, title: 'Litte Flower', author: 'Jonny', category: 'R&B' }, # duplicate 'id' here
]
)
Post.upsert_all(
[
{ id: 3, title: 'Hello world', author: 'Smith', category: 'ruby' }, # duplicate 'title' and 'author' here
],
unique_by: :index_posts_on_title_and_author
)
Post.upsert_all(
[
{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, # duplicate 'category' here
]
)
puts Post.count
# 5
puts Post.all
#<ActiveRecord::Relation [#<Article id: 1, title: "Litte Flower", category: "R&B", author: "Jonny", description: nil>, #<Article id: 2, title: "Hello world", category: "rails", author: "Smith", description: nil>, #<Article id: 4, title: "Docter Strand", category: "marvel", author: "Paul", description: nil>, #<Article id: 5, title: "Fly high", category: "Fly high", author: "Amanda", description: nil>, #<Article id: 6, title: "Freedome", category: "sky", author: "Lodder", description: nil>]>
5. insert, insert! and upsert
- Để thuận tiện hơn, Rails 6 cũng đã được giới thiệu thêm 3 method nữa đó là insert, insert! và upsert.
- Phương thức
insert
sẽ thêm một bản ghi vào cơ sở dữ liệu. Nếu bản ghi đó vi phạm một ràng buộc duy nhất, thì phương thứcinsert
sẽ bỏ qua việc thêm bản ghi vào cơ sở dữ liệu mà không đưa ra một ngoại lệ. - Tương tự, phương thức
insert!
cũng thêm một bản ghi vào cơ sở dữ liệu, nhưng sẽ raise exceptionActiveRecord::RecordNotUnique
nếu bản ghi đó vi phạm ràng buộc về tính duy nhất. - Phương thức
upsert
sẽ thêm hoặc cập nhật một bản ghi vào cơ sở dữ liệu tương tự như cách upsert_all thực hiện. - Ví dụ:
Post.insert({ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, unique_by: :category)
# tương tự
Post.insert_all([{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }], unique_by: :category)
Post.insert!({ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, returning: %w[ id title ])
# tương tự
Post.insert_all!([{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }], returning: %w[ id title ])
Post.upsert({ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }, unique_by: :category, returning: %w[ id title ])
# tương tự
Post.upsert_all([{ id: 5, title: 'Fly high', author: 'Amanda', category: 'Ballad' }], unique_by: :category, returning: %w[ id title ])
-
Trên đây là những phương thức giúp chúng ta có thể insert hay upsert hàng loạt bản ghi cùng lúc mà Rails 6 hỗ trợ. Cảm ơn mọi người đã đọc bài viết này!
-
Tài liệu tham khảo: Bulk support in Rails 6
All rights reserved