CodeGym /Các khóa học /SQL SELF /Xử lý lỗi khi nạp dữ liệu (`ON CONFLICT`)

Xử lý lỗi khi nạp dữ liệu (`ON CONFLICT`)

SQL SELF
Mức độ , Bài học
Có sẵn

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 = 1 sẽ được cập nhật tên và tuổi.
  • Sinh viên với id = 2id = 3 sẽ đượ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

  1. Đầu tiên tạo bảng tạm tmp_students:
CREATE TEMP TABLE tmp_students (
  id   INTEGER,
  name TEXT,
  age  INTEGER
);
  1. Nạp dữ liệu từ file bằng \COPY:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
  1. 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 trong ON CONFLICT nha. Ví dụ, nếu bạn chỉ sai khóa (id thay vì email), PostgreSQL sẽ "bye bye" câu lệnh của bạn luôn.
  • Dùng EXCLUDED sai 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êm SET 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.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION