Chào mừng bạn đến với trung tâm của những tình huống "drama" khi nạp dữ liệu hàng loạt! Hôm nay tụi mình sẽ học cách xử lý lỗi khi nạp dữ liệu bằng ON CONFLICT. Nó giống như bật chế độ tự lái cho máy bay: dù có gì đó trục trặc, bạn vẫn biết cách xử lý để tránh "tai nạn". Cùng khám phá mấy chiêu trò của PostgreSQL nhé!
Không ai thích bất ngờ, nhất là khi dữ liệu không chịu nạp! Khi nạp dữ liệu hàng loạt, bạn có thể gặp mấy vấn đề kinh điển sau:
- Trùng lặp dữ liệu. Ví dụ, nếu bảng có ràng buộc
UNIQUE, mà file dữ liệu của bạn lại có nhiều dòng giống nhau. - Xung đột với ràng buộc. Ví dụ, thử nạp giá trị rỗng vào cột có ràng buộc
NOT NULL. Kết quả? Lỗi liền. PostgreSQL lúc nào cũng "nghiêm túc" vụ này. - Trùng khóa chính. Bảng có thể đã có dữ liệu với id giống như trong file CSV của bạn.
Cùng xem làm sao để né mấy "cục đá ngầm" này với ON CONFLICT nhé.
Dùng ON CONFLICT để xử lý lỗi
Cú pháp của ON CONFLICT cho phép bạn chỉ định phải làm gì khi gặp xung đột với ràng buộc (ví dụ UNIQUE hoặc PRIMARY KEY). PostgreSQL cho phép bạn cập nhật dữ liệu đã có, hoặc đơn giản là bỏ qua dòng bị xung đột.
Đây là cú pháp cơ bản của ON CONFLICT:
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON CONFLICT (conflict_target)
DO UPDATE SET column1 = new_value1, column2 = new_value2;
Bạn có thể thay DO UPDATE bằng DO NOTHING nếu chỉ muốn bỏ qua xung đột thôi.
Ví dụ: cập nhật dữ liệu khi có xung đột
Giả sử bạn có bảng students:
CREATE TABLE students (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
age INT
);
Bây giờ bạn muốn nạp dữ liệu mới, nhưng một số đã có trong database rồi:
INSERT INTO students (id, name, age)
VALUES
(1, 'Peter', 22), -- Sinh viên này đã có rồi
(2, 'Anna', 20), -- Sinh viên mới
(3, 'Mal', 25) -- Sinh viên mới
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
age = EXCLUDED.age;
Trong ví dụ này, nếu đã có sinh viên với ID trùng, thì dữ liệu của bạn sẽ được cập nhật:
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
age = EXCLUDED.age;
Lưu ý từ khóa "ma thuật" EXCLUDED. Nó có nghĩa là "giá trị bạn định chèn vào nhưng bị loại ra vì xung đột".
Kết quả:
- Sinh viên với
id = 1sẽ được cập nhật tên và tuổi. - Sinh viên với
id = 2vàid = 3sẽ được thêm mới vào bảng.
Ví dụ: bỏ qua xung đột
Nếu bạn không muốn cập nhật mà chỉ muốn bỏ qua dòng gây xung đột, dùng DO NOTHING:
INSERT INTO students (id, name, age)
VALUES
(1, 'Peter', 22), -- Sinh viên này đã có rồi
(2, 'Anna', 20), -- Sinh viên mới
(3, 'Mal', 25) -- Sinh viên mới
ON CONFLICT (id) DO NOTHING;
Bây giờ các dòng bị xung đột sẽ không được chèn, còn lại thì vô database bình thường.
Ghi log lỗi
Đôi khi chỉ bỏ qua hoặc cập nhật là chưa đủ. Ví dụ, bạn cần ghi lại các xung đột để phân tích sau này. Ta có thể tạo bảng riêng để log lỗi:
CREATE TABLE conflict_log (
conflict_time TIMESTAMP DEFAULT NOW(),
id INT,
name TEXT,
age INT,
conflict_reason TEXT
);
Tiếp theo, thêm xử lý lỗi với ghi log:
INSERT INTO students (id, name, age)
VALUES
(1, 'Peter', 22),
(2, 'Anna', 20),
(3, 'Mal', 25)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
age = EXCLUDED.age
RETURNING EXCLUDED.id, EXCLUDED.name, EXCLUDED.age
INTO conflict_log;
Ví dụ cuối này chỉ chạy được trong stored procedures thôi nha. Cách hoạt động cụ thể thì tụi mình sẽ học khi tới phần PL-SQL. Mình "spoil" trước chút để bạn biết thêm một cách xử lý xung đột khi nạp dữ liệu: log lại tất cả dòng có vấn đề.
Bây giờ bạn có thể phân tích nguyên nhân xung đột. Kỹ thuật này cực kỳ hữu ích trong các hệ thống phức tạp, nơi cần lưu lại "dấu vết" khi nạp dữ liệu hàng loạt.
Ví dụ thực tế
Cùng gom hết kiến thức lại làm một bài tập nhỏ. Giả sử bạn có file CSV cập nhật sinh viên, muốn nạp vào bảng:
File students_update.csv
| id | name | age |
|---|---|---|
| 1 | Otto | 23 |
| 2 | Anna | 21 |
| 4 | Wally | 30 |
Nạp dữ liệu và xử lý xung đột
- Đầu tiên tạo bảng tạm
tmp_students:
CREATE TEMP TABLE tmp_students (
id INTEGER,
name TEXT,
age INTEGER
);
- Nạp dữ liệu từ file bằng
\COPY:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
- Chèn dữ liệu từ bảng tạm vào bảng chính bằng
INSERT ON CONFLICT:
INSERT INTO students (id, name, age)
SELECT id, name, age FROM tmp_students
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
age = EXCLUDED.age;
Bây giờ tất cả dữ liệu, kể cả dòng cập nhật (id = 1), đã được nạp thành công.
Lỗi thường gặp và cách tránh
Lỗi thì ai cũng dính, kể cả coder "pro", nhưng biết cách tránh thì bạn sẽ tiết kiệm được cả đống thời gian (và thần kinh).
- Xung đột với ràng buộc
UNIQUE. Nhớ chỉ đúng trường trongON CONFLICTnha. Ví dụ, nếu bạn chỉ sai khóa (idthay vìemail), PostgreSQL sẽ "bye bye" câu lệnh của bạn luôn. - Dùng
EXCLUDEDsai chỗ. Alias này chỉ dùng cho giá trị truyền vào trong câu lệnh hiện tại thôi. Đừng cố dùng nó ở chỗ khác. - Thiếu cột. Đảm bảo tất cả cột trong
SETđều có trong bảng. Ví dụ, thêmSET non_existing_column = 'value'là lỗi ngay.
Dùng ON CONFLICT giúp nạp dữ liệu hàng loạt vào PostgreSQL linh hoạt và an toàn hơn. Bạn không chỉ tránh được lỗi do xung đột, mà còn kiểm soát được cách xử lý dữ liệu. Người dùng (và server!) sẽ cảm ơn bạn nhiều lắm đấy.
GO TO FULL VERSION