SELECT 2 + 3; -- 5SELECT now(); -- thời điểm hiện tạiSELECT version(); -- phiên bản PostgreSQLSELECT upper('postgres'); -- POSTGRES
Rất hữu ích khi muốn thử nhanh một hàm.
2. Bí danh cột (column alias)
Bí danh đặt tên tạm cho cột hoặc biểu thức chỉ trong phạm vi câu truy vấn đó.
SELECT bieu_thuc AS ten_bi_danhFROM ten_bang;
Từ khóa AS có thể lược bỏ:
SELECT first_name ten, last_name hoFROM customer;
2.1. Bí danh có khoảng trắng hoặc chữ hoa
Phải bọc trong dấu nháy kép":
SELECT first_name || ' ' || last_name AS "Họ và tên", email AS "Địa chỉ Email"FROM customerLIMIT 3;
Họ và tên | Địa chỉ Email---------------------+--------------------------------------- Jared Ely | jared.ely@sakilacustomer.org Mary Smith | mary.smith@sakilacustomer.org Patricia Johnson | patricia.johnson@sakilacustomer.org
Nháy đơn vs nháy kép — lỗi kinh điển
Trong PostgreSQL hai loại nháy có ý nghĩa hoàn toàn khác nhau:
Ký hiệu
Ý nghĩa
Ví dụ
'...'
Chuỗi ký tự (giá trị dữ liệu)
WHERE first_name = 'Mary'
"..."
Định danh (tên bảng/cột/bí danh)
AS "Họ và tên"
Viết SELECT "Mary" sẽ báo lỗi column "Mary" does not exist vì Postgres hiểu đó là tên cột.
Viết AS 'Ho ten' cũng lỗi cú pháp trong PostgreSQL hiện đại.
-- ❌ LỖI: WHERE chạy trước SELECT nên chưa biết bí danhSELECT length AS thoi_luongFROM filmWHERE thoi_luong > 120;-- ERROR: column "thoi_luong" does not exist-- ✅ ĐÚNG: lặp lại biểu thứcSELECT length AS thoi_luongFROM filmWHERE length > 120;-- ✅ ĐÚNG: ORDER BY dùng được bí danhSELECT length AS thoi_luongFROM filmORDER BY thoi_luong DESC;-- ✅ ĐÚNG: GROUP BY dùng được bí danh (đặc cách của PostgreSQL)SELECT rating AS xep_hang, count(*)FROM filmGROUP BY xep_hang;
Cách tránh lặp biểu thức phức tạp
Nếu biểu thức dài, dùng CTE hoặc subquery thay vì chép lại:
WITH t AS ( SELECT title, length / 60.0 AS gio FROM film)SELECT * FROM t WHERE gio > 2;
SELECT cot1, cot2FROM ten_bangORDER BY cot1 [ASC | DESC] [NULLS FIRST | NULLS LAST], cot2 [ASC | DESC];
ASC — tăng dần (mặc định, không cần viết)
DESC — giảm dần
3.1. Sắp xếp theo một cột
SELECT first_name, last_nameFROM customerORDER BY first_name ASCLIMIT 5;
first_name | last_name------------+----------- Aaron | Selby Adam | Gooch Adrian | Clary Agnes | Bishop Alan | Kahn
3.2. Sắp xếp theo nhiều cột
Postgres sắp theo cột đầu trước; chỉ khi bằng nhau mới xét cột sau:
SELECT first_name, last_nameFROM customerORDER BY first_name ASC, last_name DESCLIMIT 5;
3.3. Sắp xếp theo biểu thức
SELECT first_name, last_name, length(first_name) AS do_daiFROM customerORDER BY length(first_name) DESCLIMIT 5;
3.4. Sắp xếp theo số thứ tự cột
SELECT first_name, last_nameFROM customerORDER BY 2, 1; -- sắp theo last_name rồi first_name
Tránh dùng số thứ tự trong code thật
ORDER BY 2 trở nên sai lặng lẽ khi ai đó chèn thêm cột vào SELECT. Chỉ dùng khi gõ nhanh trong psql.
3.5. NULL được sắp ở đâu?
Trong PostgreSQL, mặc định:
ASC → NULLS LAST (NULL nằm cuối)
DESC → NULLS FIRST (NULL nằm đầu)
Postgres coi NULL là lớn hơn mọi giá trị khi sắp xếp. Bạn có thể ghi đè:
-- Ví dụ tự chứa để thấy rõCREATE TABLE demo_null (num INT);INSERT INTO demo_null VALUES (1), (2), (3), (NULL);SELECT num FROM demo_null ORDER BY num;-- 1, 2, 3, NULL (ASC → NULLS LAST)SELECT num FROM demo_null ORDER BY num NULLS FIRST;-- NULL, 1, 2, 3SELECT num FROM demo_null ORDER BY num DESC;-- NULL, 3, 2, 1 (DESC → NULLS FIRST)SELECT num FROM demo_null ORDER BY num DESC NULLS LAST;-- 3, 2, 1, NULL
Khác biệt với MySQL
MySQL luôn xếp NULL đầu tiên khi ASC. Postgres làm ngược lại. Nếu bạn di chuyển truy vấn giữa hai hệ, hãy viết NULLS FIRST/NULLS LAST tường minh.
3.6. Cạm bẫy ORDER BY không xác định
-- ⚠️ Kết quả KHÔNG ổn định giữa các lần chạySELECT title, rental_rateFROM filmORDER BY rental_rate DESCLIMIT 3;
Có 336 phim cùng rental_rate = 4.99. Postgres không hứa hẹn trả về 3 phim nào trong số đó — thứ tự phụ thuộc kế hoạch thực thi, có thể đổi sau khi VACUUM hoặc khi thêm index.
Cách sửa: luôn thêm một cột phân biệt duy nhất làm tiêu chí cuối:
SELECT title, rental_rateFROM filmORDER BY rental_rate DESC, film_idLIMIT 3;
-- Xem collation hiện tạiSHOW lc_collate;-- Sắp theo quy tắc tiếng Việt cho một truy vấn cụ thểSELECT ten FROM nhan_vienORDER BY ten COLLATE "vi-VN-x-icu";
Collation ICU vi-VN-x-icu xử lý đúng thứ tự Ă, Â, Đ, Ê, Ô, Ơ, Ư và dấu thanh — điều mà collation C hay en_US.UTF-8 không làm được.
4. SELECT DISTINCT
DISTINCT loại bỏ các dòng trùng lặp khỏi kết quả.
SELECT DISTINCT cot1, cot2FROM ten_bang;
DISTINCT áp dụng cho cả dòng, không phải từng cột
SELECT DISTINCT a, b loại bỏ những dòng mà cặp (a, b) trùng nhau, chứ không phải khử trùng riêng từng cột. Đây là hiểu nhầm phổ biến nhất về DISTINCT.
4.1. Ví dụ tự chứa
DROP TABLE IF EXISTS mau_sac;CREATE TABLE mau_sac ( id SERIAL PRIMARY KEY, mau_nen VARCHAR, mau_chu VARCHAR);INSERT INTO mau_sac (mau_nen, mau_chu)VALUES ('red', 'red'), ('red', 'red'), ('red', NULL), (NULL, 'red'), ('red', 'green'), ('red', 'blue'), ('green', 'red'), ('green', 'blue'), ('green', 'green'), ('blue', 'red'), ('blue', 'green'), ('blue', 'blue');
Một cột:
SELECT DISTINCT mau_nenFROM mau_sacORDER BY mau_nen;
SELECT DISTINCT mau_nen, mau_chuFROM mau_sacORDER BY mau_nen, mau_chu;
Cặp ('red','red') xuất hiện 2 lần trong bảng nhưng chỉ trả về 1 lần. Các cặp khác đều duy nhất nên giữ nguyên.
4.2. DISTINCT ON — đặc sản của PostgreSQL
DISTINCT ON (bieu_thuc) giữ lại dòng đầu tiên của mỗi nhóm giá trị, theo thứ tự ORDER BY.
SELECT DISTINCT ON (mau_nen) mau_nen, mau_chuFROM mau_sacORDER BY mau_nen, mau_chu;
mau_nen | mau_chu---------+--------- blue | blue green | blue red | blue [NULL] | red(4 rows)
Ứng dụng thực tế — lấy lần thuê gần nhất của mỗi khách:
SELECT DISTINCT ON (customer_id) customer_id, rental_id, rental_dateFROM rentalORDER BY customer_id, rental_date DESC;
Đây là bài toán “greatest-n-per-group”. Ở các hệ CSDL khác bạn phải viết window function hoặc subquery tương quan; PostgreSQL cho phép giải trong 4 dòng.
Quy tắc bắt buộc của DISTINCT ON
Biểu thức trong DISTINCT ON (...)phải là các cột đầu tiên của ORDER BY. Nếu không, PostgreSQL báo lỗi:
ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions
Trong ví dụ trên, ORDER BY customer_id, rental_date DESC — customer_id đứng đầu, đúng yêu cầu.
4.3. DISTINCT vs GROUP BY
Hai câu sau cho kết quả giống hệt nhau:
SELECT DISTINCT rating FROM film;SELECT rating FROM film GROUP BY rating;
Trình tối ưu hóa của PostgreSQL thường tạo cùng một kế hoạch thực thi. Chọn DISTINCT khi chỉ muốn khử trùng, chọn GROUP BY khi cần hàm tổng hợp đi kèm. → Nhóm dữ liệu - GROUP BY, HAVING, hàm tổng hợp
5. Bài tập
Dùng CSDL dvdrental:
Liệt kê title và length của 10 phim ngắn nhất, kết quả phải ổn định giữa các lần chạy.
Hiển thị họ tên đầy đủ của khách hàng dưới dạng một cột tên "Khách hàng", viết hoa toàn bộ.
Có bao nhiêu mức rating khác nhau trong bảng film? Liệt kê chúng.
Với mỗi rating, tìm phim có thời lượng dài nhất — chỉ dùng DISTINCT ON.
Sắp xếp bảng film theo rental_rate giảm dần nhưng đưa các giá trị NULL (nếu có) lên đầu.
Giải thích vì sao câu sau bị lỗi và sửa lại:
SELECT rental_rate * 2 AS gia_gap_doiFROM filmWHERE gia_gap_doi > 5;
Đáp án
1.
SELECT title, lengthFROM filmORDER BY length ASC, film_idLIMIT 10;
Thêm film_id để phá thế hòa — nhiều phim cùng length = 46.