Hôm nay tụi mình sẽ mổ xẻ mấy lỗi thường gặp khi tạo function, lý do vì sao bị và cách xử lý. Chỉ có debug mới giúp coder lên trình thôi mà! Let's debug it!
Việc tạo function, nhất là lúc mới học PL/pgSQL, nhìn thì hơi khoai. Ngay cả mấy bạn code lâu năm với PostgreSQL cũng dễ dính bẫy. Giờ mình cùng xem từng lỗi một nhé.
1. Quên từ khóa RETURNS
PL/pgSQL khá là nghiêm ngặt về cách bạn mô tả function. Một trong những lỗi phổ biến nhất là quên chỉ rõ kiểu dữ liệu mà function sẽ trả về. Xem ví dụ này nha:
-- Lỗi: thiếu từ khóa RETURNS
CREATE FUNCTION incorrect_function() AS $$
BEGIN
RETURN 1;
END;
$$ LANGUAGE plpgsql;
PostgreSQL sẽ không hiểu function này trả về cái gì. RETURNS là bắt buộc để mô tả kiểu dữ liệu trả về (ví dụ RETURNS INT, RETURNS TEXT, hoặc RETURNS VOID).
Cách sửa: thêm từ khóa RETURNS cùng kiểu dữ liệu:
CREATE FUNCTION correct_function() RETURNS INT AS $$
BEGIN
RETURN 1;
END;
$$ LANGUAGE plpgsql;
2. Trả về kết quả mà không có RETURN
Mấy bạn mới học hay quên là trong PL/pgSQL, muốn trả về kết quả thì phải dùng lệnh RETURN rõ ràng. Ví dụ nè:
-- Lỗi: thiếu RETURN
CREATE FUNCTION missing_return() RETURNS TEXT AS $$
BEGIN
'Xin chào, Thế giới!'; -- Chỉ là chuỗi thôi, chưa trả về
END;
$$ LANGUAGE plpgsql;
Ở đây chuỗi 'Xin chào, Thế giới!' chỉ được ghi ra, chứ không trả về. PostgreSQL sẽ coi như không có kết quả và báo lỗi.
Cách sửa: thêm lệnh RETURN cho rõ ràng:
CREATE FUNCTION fixed_return() RETURNS TEXT AS $$
BEGIN
RETURN 'Xin chào, Thế giới!';
END;
$$ LANGUAGE plpgsql;
3. Gán giá trị cho biến chưa khai báo
Trong PL/pgSQL, bạn phải khai báo biến trong block DECLARE trước khi dùng. Ví dụ:
-- Lỗi: biến my_var chưa khai báo
CREATE FUNCTION missing_variable() RETURNS VOID AS $$
BEGIN
my_var := 'Xin chào, Thế giới!';
END;
$$ LANGUAGE plpgsql;
PostgreSQL không biết biến my_var là gì vì chưa khai báo trong DECLARE.
Cách sửa: luôn khai báo biến trong DECLARE:
CREATE FUNCTION declared_variable() RETURNS VOID AS $$
DECLARE
my_var TEXT;
BEGIN
my_var := 'Xin chào, Thế giới!';
END;
$$ LANGUAGE plpgsql;
4. Dùng sai kiểu trả về VOID
Kiểu VOID nghĩa là function không trả về dữ liệu gì cả. Đôi khi bạn lại dùng RETURN trong function kiểu VOID, thế là lỗi:
-- Lỗi: RETURN trong function kiểu VOID
CREATE FUNCTION void_example() RETURNS VOID AS $$
BEGIN
RETURN 1; -- Không được trả về giá trị
END;
$$ LANGUAGE plpgsql;
Function kiểu VOID không nên trả về giá trị nào cả. Có thể dùng RETURN nhưng không kèm giá trị.
Cách sửa: hoặc bỏ RETURN, hoặc ghi RETURN không giá trị:
CREATE FUNCTION correct_void() RETURNS VOID AS $$
BEGIN
-- Chỉ thực hiện hành động thôi
RAISE NOTICE 'Function này không trả về gì hết';
RETURN; -- Kết thúc function
END;
$$ LANGUAGE plpgsql;
5. Dùng RAISE debug sai cách
Debug trong PL/pgSQL thường dùng lệnh RAISE NOTICE. Nhưng nếu format hoặc biến sai là lỗi ngay.
Ví dụ:
-- Lỗi: format sai
CREATE FUNCTION debug_example() RETURNS VOID AS $$
BEGIN
RAISE NOTICE 'Giá trị là %'; -- Thiếu biến
END;
$$ LANGUAGE plpgsql;
Lệnh RAISE đòi hỏi sau % phải có biến hoặc giá trị. Nếu để trống, PostgreSQL sẽ không xử lý được.
Cách sửa: đảm bảo biến hoặc giá trị được truyền đúng:
CREATE FUNCTION fixed_debug() RETURNS VOID AS $$
DECLARE
my_var TEXT := 'PostgreSQL';
BEGIN
RAISE NOTICE 'Giá trị là %', my_var; -- Có biến rồi nè
END;
$$ LANGUAGE plpgsql;
6. Lỗi trùng tên biến và cột
Nếu tên biến trùng với tên cột thì dễ bị kết quả bất ngờ. Ví dụ:
-- Lỗi: trùng tên biến và cột
CREATE FUNCTION name_conflict() RETURNS TEXT AS $$
DECLARE
name TEXT;
BEGIN
SELECT name INTO name FROM students LIMIT 1; -- name nào được dùng đây?
RETURN name;
END;
$$ LANGUAGE plpgsql;
PL/pgSQL sẽ ưu tiên biến hơn là tên cột nếu trùng tên.
Cách sửa: dùng alias cho bảng hoặc tránh trùng tên.
CREATE FUNCTION fixed_conflict() RETURNS TEXT AS $$
DECLARE
student_name TEXT;
BEGIN
SELECT s.name INTO student_name FROM students s LIMIT 1;
RETURN student_name;
END;
$$ LANGUAGE plpgsql;
7. Lỗi khi chạy query trong vòng lặp
Lỗi hay gặp khi chạy SQL trong vòng lặp. Ví dụ:
-- Lỗi: query sai trong vòng lặp
CREATE FUNCTION cycle_error() RETURNS VOID AS $$
BEGIN
FOR rec IN SELECT * FROM students LOOP
EXECUTE 'UPDATE students SET active = TRUE WHERE id = ' || rec.id;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SQL injection... Nguy hiểm! Nối chuỗi để tạo query là thói quen xấu, dễ bị tấn công.
Cách sửa là dùng tham số:
CREATE FUNCTION safe_cycle() RETURNS VOID AS $$
BEGIN
FOR rec IN SELECT * FROM students LOOP
EXECUTE 'UPDATE students SET active = TRUE WHERE id = $1' USING rec.id;
END LOOP;
END;
$$ LANGUAGE plpgsql;
8. Lỗi kiểu dữ liệu
Ví dụ lỗi:
-- Lỗi: kiểu dữ liệu không khớp
CREATE FUNCTION type_error() RETURNS INT AS $$
DECLARE
my_var TEXT := 'khong_phai_so';
BEGIN
RETURN my_var; -- Lỗi vì trả về TEXT thay vì INT
END;
$$ LANGUAGE plpgsql;
PostgreSQL đòi INT mà lại nhận TEXT. Kiểu dữ liệu phải khớp nhau mới được.
Cách sửa? Đảm bảo kiểu dữ liệu đúng, hoặc ép kiểu rõ ràng:
CREATE FUNCTION type_correct() RETURNS INT AS $$
DECLARE
my_var TEXT := '42';
BEGIN
RETURN my_var::INT; -- Ép kiểu từ text sang số
END;
$$ LANGUAGE plpgsql;
Best practice và tips
- Chia nhỏ function phức tạp thành nhiều function nhỏ. Debug và test sẽ dễ hơn nhiều.
- Nhớ comment trong function để giải thích mấy đoạn khó hiểu.
- Luôn test function với dữ liệu nhỏ trước khi chạy trên bảng thật.
- Debug bằng
RAISE NOTICEđể xem luồng chạy. - Tránh SQL injection: luôn dùng tham số cho query.
-- Dùng RAISE để debug
DO $$
DECLARE
total_students INT;
BEGIN
SELECT COUNT(*) INTO total_students FROM students;
RAISE NOTICE 'Tổng số sinh viên: %', total_students; -- Thông báo debug
END;
$$;
Những tips này sẽ giúp bạn tránh được cả đống đau đầu và không còn "dẫm phải cạm bẫy" PL/pgSQL nữa!
GO TO FULL VERSION