Google Sheets hay Excel: so sánh thật cho người dùng văn phòng và chủ cửa hàng nhỏ
Google Sheets hay Excel: so chi phí, làm chung, dùng trên điện thoại, khi mất mạng, giới hạn dữ liệu, Apps Script với VBA và các công thức khác nhau.
Mẹo Google Sheets · 13 phút đọc
Apps Script là ngôn ngữ lập trình (dựa trên JavaScript) của Google, chạy trên máy chủ Google, dùng để tự động hoá Google Sheets: thêm hàm mới dùng như công thức, tự ghi thời gian khi sửa ô, chạy việc theo giờ và gửi email. Bạn mở nó trong file Google Sheets ở **Tiện ích mở rộng › Apps Script**, không cần cài gì. Bài này đi từ hàm đầu tiên tới hàm tuỳ chỉnh, onEdit, trigger hằng ngày, gửi email, màn hình cấp quyền "chưa xác minh", hạn mức của Google và cách kiểm tra một đoạn script lạ trước khi chép.

Google Apps Script là môi trường viết mã miễn phí đi kèm tài khoản Google. Mã viết bằng JavaScript, lưu ngay trong file (gọi là script gắn với file) hoặc thành dự án riêng, và chạy trên máy chủ Google bằng chính tài khoản của bạn — tắt máy tính thì trigger theo giờ vẫn chạy.
| Bạn muốn | Công thức làm được? | Apps Script |
|---|---|---|
| Tính toán trên dữ liệu trong bảng | Được — nên dùng công thức trước | Chỉ khi công thức quá rối |
| Ghi lại thời điểm một ô được sửa | Không (NOW() luôn đổi) | onEdit ghi một lần, giữ nguyên |
| Gửi email, tạo file Docs, PDF | Không | MailApp, DocumentApp, DriveApp |
| Chạy việc mỗi sáng 7 giờ | Không | Trigger theo giờ |
| Thêm menu, hộp thoại riêng | Không | onOpen, HtmlService |
| Lấy dữ liệu từ file Sheets khác | Được — IMPORTRANGE | Không cần |
Apps Script chỉ chạy trên Google Sheets. File .xlsx tải về mở bằng Excel thì script không chạy (Excel có VBA, ngôn ngữ khác). File .xlsx tải lên Drive cũng phải lưu thành định dạng Google Sheets thì mới gắn script được. So sánh hai bên ở bài Google Sheets hay Excel.
function xinChao() {
const sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange('A1').setValue('Xin chào từ Apps Script');
Logger.log('Đã ghi vào ' + sheet.getName() + ' lúc ' + new Date());
}Ba thứ bạn sẽ gặp trong mọi script: SpreadsheetApp (file, trang tính, ô), getValues / setValues (đọc, ghi cả vùng ô dưới dạng mảng hai chiều), Logger.log hoặc console.log (in giá trị để dò lỗi). Mảng getValues đánh số từ 0: cột A là r[0], cột D là r[3]; còn getRange(dòng, cột) đánh số từ 1.
Đặt múi giờ trước khi làm gì với ngày giờ
Script có múi giờ riêng, có thể khác múi giờ của bảng tính. Vào Cài đặt dự án (biểu tượng bánh răng bên trái trình soạn) đặt múi giờ (GMT+07:00) Bangkok, Hà Nội — giống trong Tệp › Cài đặt của bảng tính. Lệch múi giờ là lý do hay gặp nhất khiến ngày ghi ra bị sai một ngày.
Hàm bạn viết trong Apps Script dùng được ngay trong ô như hàm có sẵn. Ví dụ hàm bỏ dấu tiếng Việt — việc mà công thức thường phải lồng hàng chục SUBSTITUTE:
/**
* Bỏ dấu tiếng Việt: "Nguyễn Thu Hà" → "Nguyen Thu Ha".
* @param {string|Array<Array<string>>} text Ô hoặc vùng ô cần bỏ dấu
* @return Chữ không dấu
* @customfunction
*/
function BODAU(text) {
if (Array.isArray(text)) return text.map(row => row.map(BODAU));
return String(text)
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/đ/g, 'd')
.replace(/Đ/g, 'D');
}=BODAU(B2)onEdit là trigger đơn giản: chỉ cần đặt tên hàm đúng là onEdit, Google tự gọi mỗi khi có người sửa một ô, không cần cấp quyền. Ví dụ tab Công việc: cột E Trạng thái, cột F Ngày xong. Khi ai đó chọn Xong ở cột E, cột F cùng dòng tự ghi ngày giờ — và không đổi nữa, khác hẳn hàm NOW().
function onEdit(e) {
const range = e.range;
const sheet = range.getSheet();
if (sheet.getName() !== 'Công việc') return; // chỉ tab Công việc
if (range.getColumn() !== 5 || range.getRow() < 2) return; // chỉ cột E, bỏ dòng tiêu đề
const o = range.offset(0, 1); // ô cột F cùng dòng
if (e.value === 'Xong' && o.getValue() === '') {
o.setValue(new Date());
}
}
Bẫy hay gặp với onEdit
onEdit không chạy khi ô bị đổi bởi script khác, bởi công thức tính lại hay bởi IMPORTRANGE — chỉ chạy khi người dùng sửa. Dán một lúc nhiều ô thì e.value không có giá trị, nên ví dụ trên bỏ qua. Bấm Chạy hàm onEdit ngay trong trình soạn sẽ báo lỗi vì không có e — muốn thử thì sửa một ô trong bảng. Trigger đơn giản cũng tối đa 30 giây và không gửi được email.
Cùng nhóm với onEdit là onOpen: chạy khi mở file, hay dùng để thêm một menu riêng cạnh menu Trợ giúp, bấm vào là gọi hàm của bạn — người dùng không phải mở trình soạn.
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Tiện ích của tôi')
.addItem('Gửi nhắc việc trễ hạn ngay', 'guiNhacHan')
.addItem('Bật nhắc mỗi sáng', 'caiNhacHangNgay')
.addToUi();
}Gửi email cần quyền, nên không làm được trong onEdit. Cách đúng: viết một hàm gửi email, rồi cài trigger theo giờ (installable trigger) để Google gọi hàm đó mỗi sáng bằng tài khoản của bạn.
const EMAIL_NHAN = 'ban@congty.vn'; // đổi thành email của bạn
function guiNhacHan() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Công việc');
const rows = sheet.getDataRange().getValues(); // đọc cả bảng MỘT lần
const homNay = new Date();
homNay.setHours(0, 0, 0, 0);
const tre = rows.slice(1).filter(r =>
r[4] !== 'Xong' && r[3] instanceof Date && r[3] < homNay);
if (tre.length === 0) return; // không có việc trễ thì thôi
const tz = Session.getScriptTimeZone();
const noiDung = tre.map(r =>
'- ' + r[0] + ' (' + r[2] + '), hạn ' + Utilities.formatDate(r[3], tz, 'dd/MM/yyyy')
).join('\n');
MailApp.sendEmail(EMAIL_NHAN, tre.length + ' việc trễ hạn', noiDung);
}function caiNhacHangNgay() {
// xoá trigger cũ của hàm này để không bị gửi trùng
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === 'guiNhacHan')
.forEach(t => ScriptApp.deleteTrigger(t));
ScriptApp.newTrigger('guiNhacHan')
.timeBased()
.everyDays(1)
.atHour(7)
.create();
}
Trigger đã cài cũng xem và xoá được trong trình soạn, ở mục trigger (biểu tượng đồng hồ) trên thanh bên trái. Mỗi trigger hiện tên hàm, loại sự kiện, tài khoản chạy; lần chạy lỗi được ghi ở mục lần thực thi để bạn xem lý do.
Cảnh báo khi gửi email bằng script
Email gửi đi từ chính tài khoản Gmail của bạn. Gửi nhầm vòng lặp có thể gửi hàng trăm thư trong vài giây; Google còn giới hạn số người nhận mỗi ngày (phần hạn mức bên dưới). Luôn thử với EMAIL_NHAN là email của chính bạn trước, và đừng gửi cho khách hàng danh sách lấy từ cột chưa kiểm tra.
Lần đầu chạy một hàm cần quyền (ghi bảng tính, gửi email, tạo trigger), Google hiện cửa sổ cần cấp quyền. Các bước:
Google chỉ xác minh những ứng dụng nộp hồ sơ để phát hành công khai. Script nằm trong file của bạn, chạy bằng tài khoản của bạn, không phát hành cho ai — nên nó luôn hiện dòng "chưa xác minh", kể cả hàm xinChao bạn tự viết. Đây không phải lỗi. Nhưng chính vì vậy, bước 3 là lúc phải đọc kỹ: bạn đang cho đoạn mã đó quyền làm mọi thứ trong danh sách bằng tài khoản của mình.
Tài khoản Google Workspace của công ty có thể không có nút Nâng cao nếu quản trị viên chặn script chưa duyệt — khi đó nhờ quản trị viên cho phép. Quyền đã cấp xem và thu hồi được ở trang quản lý tài khoản Google, phần ứng dụng bên thứ ba có quyền truy cập: myaccount.google.com/connections.
Google giới hạn mỗi tài khoản một lượng chạy mỗi ngày. Các con số hay đụng tới nhất, theo trang hạn mức chính thức của Apps Script (Google ghi rõ có thể thay đổi bất kỳ lúc nào không báo trước):
| Hạn mức | Tài khoản Gmail cá nhân | Google Workspace |
|---|---|---|
| Người nhận email mỗi ngày (MailApp) | 100 | 1.500 |
| Thời gian chạy mỗi lần thực thi | 6 phút | 6 phút |
| Hàm tuỳ chỉnh mỗi lần gọi | 30 giây | 30 giây |
| Tổng thời gian trigger chạy mỗi ngày | 90 phút | 6 giờ |
| Số trigger mỗi người mỗi script | 20 | 20 |
| Số lần gọi UrlFetch mỗi ngày | 20.000 | 100.000 |
Logger.log('Còn gửi được: ' + MailApp.getRemainingDailyQuota() + ' người nhận hôm nay');Đụng giới hạn 6 phút thường là do đọc ghi từng ô một trong vòng lặp. Mỗi lần getValue, setValue là một lần gọi tới bảng tính; 1.000 dòng là 2.000 lần gọi. Đọc cả vùng một lần, tính trong bộ nhớ, ghi lại một lần thì cùng việc đó chỉ mất vài giây:
// CHẬM: mỗi ô một lần gọi tới bảng tính
for (let i = 2; i <= 1000; i++) {
sheet.getRange(i, 7).setValue(sheet.getRange(i, 4).getValue() * 2);
}
// NHANH: đọc một lần, tính trong bộ nhớ, ghi một lần
const d = sheet.getRange(2, 4, 999, 1).getValues();
sheet.getRange(2, 7, 999, 1).setValues(d.map(r => [r[0] * 2]));Script chạy với đúng quyền của tài khoản bạn. Một đoạn mã chép từ nhóm Facebook, diễn đàn hay do người lạ gửi qua Zalo có thể đọc file Drive, gửi thư từ Gmail của bạn, hoặc gửi dữ liệu ra một máy chủ ngoài. Trước khi bấm Cho phép, kiểm tra:
| Thấy gì trong mã hoặc màn hình quyền | Vì sao đáng ngờ |
|---|---|
| UrlFetchApp.fetch tới một địa chỉ lạ | Dữ liệu trong file có thể bị gửi ra ngoài |
| GmailApp, quyền đọc hoặc xoá thư | Script nhắc hạn chỉ cần gửi thư (MailApp), không cần đọc hộp thư |
| DriveApp.getFiles(), quyền xem và xoá mọi tệp Drive | Script cho một bảng tính hiếm khi cần đụng tới mọi file |
| Mã bị làm rối: chuỗi dài vô nghĩa, eval, atob | Cố tình che việc thật sự làm |
| Quyền không khớp với việc script hứa làm | Ví dụ hàm bỏ dấu mà xin quyền gửi email |
Viết vài hàm như trên mất một buổi. Viết cả một hộp thoại có Kanban kéo thả, email nhắc hạn, báo cáo tuần, kết nối Google Calendar thì mất nhiều tuần. Các mẫu PRO của HiTaVa đi kèm gói Apps Script viết sẵn, mã nguồn để mở trong Code.gs và các file .html, cài bằng cách dán vào Tiện ích mở rộng › Apps Script theo hướng dẫn từng bước.

Chủ nhà trọ thì Quản lý phòng trọ PRO dùng Apps Script để tạo hoá đơn có mã QR VietQR, PDF lưu Drive, gửi email hàng loạt, đối soát sao kê ngân hàng và email nhắc việc mỗi sáng; phần tính tiền, công nợ vẫn chạy bằng công thức trên cả Excel.
Hướng dẫn cài đặt và dùng Bảng theo dõi công việc PRO, gồm phần cài Apps Script
Xem trên YouTube ↗Không. Apps Script miễn phí với mọi tài khoản Google, kể cả Gmail cá nhân; chỉ khác nhau ở hạn mức (ví dụ Gmail gửi được 100 người nhận mỗi ngày, Google Workspace 1.500).
Trong file Google Sheets, menu Tiện ích mở rộng › Apps Script. Nếu không thấy, kiểm tra file có đang ở chế độ xem hoặc là file .xlsx chưa lưu thành định dạng Google Sheets không.
Không. Apps Script chỉ chạy trong Google Sheets (và các ứng dụng Google khác). Excel dùng VBA hoặc Office Scripts, cú pháp khác hẳn; file tải về dạng .xlsx sẽ không mang theo script.
Hay gặp nhất: tên hàm gõ sai (phải đúng onEdit), ô bị đổi bởi công thức hoặc script khác thay vì người sửa, hoặc bạn bấm Chạy trong trình soạn nên không có e. onEdit cũng không gửi được email — việc đó cần trigger cài đặt.
Màn hình này luôn hiện với script tự viết hoặc gắn trong file, nên bản thân nó không phải dấu hiệu nguy hiểm. Rủi ro nằm ở đoạn mã: chỉ bấm Cho phép khi bạn biết script đến từ đâu và danh sách quyền khớp với việc nó làm.
Có. Trigger chạy trên máy chủ Google bằng tài khoản người cài, không cần mở file hay bật máy. Giờ chạy lệch trong khoảng một tiếng so với giờ đặt.
Mẫu khác liên quan: Quản lý phòng trọ PRO.
Google Sheets hay Excel: so chi phí, làm chung, dùng trên điện thoại, khi mất mạng, giới hạn dữ liệu, Apps Script với VBA và các công thức khác nhau.
Cách dùng hàm FILTER trong Google Sheets: lọc 1 hay nhiều điều kiện AND/OR, theo ngày, chữ chứa, kết hợp SORT, UNIQUE và sửa lỗi không tìm thấy kết quả.
Cách dùng hàm SUMIFS và COUNTIFS trong Google Sheets: cú pháp, nhiều điều kiện, lọc theo tháng bằng DATE, ký tự đại diện, sửa lỗi ra 0, so với SUMIF.
Cách tạo danh sách thả xuống (dropdown) trong Google Sheets: nhập tay, lấy từ dải ô, chip màu, thả xuống phụ thuộc 2 cấp, chặn nhập sai, sửa lỗi không hợp lệ.
Xem thêm mẹo và video hướng dẫn trên kênh YouTube HiTaVa, hoặc đọc các bài khác trong chủ đề Mẹo Google Sheets.
Cần hỏi thêm?Zalo 0935077673 (Hải Vân)