Trong phát triển ứng dụng cơ sở dữ liệu, xử lý xung đột khóa chính là một vấn đề thường trực. Khi chèn dữ liệu trùng với bản ghi hiện có về khóa chính, ta cần quyết định hành động: bỏ qua, cập nhật hay báo lỗi?
Dameng Database (DM8) cung cấp hai cơ chế hiệu quả để giải quyết vấn đề này: câu lệnh MERGE INTO chuẩn SQL và chỉ dẫn IGNORE_ROW_ON_DUPKEY_INDEX. Bài viết phân tích sâu cả hai phương pháp kèm ví dụ minh họa.
Chuẩn bị môi trường
CREATE TABLE nhan_vien (
ma_nv INT PRIMARY KEY,
ten_nv VARCHAR(50) NOT NULL,
ngay_vao DATE,
phong_ban VARCHAR(50),
luong DECIMAL(10,2)
);
INSERT INTO nhan_vien VALUES (1, 'Nguyễn A', '2023-01-15', 'Kỹ thuật', 15000);
INSERT INTO nhan_vien VALUES (2, 'Trần B', '2023-03-20', 'Marketing', 12000);
COMMIT;
Phương pháp 1: MERGE INTO – Giải pháp toàn diện
MERGE INTO thực hiện logic "tồn tại thì cập nhật, chưa có thì chèn" trong một thao tác duy nhất.
Ví dụ cơ bản
MERGE INTO nhan_vien t
USING (
SELECT 1 AS ma_nv, 'Nguyễn Văn A' AS ten_nv, '2023-01-15' AS ngay_vao,
'Kỹ thuật' AS phong_ban, 16000 AS luong FROM DUAL
UNION ALL
SELECT 3, 'Lê C', '2024-01-10', 'Tài chính', 11000 FROM DUAL
) nguon
ON (t.ma_nv = nguon.ma_nv)
WHEN MATCHED THEN
UPDATE SET
t.ten_nv = nguon.ten_nv,
t.phong_ban = nguon.phong_ban,
t.luong = nguon.luong
WHEN NOT MATCHED THEN
INSERT (ma_nv, ten_nv, ngay_vao, phong_ban, luong)
VALUES (nguon.ma_nv, nguon.ten_nv, nguon.ngay_vao, nguon.phong_ban, nguon.luong);
Kết quả:
- Bản ghi mã 1 được cập nhật (tên thành "Nguyễn Văn A", lương tăng lên 16000)
- Bản ghi mới mã 3 được chèn vào
Tính năng nâng cao
Cập nhật có điều kiện:
MERGE INTO nhan_vien t
USING (SELECT 1 AS ma_nv, 18000 AS luong_moi FROM DUAL) nguon
ON (t.ma_nv = nguon.ma_nv)
WHEN MATCHED THEN
UPDATE SET t.luong = nguon.luong_moi
WHERE t.luong < nguon.luong_moi -- Chỉ tăng lương
WHEN NOT MATCHED THEN
INSERT (ma_nv, luong) VALUES (nguon.ma_nv, nguon.luong_moi);
Xóa khi khớp điều kiện:
MERGE INTO nhan_vien t
USING (SELECT 1 AS ma_nv, 'THOAI_VIEC' AS trang_thai FROM DUAL) nguon
ON (t.ma_nv = nguon.ma_nv)
WHEN MATCHED THEN
UPDATE SET t.phong_ban = 'Đã nghỉ'
DELETE WHERE nguon.trang_thai = 'THOAI_VIEC';
Phương pháp 2: IGNORE_ROW_ON_DUPKEY_INDEX – Bỏ qua xung đột
Chỉ dẫn này cho phép bỏ qua các dòng gây xung đột khóa chính mà không báo lỗi.
Ví dụ cơ bản
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(nhan_vien(ma_nv)) */
INTO nhan_vien (ma_nv, ten_nv, phong_ban, luong)
VALUES (1, 'Nguyễn A', 'Kỹ thuật', 15000); -- Bị bỏ qua do trùng khóa
Chèn hàng loạt
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(nhan_vien(ma_nv)) */
INTO nhan_vien (ma_nv, ten_nv, phong_ban, luong)
SELECT 1, 'Nguyễn A', 'Kỹ thuật', 15000 FROM DUAL UNION ALL
SELECT 4, 'Phạm D', 'Hành chính', 9000 FROM DUAL UNION ALL
SELECT 2, 'Trần B', 'Marketing', 12500 FROM DUAL; -- Chỉ chèn được mã 4
Chỉ mục duy nhất đa cột
CREATE UNIQUE INDEX idx_ten_phong ON nhan_vien(ten_nv, phong_ban);
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(nhan_vien(ten_nv, phong_ban)) */
INTO nhan_vien (ma_nv, ten_nv, phong_ban, luong)
VALUES (5, 'Nguyễn A', 'Kỹ thuật', 16000); -- Bị bỏ qua do trùng tên + phòng
So sánh và lựa chọn
| Đặc điểm | MERGE INTO | IGNORE HINT |
|---|---|---|
| Độ phức tạp | Cao | Thấp |
| Khả năng cập nhật | Có | Không |
| Hỗ trợ xóa | Có | Không |
| Tính chuẩn hóa | SQL chuẩn | Đặc tả Dameng |
| Phản hồi kết quả | Chi tiết | Chỉ số dòng chèn thực tế |
Hướng dẫn lựa chọn
- Dùng
MERGE INTOkhi cần cập nhật/xóa hoặc logic phức tạp - Dùng
IGNORE HINTkhi chỉ muốn chèn dữ liệu mới, bỏ qua trùng lặp - Ưu tiên
MERGE INTOnếu cần khả năng di chuyển giữa các hệ CSDL
Lưu ý quan trọng
Vị trí chỉ dẫn: Phải đặt ngay sau INSERT, không được đặt sau tên bảng.
-- Đúng
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(...) */ INTO ...
-- Sai
INSERT INTO ... /*+ IGNORE_ROW_ON_DUPKEY_INDEX(...) */ ...
Xử lý NULL: Các giá trị NULL trong cột khóa duy nhất không được coi là trùng nhau.
Giao dịch: MERGE INTO là thao tác nguyên tử – lỗi ở bất kỳ dòng nào sẽ làm toàn bộ thao tác thất bại.
Ví dụ thực tế
Đồng bộ doanh thu hàng ngày:
MERGE INTO doanh_thu_ngay d
USING (
SELECT ngay_ban, sp_id, SUM(so_tien) AS tong_tien
FROM du_lieu_tam
WHERE ngay_ban = TRUNC(SYSDATE)
GROUP BY ngay_ban, sp_id
) tmp
ON (d.ngay_ban = tmp.ngay_ban AND d.sp_id = tmp.sp_id)
WHEN MATCHED THEN
UPDATE SET d.tong_tien = d.tong_tien + tmp.tong_tien
WHEN NOT MATCHED THEN
INSERT (ngay_ban, sp_id, tong_tien) VALUES (tmp.ngay_ban, tmp.sp_id, tmp.tong_tien);
Làm sạch dữ liệu:
INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(du_lieu_sach(id)) */
INTO du_lieu_sach
SELECT * FROM du_lieu_tho
WHERE chat_luong = 'TOT';