Upsert dữ liệu vào LarkBase bằng n8n: update theo ID, insert cái mới, không đè cột tự thêm

@Nguyễn Ngô Thượng//~5 phút đọc0
Chia sẻ:

TL;DR: Upsert là "có rồi thì cập nhật, chưa có thì thêm mới". Muốn làm đúng trên LarkBase cần ba thứ: một ID nghiệp vụ ổn định để so khớp, record_id của LarkBase để update đúng dòng, và payload chỉ chứa những trường đến từ nguồn — nhờ vậy các cột người dùng tự thêm tay không bị đè.

Bài toán

Bạn sync đơn hàng từ hệ thống bán hàng vào LarkBase mỗi giờ. Ba tình huống xảy ra cùng lúc:

  • Đơn 8051 đã có trong bảng, lần sync này đổi trạng thái từ "đang xử lý" sang "huỷ" → cần update.
  • Đơn 8052 chưa có → cần insert.
  • Cột "Ghi chú CSKH" mà nhân viên tự gõ tay cho đơn 8051phải giữ nguyên.

Làm sai một trong ba, hậu quả thấy ngay: hoặc bảng đầy bản ghi trùng, hoặc nhân viên mất công ghi chú sau mỗi lần sync — và lần sau họ sẽ không dùng hệ thống nữa.

Mấu chốt: một cột ID định danh

Mỗi bản ghi cần một ID nghiệp vụ ổn định — mã đơn, mã khách, mã chứng từ. Một dòng một ID, không trùng.

⚠️ Warning: Không có ID ổn định thì không có upsert, chỉ có insert trùng lặp. Đừng dùng tổ hợp "tên + ngày" làm khoá — khách đổi tên một chữ là hệ thống coi như bản ghi mới.

Cần phân biệt rõ hai loại ID, đây là chỗ hay nhầm nhất:

Loại Ví dụ Dùng để làm gì
ID nghiệp vụ 8051 (mã đơn) So khớp xem bản ghi đã tồn tại chưa
record_id recXXXXXXXX Chỉ đúng dòng cần update trong LarkBase

Update phải đi theo record_id. Nhầm sang ID nghiệp vụ là sửa nhầm dòng khác.

Luồng upsert trong n8n

Bước 1: Đọc dữ liệu hiện có

Lấy toàn bộ bản ghi trong bảng đích. Nhớ xử lý phân trang khi đọc — đọc thiếu dòng thì bản ghi đã tồn tại sẽ bị coi là mới và bạn nhận về một đống bản ghi trùng.

Bước 2: So khớp theo ID

Dùng node Code dựng một map từ ID nghiệp vụ sang record_id, rồi chia dữ liệu nguồn thành hai nhóm: cần update và cần insert.

Bước 3: Update và insert riêng

Hai nhánh gọi hai endpoint khác nhau. Update cần record_id, insert chỉ cần fields.

Node Code cho bước so khớp:

// $('Đọc LarkBase').all() — dữ liệu đang có trong bảng
const existing = new Map();
for (const item of $('Đọc LarkBase').all()) {
  const businessId = item.json.fields['Mã đơn'];
  if (businessId) existing.set(String(businessId), item.json.record_id);
}

const toUpdate = [];
const toInsert = [];

for (const item of $input.all()) {
  const row = item.json;
  const recordId = existing.get(String(row.ma_don));

  // Chỉ đưa vào payload những trường đến từ nguồn.
  // Cột nào không có ở đây thì LarkBase giữ nguyên giá trị cũ.
  const fields = {
    'Mã đơn': String(row.ma_don),
    'Trạng thái': row.trang_thai,
    'Tổng tiền': Number(row.tong_tien),
  };

  if (recordId) {
    toUpdate.push({ json: { _action: 'update', record_id: recordId, fields } });
  } else {
    toInsert.push({ json: { _action: 'insert', fields } });
  }
}

return [...toUpdate, ...toInsert];

Code trên trả cả hai nhóm kèm trường _action — đặt node IF phía sau lọc theo _action để rẽ nhánh update / insert. Điểm quan trọng nằm ở chỗ khác: payload chỉ chứa trường đến từ nguồn.

Vì sao cột tự thêm tay không bị mất

LarkBase cập nhật theo kiểu ghi đè từng trường có trong payload, không phải thay cả dòng. Cột "Ghi chú CSKH" không nằm trong fields bạn gửi lên, nên nó không bị đụng tới.

Đây là lý do phải liệt kê tường minh từng trường thay vì quăng nguyên object nguồn lên. Quăng nguyên object là lúc nào đó nguồn thêm một trường trùng tên cột nội bộ, và ghi chú của nhân viên biến mất.

Ba chỗ hay vấp

Sai kiểu dữ liệu field. Payload phải khớp định dạng field của LarkBase. Cột số mà gửi chuỗi là lỗi ngay; cột ngày cần timestamp mili giây chứ không phải chuỗi "2026-08-18". Kiểm vài bản ghi mẫu trước khi chạy cả lô.

Đụng rate limit. Bảng vài nghìn dòng mà bắn update từng dòng liên tục sẽ bị chặn. Dùng Loop Over Items chia lô, hoặc dùng endpoint batch để gửi nhiều bản ghi trong một lần gọi.

Không có retry. Sync nửa chừng rồi rớt mạng để lại trạng thái nửa vời. Bật retry on fail trên node gọi API, và gắn error workflow để biết lần chạy nào hỏng — thay vì phát hiện qua báo cáo lệch số vào tuần sau.

Kết quả nhận được

Luồng làm đúng thì idempotent: chạy lại bao nhiêu lần cũng không sinh bản ghi trùng, trạng thái trong LarkBase luôn khớp nguồn, và phần dữ liệu người dùng tự bổ sung vẫn còn nguyên.

Đó cũng là tiêu chí để biết luồng sync đã xong hay chưa — không phải "chạy được một lần", mà là "chạy lại lần thứ mười vẫn ra đúng một kết quả".

ℹ️ Info: Trước đây tôi dựng luồng này bằng AnyCross. Từ 2026 chuyển sang n8n vì tự host được, xem log từng node khi lần lỗi, và không tính tiền theo lượt chạy — luồng sync mỗi giờ thì số lượt cộng lại rất nhanh.

Tự làm mất bao lâu (ước lượng)

Việc Người đã quen n8n Người mới
Luồng upsert cho một bảng nửa ngày 2–3 ngày
Thêm chia lô, retry, cảnh báo 2–3 giờ 1 ngày
Sửa lại khi nguồn đổi cấu trúc 1 giờ mỗi lần nửa ngày mỗi lần

Con số đáng chú ý là dòng cuối. Luồng upsert không phải làm một lần rồi thôi — nguồn dữ liệu đổi trường, nghiệp vụ đổi quy tắc, và mỗi lần như vậy có người phải sửa. Đó mới là chi phí thật của tự làm, không phải buổi đầu tiên dựng luồng.

Nếu luồng sync này là thứ kế toán hoặc vận hành dựa vào hằng ngày, cân nhắc thuê dựng kèm gói vận hành — để khi nó hỏng thì có người chịu trách nhiệm sửa, không phải bạn.

Xem dịch vụ automation →

Bài viết hữu ích?

Chia sẻ để nhiều người biết đến!

Chia sẻ:

>_ LLM-Friendly Copy

Copy as Markdown to use with ChatGPT, Claude, or other AI tools

1,101 words|5,765 characters

//Bình luận

Bài viết liên quan

Khám phá thêm những bài viết cùng chủ đề với Upsert dữ liệu vào LarkBase bằng n8n: update theo ID, insert cái mới, không đè cột tự thêm

Bài viết hữu ích? Hãy kết nối với Diginno!

Chúng tôi giúp doanh nghiệp SME ứng dụng AI và automation vào quy trình làm việc - từ tư vấn chiến lược đến triển khai thực tế.

Upsert dữ liệu vào LarkBase bằng n8n: update theo ID, insert cái mới, không đè cột tự thêm | Blog - Diginno