Đây là nguyên nhân số 1 gây sai số trong báo cáo thống kê.
2.2. NUMERIC / DECIMAL — chính xác tuyệt đối
NUMERIC(precision, scale)-- precision: tổng số chữ số-- scale: số chữ số sau dấu thập phân
NUMERIC(15, 2) -- tối đa 13 chữ số phần nguyên + 2 chữ số thập phânNUMERIC(5, 2) -- 999.99 là lớn nhấtNUMERIC -- không giới hạn (lưu chính xác mọi giá trị nhập vào)
MONEY phụ thuộc lc_monetary của phiên, không chuyển đổi tiền tệ được, và độ chính xác cố định. Tài liệu chính thức PostgreSQL cũng khuyến cáo tránh. Dùng NUMERIC(15,2) + cột currency VARCHAR(3).
3. Kiểu chuỗi
Kiểu
Mô tả
CHAR(n)
Độ dài cố định, đệm khoảng trắng
VARCHAR(n)
Giới hạn n ký tự
VARCHAR
Không giới hạn
TEXT
Không giới hạn
3.1. TEXT vs VARCHAR(n) — hiệu năng giống hệt nhau
Trong PostgreSQL, TEXT và VARCHAR(n) lưu trữ y hệt nhau
Không có khác biệt về tốc độ hay dung lượng. VARCHAR(n) chỉ thêm một phép kiểm tra độ dài.
Điều này khác với SQL Server hay Oracle, nơi TEXT/CLOB là kiểu đặc biệt chậm hơn.
Nên dùng gì?
TEXT khi không có giới hạn nghiệp vụ rõ ràng.
VARCHAR(n) khi giới hạn là một quy tắc thật (mã bưu chính 5 ký tự, ISBN 13 ký tự).
Nếu chỉ muốn “phòng xa”, hãy dùng TEXT CHECK (length(x) <= 255) — sửa CHECK dễ hơn nhiều so với ALTER TYPE.
3.2. CHAR(n) — tránh dùng
CREATE TABLE t (ma CHAR(10));INSERT INTO t VALUES ('ABC');SELECT length(ma), ma || '|' FROM t;-- 3 | 'ABC |' ← có 7 khoảng trắng đệm khi nối chuỗi
CHAR đệm khoảng trắng, gây bất ngờ khi so sánh và nối chuỗi, mà không tiết kiệm dung lượng.
-- Bỏ dấu tiếng Việt (cần extension unaccent)CREATE EXTENSION IF NOT EXISTS unaccent;SELECT unaccent('Nguyễn Văn Ánh'); -- 'Nguyen Van Anh'-- Tìm kiếm không dấu, không phân biệt hoa thườngCREATE INDEX idx_ten_unaccent ON nguoi_dung (lower(unaccent(ho_ten)));SELECT * FROM nguoi_dungWHERE lower(unaccent(ho_ten)) LIKE '%nguyen%';
unaccent không phải IMMUTABLE mặc định
Để dùng trong index, cần bọc trong hàm IMMUTABLE:
CREATE FUNCTION f_unaccent(text) RETURNS text AS$$ SELECT unaccent('unaccent', $1) $$LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE;CREATE INDEX idx_ten ON nguoi_dung (lower(f_unaccent(ho_ten)));
4. BOOLEAN
CREATE TABLE t (dang_hoat_dong BOOLEAN NOT NULL DEFAULT true);
Giá trị chấp nhận:
TRUE
FALSE
TRUE, true, 't', 'true', 'y', 'yes', 'on', '1'
FALSE, false, 'f', 'false', 'n', 'no', 'off', '0'
SELECT * FROM t WHERE dang_hoat_dong; -- gọn hơn = trueSELECT * FROM t WHERE NOT dang_hoat_dong;SELECT * FROM t WHERE dang_hoat_dong IS NOT TRUE; -- gồm cả NULL
BOOLEAN có 3 trạng thái: true, false, NULL. Thêm NOT NULL nếu chỉ muốn 2.
5. Ngày và giờ
Kiểu
Byte
Mô tả
DATE
4
Chỉ ngày
TIME
8
Chỉ giờ
TIMESTAMP
8
Ngày + giờ, không múi giờ
TIMESTAMPTZ
8
Ngày + giờ có múi giờ
INTERVAL
16
Khoảng thời gian
5.1. TIMESTAMPTZ — gần như luôn là lựa chọn đúng
Tên gọi gây hiểu nhầm
TIMESTAMPTZkhông lưu múi giờ. Nó lưu thời điểm tuyệt đối (UTC) và tự chuyển đổi sang múi giờ của phiên khi hiển thị.
TIMESTAMP (không TZ) lưu “chữ số trên đồng hồ” mà không biết đó là đồng hồ ở đâu — hầu như luôn là nguồn gốc của bug.
SET timezone = 'Asia/Ho_Chi_Minh';SELECT now(); -- 2026-07-30 14:30:00+07SET timezone = 'UTC';SELECT now(); -- 2026-07-30 07:30:00+00-- Cùng một thời điểm, hiển thị khác nhau
Chuyển đổi múi giờ:
SELECT now() AT TIME ZONE 'Asia/Ho_Chi_Minh';SELECT '2026-07-30 10:00:00'::timestamp AT TIME ZONE 'UTC';-- Xem danh sách múi giờSELECT * FROM pg_timezone_names WHERE name LIKE 'Asia/H%';
Khi nào dùng TIMESTAMP không TZ? Chỉ khi thời gian thực sự không gắn với thời điểm tuyệt đối: giờ mở cửa cửa hàng (“9:00 sáng giờ địa phương, ở đâu cũng vậy”), lịch phát sóng truyền hình.
5.2. Hàm ngày giờ hay dùng
now() -- thời điểm hiện tại (đầu giao dịch)current_date -- ngày hôm naycurrent_time -- giờ hiện tạiclock_timestamp() -- thời gian thực, thay đổi trong cùng câu lệnh-- Trích xuất thành phầnEXTRACT(YEAR FROM ngay)EXTRACT(MONTH FROM ngay)EXTRACT(DAY FROM ngay)EXTRACT(DOW FROM ngay) -- 0 = Chủ nhật … 6 = Thứ bảyEXTRACT(ISODOW FROM ngay) -- 1 = Thứ hai … 7 = Chủ nhậtEXTRACT(QUARTER FROM ngay)EXTRACT(EPOCH FROM ngay) -- Unix timestamp-- Cắt về đầu kỳ (rất hữu ích khi nhóm)date_trunc('month', ngay) -- 2026-07-01 00:00:00date_trunc('week', ngay) -- đầu tuần (thứ Hai)date_trunc('day', ngay)date_trunc('hour', ngay)-- Định dạngto_char(now(), 'DD/MM/YYYY') -- '30/07/2026'to_char(now(), 'YYYY-MM-DD HH24:MI:SS')to_char(now(), 'Day, DD Month YYYY')-- Phân tích chuỗito_date('30/07/2026', 'DD/MM/YYYY')to_timestamp('30/07/2026 14:30', 'DD/MM/YYYY HH24:MI')-- Tuổiage(timestamp '1990-05-15') -- '36 years 2 mons 15 days'age('2026-07-30', '1990-05-15')
5.3. INTERVAL
SELECT now() + INTERVAL '1 day';SELECT now() - INTERVAL '2 hours 30 minutes';SELECT now() + INTERVAL '3 months';SELECT INTERVAL '1 year 2 months 3 days';-- Cộng số ngày trực tiếp với DATESELECT current_date + 7; -- 7 ngày sauSELECT current_date - DATE '2026-01-01'; -- số ngày chênh lệch (integer)-- Tính khoảng thời gianSELECT return_date - rental_date AS thoi_gian_thueFROM rental WHERE return_date IS NOT NULL LIMIT 5;-- Chuyển interval sang sốSELECT EXTRACT(EPOCH FROM INTERVAL '2 hours') / 3600; -- 2SELECT EXTRACT(DAY FROM (return_date - rental_date)) FROM rental LIMIT 1;
5.4. So sánh khoảng thời gian — nhắc lại
Không dùng BETWEEN với TIMESTAMP
-- ❌ Mất gần trọn ngày 31WHERE payment_date BETWEEN '2007-03-01' AND '2007-03-31'-- ✅ Nửa khoảng mởWHERE payment_date >= '2007-03-01' AND payment_date < '2007-04-01'-- ✅ Hoặc dùng date_truncWHERE date_trunc('month', payment_date) = '2007-03-01'
Lưu ý: date_trunc trong WHERE sẽ vô hiệu hóa index trên payment_date. Với bảng lớn, dạng nửa khoảng mở nhanh hơn nhiều.
CREATE TABLE t ( id UUID PRIMARY KEY DEFAULT gen_random_uuid());
gen_random_uuid() có sẵn từ PostgreSQL 13 (trước đó cần extension pgcrypto).
6.1. UUID vs BIGINT làm khóa chính
BIGINT IDENTITY
UUID
Dung lượng
8 byte
16 byte
Sinh ở client được
❌
✅
Lộ số lượng bản ghi
⚠️ Có (id=1000 → biết có ~1000 dòng)
❌ Không
Hiệu năng index
Tốt (tuần tự)
⚠️ Kém — ngẫu nhiên gây phân mảnh B-tree
Gộp dữ liệu nhiều nguồn
Xung đột
Không xung đột
UUID v7 — giải pháp cho vấn đề phân mảnh
UUID v7 sắp xếp được theo thời gian, nên chèn tuần tự vào index như BIGINT, đồng thời giữ tính duy nhất toàn cục. PostgreSQL 18 có uuidv7() sẵn; phiên bản cũ hơn có thể sinh từ ứng dụng.
7. ARRAY
PostgreSQL cho phép mọi kiểu dữ liệu tạo thành mảng.
CREATE TABLE bai_viet ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, tieu_de TEXT NOT NULL, the TEXT[], diem INT[]);INSERT INTO bai_viet (tieu_de, the, diem)VALUES ('Học PostgreSQL', ARRAY['sql','database','postgres'], ARRAY[9,8,10]), ('Học Python', '{"python","lap-trinh"}', '{7,9}');
7.1. Truy cập và toán tử
SELECT the[1] FROM bai_viet; -- ⚠️ mảng đánh số từ 1SELECT the[1:2] FROM bai_viet; -- lát cắtSELECT array_length(the, 1) FROM bai_viet;SELECT cardinality(the) FROM bai_viet; -- tổng số phần tử-- Tìm kiếmWHERE 'sql' = ANY(the) -- chứa 'sql'WHERE the @> ARRAY['sql','database'] -- chứa TẤT CẢWHERE the && ARRAY['sql','python'] -- chứa ÍT NHẤT MỘTWHERE NOT ('sql' = ANY(the)) -- không chứa-- Thao tácSELECT array_append(the, 'moi') FROM bai_viet;SELECT array_remove(the, 'sql') FROM bai_viet;SELECT array_to_string(the, ', ') FROM bai_viet;SELECT array_position(the, 'database') FROM bai_viet;
7.2. Khai triển mảng thành dòng
SELECT id, tieu_de, unnest(the) AS tung_the FROM bai_viet;-- Cách chuẩn hơn (kiểm soát được join)SELECT b.id, b.tieu_de, tFROM bai_viet b, unnest(b.the) AS t;
7.3. Index cho mảng
CREATE INDEX idx_bai_viet_the ON bai_viet USING GIN (the);-- Bây giờ @>, &&, = ANY() dùng được index
Khi nào không dùng mảng
Mảng tiện nhưng phá vỡ chuẩn hóa. Đừng dùng khi:
Cần khóa ngoại tới từng phần tử.
Cần thêm thuộc tính cho từng phần tử (thời gian gắn thẻ, người gắn).
Cần cập nhật từng phần tử thường xuyên (phải ghi lại cả mảng).
Trong những trường hợp đó, bảng quan hệ nhiều-nhiều vẫn tốt hơn.
8. JSON và JSONB
PostgreSQL có hai kiểu JSON:
Đặc điểm
JSON
JSONB
Lưu trữ
Văn bản nguyên văn
Nhị phân đã phân tích
Dung lượng
Lớn hơn (giữ khoảng trắng)
Nhỏ hơn
Tốc độ ghi
Nhanh hơn
Chậm hơn chút
Tốc độ đọc/truy vấn
Chậm (phân tích mỗi lần)
Nhanh
Index
Chỉ full-text
GIN — mạnh
Giữ thứ tự khóa
✅
❌
Khóa trùng
Giữ giá trị cuối
Loại bỏ
Toán tử phong phú
Hạn chế
Đầy đủ
Gần như luôn dùng JSONB
Chỉ dùng JSON khi bạn cần lưu nguyên văn tài liệu gốc (ví dụ để kiểm tra chữ ký số) hoặc thứ tự khóa quan trọng.
8.1. Lưu đối tượng JSON
CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, properties JSONB);INSERT INTO products(name, properties)VALUES ('Ink Fusion T-Shirt', '{"color": "white", "size": ["S","M","L","XL"]}'), ('ThreadVerse T-Shirt', '{"color": "black", "size": ["S","M","L","XL"]}'), ('Design Dynamo T-Shirt', '{"color": "blue", "size": ["S","M","L","XL"]}')RETURNING *;
Chú ý: JSONB đã sắp xếp lại thứ tự khóa — size đứng trước color dù ta nhập ngược lại.
8.2. Toán tử trích xuất
-- -> trả về JSON (giữ dấu nháy kép)SELECT id, name, properties -> 'color' AS color FROM products;
id | name | color----+-----------------------+--------- 1 | Ink Fusion T-Shirt | "white" 2 | ThreadVerse T-Shirt | "black" 3 | Design Dynamo T-Shirt | "blue"
-- ->> trả về TEXT (bỏ dấu nháy kép)SELECT id, name, properties ->> 'color' AS color FROM products;
id | name | color----+-----------------------+------- 1 | Ink Fusion T-Shirt | white 2 | ThreadVerse T-Shirt | black 3 | Design Dynamo T-Shirt | blue
Lọc theo giá trị JSON:
SELECT id, name, properties ->> 'color' AS colorFROM productsWHERE properties ->> 'color' IN ('black', 'white');
id | name | color----+---------------------+------- 1 | Ink Fusion T-Shirt | white 2 | ThreadVerse T-Shirt | black
8.3. Bảng toán tử đầy đủ
Toán tử
Ý nghĩa
Ví dụ
->
Lấy trường, trả về JSON
data -> 'ten'
->>
Lấy trường, trả về text
data ->> 'ten'
#>
Lấy theo đường dẫn, trả JSON
data #> '{dia_chi,thanh_pho}'
#>>
Lấy theo đường dẫn, trả text
data #>> '{dia_chi,thanh_pho}'
@>
Chứa
data @> '{"mau":"do"}'
<@
Bị chứa trong
?
Có khóa này không
data ? 'email'
?|
Có ít nhất một khóa
data ?| ARRAY['a','b']
?&
Có tất cả các khóa
data ?& ARRAY['a','b']
||
Nối/gộp
data || '{"moi":1}'
-
Xóa khóa
data - 'khoa'
#-
Xóa theo đường dẫn
data #- '{a,b}'
@?
JSONPath tồn tại
data @? '$.items[*] ? (@.gia > 100)'
8.4. Lưu mảng JSON
CREATE TABLE contacts ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, phones JSONB);INSERT INTO contacts(name, phones)VALUES ('John Doe','["408-111-2222", "408-111-2223"]'), ('Jane Doe','["212-111-2222", "212-111-2223"]');-- Lấy phần tử theo chỉ số (mảng JSON đánh số từ 0!)SELECT name, phones ->> 0 AS "so_chinh" FROM contacts;
name | so_chinh----------+-------------- John Doe | 408-111-2222 Jane Doe | 212-111-2222
Mảng JSON đánh số từ 0, mảng PostgreSQL đánh số từ 1
Nguồn nhầm lẫn thường gặp khi trộn hai loại.
8.5. Hàm JSON hữu ích
-- Khai triển thành dòngSELECT jsonb_array_elements('[1,2,3]'::jsonb);SELECT * FROM jsonb_each('{"a":1,"b":2}'::jsonb);SELECT jsonb_object_keys('{"a":1,"b":2}'::jsonb);-- Tạo JSON từ dữ liệu quan hệSELECT jsonb_build_object('id', film_id, 'ten', title) FROM film LIMIT 3;SELECT jsonb_agg(jsonb_build_object('id', film_id, 'ten', title))FROM film LIMIT 3;SELECT row_to_json(f) FROM (SELECT film_id, title FROM film LIMIT 3) f;-- Cập nhật một trườngUPDATE productsSET properties = jsonb_set(properties, '{color}', '"red"')WHERE id = 1;-- Thêm trường (gộp)UPDATE productsSET properties = properties || '{"gia": 199000}'::jsonbWHERE id = 1;-- Xóa trườngUPDATE products SET properties = properties - 'gia' WHERE id = 1;-- Chuyển JSON thành bảngSELECT * FROM jsonb_to_recordset('[{"a":1,"b":"x"},{"a":2,"b":"y"}]'::jsonb) AS t(a INT, b TEXT);
8.6. Index cho JSONB
-- GIN mặc định: hỗ trợ @>, ?, ?|, ?&CREATE INDEX idx_props ON products USING GIN (properties);-- GIN jsonb_path_ops: nhỏ hơn, nhanh hơn, chỉ hỗ trợ @>CREATE INDEX idx_props_path ON products USING GIN (properties jsonb_path_ops);-- B-tree trên một trường cụ thể (nhanh nhất cho trường đó)CREATE INDEX idx_color ON products ((properties ->> 'color'));
->> không dùng được index GIN
WHERE properties ->> 'color' = 'red' -- ❌ không dùng GINWHERE properties @> '{"color":"red"}' -- ✅ dùng được GIN
Nếu bạn thường lọc bằng ->>, hãy tạo B-tree trên biểu thức đó.
8.7. Khi nào dùng JSONB?
Nên dùng khi:
Thuộc tính thay đổi theo từng bản ghi (thông số kỹ thuật sản phẩm).
Lưu payload từ API bên ngoài.
Cấu hình, metadata, log.
Schema chưa ổn định trong giai đoạn phát triển.
Không nên khi:
Dữ liệu có cấu trúc rõ ràng, ổn định → dùng cột thường.
Cần khóa ngoại, ràng buộc, hoặc join thường xuyên trên trường đó.
Cần cập nhật một trường nhỏ liên tục — mỗi lần update ghi lại toàn bộ JSONB.
Mẫu lai thường tốt nhất
CREATE TABLE san_pham ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ten TEXT NOT NULL, -- cột thường: hay dùng, cần index gia NUMERIC(12,2) NOT NULL, -- cột thường: cần tính toán thuoc_tinh JSONB -- JSONB: thông số linh hoạt);
9. ENUM
CREATE TYPE trang_thai_don AS ENUM ('cho', 'dang_giao', 'hoan_thanh', 'huy');CREATE TABLE don_hang ( id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, trang_thai trang_thai_don NOT NULL DEFAULT 'cho');
Ưu điểm: lưu trữ nhỏ (4 byte), sắp xếp theo thứ tự khai báo (không phải bảng chữ cái).
SELECT * FROM don_hang ORDER BY trang_thai;-- cho → dang_giao → hoan_thanh → huy
9.1. Sửa đổi enum
ALTER TYPE trang_thai_don ADD VALUE 'hoan_tien';ALTER TYPE trang_thai_don ADD VALUE 'dang_dong_goi' BEFORE 'dang_giao';ALTER TYPE trang_thai_don RENAME VALUE 'cho' TO 'cho_xu_ly';
Không thể xóa giá trị khỏi enum
Muốn xóa, bạn phải tạo type mới, chuyển đổi cột, xóa type cũ — thao tác đòi hỏi khóa bảng:
ALTER TYPE trang_thai_don RENAME TO trang_thai_don_cu;CREATE TYPE trang_thai_don AS ENUM ('cho', 'hoan_thanh');ALTER TABLE don_hang ALTER COLUMN trang_thai TYPE trang_thai_don USING trang_thai::text::trang_thai_don;DROP TYPE trang_thai_don_cu;
9.2. Ba lựa chọn thay thế
Cách
Ưu
Nhược
ENUM
Nhỏ gọn, thứ tự tùy chỉnh
Khó xóa giá trị, khó thêm metadata
TEXT + CHECK
Dễ sửa (DROP/ADD CONSTRAINT)
Tốn dung lượng hơn
Bảng tra cứu + FK
Linh hoạt nhất, thêm được mô tả/thứ tự/màu sắc
Cần join
Thực tế: dùng bảng tra cứu khi danh sách có thể thay đổi hoặc cần thuộc tính kèm theo; dùng TEXT + CHECK cho danh sách nhỏ, ổn định.
10. Các kiểu khác
10.1. hstore — key-value phẳng
CREATE EXTENSION IF NOT EXISTS hstore;CREATE TABLE t (thuoc_tinh HSTORE);INSERT INTO t VALUES ('"mau" => "do", "size" => "L"');SELECT thuoc_tinh -> 'mau' FROM t;SELECT * FROM t WHERE thuoc_tinh ? 'mau';
Về cơ bản JSONB đã thay thế hstore (chỉ hỗ trợ chuỗi phẳng, không lồng nhau). Dùng hstore chỉ khi bảo trì code cũ.
10.2. Kiểu khoảng (range)
CREATE TABLE gia_khuyen_mai ( san_pham_id INT, khoang_ngay DATERANGE, gia NUMERIC(12,2));INSERT INTO gia_khuyen_mai VALUES (1, '[2026-08-01, 2026-08-31]', 99000);-- Kiểm tra chứaSELECT * FROM gia_khuyen_mai WHERE khoang_ngay @> current_date;-- Kiểm tra giao nhauSELECT * FROM gia_khuyen_mai WHERE khoang_ngay && '[2026-08-15,2026-09-15]'::daterange;
Các kiểu: INT4RANGE, INT8RANGE, NUMRANGE, TSRANGE, TSTZRANGE, DATERANGE.
CREATE TABLE tep (id INT, noi_dung BYTEA);INSERT INTO tep VALUES (1, '\x48656c6c6f');SELECT encode(noi_dung, 'hex'), encode(noi_dung, 'base64') FROM tep;
Đừng lưu file lớn trong CSDL
Lưu file trên object storage (S3, MinIO) và chỉ giữ đường dẫn trong CSDL. Lưu ảnh/video trong BYTEA làm phình backup, chậm replication, tốn bộ nhớ đệm.
10.4. XML
CREATE TABLE t (du_lieu XML);INSERT INTO t VALUES ('<sach><ten>PostgreSQL</ten></sach>');SELECT xpath('/sach/ten/text()', du_lieu) FROM t;
Nếu bạn có lựa chọn, JSONB tốt hơn về mọi mặt.
10.5. Kiểu tự định nghĩa
DOMAIN — kiểu có sẵn + ràng buộc:
CREATE DOMAIN email_vn AS VARCHAR(255) CHECK (VALUE ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');CREATE DOMAIN so_duong AS NUMERIC CHECK (VALUE > 0);CREATE TABLE t ( email email_vn NOT NULL, gia so_duong NOT NULL);
Ràng buộc được tái sử dụng ở mọi bảng — nhất quán hơn nhiều so với chép CHECK khắp nơi.
TYPE — kiểu tổng hợp:
CREATE TYPE dia_chi AS ( duong TEXT, phuong TEXT, quan TEXT, thanh_pho TEXT);CREATE TABLE khach (id INT, noi_o dia_chi);INSERT INTO khach VALUES (1, ROW('123 Lê Lợi','Bến Nghé','Quận 1','TP.HCM'));SELECT (noi_o).thanh_pho FROM khach;
Dấu ngoặc quanh (noi_o) là bắt buộc khi truy cập trường.
11. Bài tập
Thiết kế bảng giao_dich lưu tiền tệ chính xác, thời điểm có múi giờ, và mã tiền tệ.
Chứng minh sự khác biệt giữa NUMERIC và FLOAT khi cộng 0.1 + 0.2 mười lần.
Tạo bảng bai_viet với cột thẻ dạng mảng; tìm bài viết chứa cả 'sql' và 'postgres'.
Tạo bảng sản phẩm với JSONB; tìm sản phẩm có color = 'blue' bằng cả ->> và @>.
So sánh EXPLAIN của hai cách lọc ở bài 4 khi đã tạo index GIN.
Tạo DOMAIN cho số điện thoại Việt Nam và dùng nó trong 2 bảng khác nhau.
Nhóm doanh thu payment theo tuần bằng date_trunc.
Cho biết sự khác nhau giữa now() và clock_timestamp() trong cùng một câu lệnh.
Đáp án
1.
CREATE TABLE giao_dich ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, so_tien NUMERIC(15,2) NOT NULL CHECK (so_tien <> 0), tien_te CHAR(3) NOT NULL DEFAULT 'VND' CHECK (tien_te ~ '^[A-Z]{3}$'), thoi_diem TIMESTAMPTZ NOT NULL DEFAULT now(), mo_ta TEXT);
2.
SELECT sum(0.1::float8 + 0.2::float8) FROM generate_series(1,10);-- 3.0000000000000004SELECT sum(0.1::numeric + 0.2::numeric) FROM generate_series(1,10);-- 3.0
3.
SELECT * FROM bai_viet WHERE the @> ARRAY['sql','postgres'];
@> là “chứa tất cả”; && là “chứa ít nhất một”.
4.
SELECT * FROM products WHERE properties ->> 'color' = 'blue';SELECT * FROM products WHERE properties @> '{"color":"blue"}';
5.
CREATE INDEX idx_props ON products USING GIN (properties);EXPLAIN ANALYZE SELECT * FROM products WHERE properties @> '{"color":"blue"}';-- Bitmap Index Scan on idx_props ✅EXPLAIN ANALYZE SELECT * FROM products WHERE properties ->> 'color' = 'blue';-- Seq Scan ❌
Muốn ->> dùng index: CREATE INDEX ON products ((properties ->> 'color'));
6.
CREATE DOMAIN sdt_vn AS VARCHAR(15) CHECK (VALUE ~ '^0[35789][0-9]{8}$');CREATE TABLE khach (id INT PRIMARY KEY, sdt sdt_vn);CREATE TABLE nhan_vien (id INT PRIMARY KEY, sdt sdt_vn NOT NULL);INSERT INTO khach VALUES (1, '0901234567'); -- OKINSERT INTO khach VALUES (2, '123'); -- ERROR: violates check
7.
SELECT date_trunc('week', payment_date)::date AS tuan, round(sum(amount), 2) AS doanh_thu, count(*) AS so_giao_dichFROM paymentGROUP BY tuanORDER BY tuan;