Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

SELECT nâng cao: column expressions, aliases, DISTINCT

SQL and Data Analysis cho BA Bài 6/60

Mở đầu — vì sao bài này quan trọng

Ở Bài 1, bạn đã làm quen với SELECT cơ bản: chọn cột, lọc bằng WHERE, sắp xếp bằng ORDER BY. Nhưng trong thực tế công việc của một Business Analyst (BA), một câu SELECT chỉ "lấy nguyên cột ra" thường là chưa đủ. Khách hàng nội bộ — phòng kế toán, phòng marketing, ban giám đốc — không quan tâm tới dữ liệu thô. Họ muốn thấy giá đã bao gồm VAT, lợi nhuận gộp trên mỗi đơn, tỷ lệ phần trăm chiết khấu, hay đơn giản là một danh sách không trùng lặp các tỉnh thành mà công ty đang phục vụ.

Đây chính là nơi SELECT nâng cao phát huy sức mạnh. Thay vì kéo dữ liệu thô về Excel rồi mới gõ công thức tính tay — vừa chậm, vừa dễ sai, vừa khó lặp lại — bạn để chính database tính toán ngay trong câu truy vấn. Kết quả trả về đã sạch, đã đặt tên rõ ràng, đã loại trùng, sẵn sàng đưa thẳng vào báo cáo hay dashboard.

Bài này tập trung vào ba kỹ năng nền tảng nhưng cực kỳ thường dùng: column expressions (tính toán ngay trong SELECT), aliases (đặt tên lại cho cột và bảng), và DISTINCT (loại bỏ dòng trùng). Đây là bộ ba mà gần như mọi báo cáo SQL của BA đều đụng tới hàng ngày. Nắm chắc nó, bạn sẽ viết query gọn hơn, đọc dễ hơn, và quan trọng nhất là ít sai số liệu hơn khi trình bày với sếp.

Khái niệm cốt lõi

Column expressions — tính toán ngay trong SELECT

Trong mệnh đề SELECT, bạn không bắt buộc chỉ liệt kê tên cột. Bạn có thể đặt vào đó cả một biểu thức (expression): phép tính số học, ghép chuỗi, hay bất kỳ công thức nào kết hợp các cột với nhau và với hằng số.

Ví dụ với một bảng products có các cột product_name, price, cost:

SELECT
  product_name,
  price,
  price * 1.1            AS price_with_vat,
  price - cost           AS gross_profit,
  (price - cost) / price AS margin_ratio
FROM products;

Ở đây có ba biểu thức tính toán:

  • price * 1.1 — giá đã cộng 10% VAT.
  • price - cost — lợi nhuận gộp tuyệt đối trên mỗi sản phẩm.
  • (price - cost) / price — tỷ suất lợi nhuận gộp (margin), một con số tỷ lệ.
Các phép toán số học cơ bản đều dùng được: cộng +, trừ -, nhân *, chia /, và lấy phần dư % (modulo). Database tính từng dòng một và trả về kết quả như thể đó là một cột thật.

Một điểm cần khắc cốt ghi tâm với BA: thứ tự ưu tiên phép toán. SQL tuân theo quy tắc toán học thông thường — nhân chia trước, cộng trừ sau. Nếu bạn muốn ép thứ tự khác, hãy dùng dấu ngoặc đơn. So sánh hai biểu thức sau:

SELECT price - cost / price  FROM products;   -- chia trước: price - (cost/price)
SELECT (price - cost) / price FROM products;  -- trừ trước rồi mới chia

Hai dòng này cho kết quả hoàn toàn khác nhau. Rất nhiều báo cáo sai margin chỉ vì thiếu một cặp ngoặc. Khi tính tỷ lệ phần trăm, hãy luôn bao biểu thức trừ trong ngoặc trước khi chia.

Aliases — đặt tên lại cho cột và bảng

Khi bạn viết price 1.1, nếu không làm gì thêm, tên cột trả về sẽ là chính chuỗi price 1.1 — vừa xấu, vừa khó dùng khi kết nối sang Power BI hay Google Sheets. Alias giải quyết việc này bằng cách gán cho biểu thức (hoặc cột) một cái tên mới, dễ đọc.

Cú pháp dùng từ khóa AS:

SELECT price * 1.1 AS price_with_vat
FROM products;

Từ khóa AS về kỹ thuật là tùy chọn — price * 1.1 price_with_vat cũng chạy — nhưng luôn viết AS để câu query rõ ràng, tránh nhầm lẫn (đặc biệt khi liệt kê nhiều cột, thiếu dấu phẩy mà lại không có AS sẽ ra lỗi khó hiểu).

Nếu tên alias có khoảng trắng hoặc tiếng Việt có dấu, bạn cần bọc nó trong dấu nháy phù hợp với hệ quản trị:

  • MySQL: dấu backtick ` Giá sau VAT
  • PostgreSQL và chuẩn SQL: dấu nháy kép "Giá sau VAT"
SELECT price * 1.1 AS Giá đã có VAT   -- MySQL
FROM products;

Alias không chỉ dùng cho cột mà còn cho bảng (table alias). Khi query đụng nhiều bảng, đặt bí danh ngắn giúp viết gọn hơn nhiều:

SELECT p.product_name, p.price
FROM products AS p;

Một lưu ý kỹ thuật quan trọng: trong hầu hết hệ quản trị, bạn không thể dùng column alias ngay trong mệnh đề WHERE của cùng câu query đó, vì WHERE được xử lý trước SELECT. Câu sau sẽ báo lỗi trên PostgreSQL:

SELECT price * 1.1 AS price_with_vat
FROM products
WHERE price_with_vat > 100000;   -- LỖI: chưa biết price_with_vat

Bạn phải lặp lại biểu thức: WHERE price * 1.1 > 100000. Tuy nhiên, alias lại dùng được trong ORDER BYGROUP BY ở phần lớn hệ quản trị — đây là điểm khác biệt cần nhớ.

DISTINCT — loại bỏ dòng trùng lặp

DISTINCT loại bỏ các dòng kết quả trùng nhau, chỉ giữ lại các giá trị duy nhất. Đây là công cụ tối quan trọng khi BA cần trả lời những câu hỏi kiểu "có bao nhiêu loại...", "danh sách các... khác nhau".

SELECT DISTINCT city
FROM customers;

Câu này trả về danh sách các thành phố không trùng trong bảng khách hàng. Nếu có 50.000 khách ở Hà Nội, Hà Nội vẫn chỉ xuất hiện một lần.

Điểm dễ hiểu nhầm nhất: DISTINCT áp dụng cho toàn bộ tổ hợp các cột được liệt kê, không phải riêng cột đầu tiên.

SELECT DISTINCT city, district
FROM customers;

Câu này trả về các cặp (thành phố, quận/huyện) duy nhất. "Hà Nội – Cầu Giấy" và "Hà Nội – Đống Đa" là hai dòng khác nhau và đều được giữ. DISTINCT luôn đứng ngay sau SELECT và chi phối tất cả các cột phía sau nó — bạn không thể chỉ "distinct" một cột giữa chừng.

Tình huống thực tế

Tình huống 1 — Báo cáo giá niêm yết kèm VAT cho sàn Tiki

Bạn là BA tại một nhà bán hàng đang kinh doanh trên Tiki. Phòng kinh doanh yêu cầu một bảng giá để in lên brochure: hiển thị tên sản phẩm, giá gốc, giá đã gồm VAT 10%, và mức lợi nhuận gộp để nội bộ tham khảo. Bảng productsprice (giá bán chưa VAT) và cost (giá vốn).

SELECT
  product_name                            AS "Tên sản phẩm",
  price                                   AS "Giá chưa VAT",
  ROUND(price * 1.1, 0)                   AS "Giá đã gồm VAT",
  price - cost                            AS "Lợi nhuận gộp",
  ROUND((price - cost) / price * 100, 1)  AS "Margin (%)"
FROM products
ORDER BY "Margin (%)" DESC;

Với một sản phẩm có price = 250000cost = 180000, kết quả là: giá gồm VAT = 275.000đ, lợi nhuận gộp = 70.000đ, margin = 28%. Lưu ý mình dùng ROUND để cắt số lẻ — không ai muốn thấy "274999.9999" trên brochure. Và mình tận dụng được việc ORDER BY chấp nhận alias để sắp xếp theo margin giảm dần, đưa sản phẩm lời nhất lên đầu.

Bài học: Thay vì xuất ba cột thô rồi tính ba cột công thức trong Excel cho hàng nghìn sản phẩm, một câu query làm sạch mọi thứ. Khi giá thay đổi tháng sau, bạn chỉ chạy lại đúng câu đó — không lo công thức Excel bị kéo lệch dòng.

Tình huống 2 — Đếm vùng phủ giao hàng của một startup giao đồ ăn

Một startup giao đồ ăn ở TP.HCM (kiểu Baemin/ShopeeFood) muốn biết: "Hiện chúng ta đã có tài xế hoạt động ở bao nhiêu quận khác nhau?" và "Liệt kê các tổ hợp quận – phường đang có đơn." Bảng orders có cột districtward.

-- Đếm số quận khác nhau có phát sinh đơn
SELECT COUNT(DISTINCT district) AS so_quan_hoat_dong
FROM orders;

-- Liệt kê các cặp quận – phường duy nhất SELECT DISTINCT district, ward FROM orders ORDER BY district, ward;

Câu đầu kết hợp DISTINCT bên trong COUNT — một pattern cực kỳ phổ biến: COUNT(DISTINCT cột) đếm số giá trị duy nhất, khác hẳn COUNT(cột) vốn đếm tất cả các dòng. Nếu Quận 1 có 5.000 đơn, COUNT(district) đếm cả 5.000, còn COUNT(DISTINCT district) chỉ tính Quận 1 là một.

Bài học: Phân biệt rõ "đếm số dòng" và "đếm số giá trị khác nhau". Sếp hỏi "bao nhiêu quận" mà bạn trả lời bằng tổng số đơn thì lệch hoàn toàn. COUNT(DISTINCT ...) là câu trả lời đúng cho "bao nhiêu loại/bao nhiêu khác nhau".

Tình huống 3 — Ghép chuỗi tạo nhãn hiển thị cho dashboard fintech

BA tại một ví điện tử (kiểu MoMo/ZaloPay) cần tạo một cột "nhãn giao dịch" để hiển thị trên dashboard nội bộ, dạng "NẠP TIỀN – 500.000đ" hay "CHUYỂN KHOẢN – 1.200.000đ". Bảng transactionstxn_typeamount.

SELECT
  txn_id,
  UPPER(txn_type) || ' – ' || FORMAT(amount, 0) || 'đ' AS nhan_hien_thi
FROM transactions;

Trong PostgreSQL, toán tử ghép chuỗi là ||. Trong MySQL, bạn dùng hàm CONCAT(...):

SELECT
  txn_id,
  CONCAT(UPPER(txn_type), ' – ', FORMAT(amount, 0), 'đ') AS nhan_hien_thi
FROM transactions;

Đây là một column expression dạng chuỗi: kết hợp giá trị cột, hàm xử lý (UPPER viết hoa, FORMAT thêm dấu phân cách hàng nghìn) và hằng chuỗi (' – ', 'đ') thành một nhãn gọn gàng.

Bài học: Expression không chỉ để tính số. Ghép chuỗi giúp BA tạo ra những nhãn thân thiện ngay trong SQL, đỡ phải xử lý hậu kỳ. Lưu ý sự khác biệt cú pháp giữa PostgreSQL (||) và MySQL (CONCAT) — đây là một trong những điểm khiến query "copy y nguyên" giữa hai hệ thường gãy.

Hướng dẫn từng bước

Khi cần viết một câu SELECT nâng cao, hãy đi theo trình tự sau để tránh rối:

  • Xác định câu hỏi nghiệp vụ trước. Bạn cần "giá sau VAT", "margin %", hay "danh sách quận duy nhất"? Viết câu hỏi ra giấy bằng tiếng Việt trước khi gõ SQL.
  • Liệt kê các cột gốc cần dùng. Mở bảng, xác định đúng tên cột nguồn (price, cost, district...). Sai tên cột là lỗi phổ biến nhất.
  • Dựng biểu thức tính toán. Viết công thức, đặc biệt chú ý ngoặc đơn cho các phép tính tỷ lệ. Tự nhẩm: "nhân chia trước, cộng trừ sau — mình có cần ngoặc không?"
  • Gán alias rõ nghĩa cho mọi biểu thức. Mọi cột tính toán đều phải có AS tên_rõ_ràng. Đừng để cột vô danh lọt vào báo cáo.
  • Cân nhắc DISTINCT. Câu hỏi có chứa "các ... khác nhau", "không trùng", "bao nhiêu loại" không? Nếu có, thêm DISTINCT ngay sau SELECT, hoặc dùng COUNT(DISTINCT ...).
  • Làm tròn và định dạng nếu là số tiền. Dùng ROUND cho số thập phân, để báo cáo trông chuyên nghiệp.
  • Chạy thử với LIMIT nhỏ. Thêm LIMIT 10 để xem nhanh kết quả mẫu, kiểm tra công thức đúng chưa, rồi mới bỏ ra để chạy toàn bộ.

Lỗi thường gặp & mẹo

Quên ngoặc khi tính tỷ lệ. price - cost / price không bằng (price - cost) / price. Đây là lỗi làm sai margin nhiều nhất. Mẹo: hễ tính phần trăm là phản xạ bọc tử số trong ngoặc.

Chia cho 0 hoặc cho NULL. Nếu có dòng price = 0, biểu thức / price sẽ lỗi hoặc trả NULL. Trong báo cáo thật, hãy lường trước (bài về NULLIF/COALESCE sẽ xử lý sâu phần này — ở đây chỉ cần ý thức rủi ro).

Tưởng DISTINCT chỉ áp lên một cột. SELECT DISTINCT city, district lọc trùng trên cặp (city, district), không phải riêng city. Nếu bạn thật sự chỉ muốn các city duy nhất thì đừng kèm thêm cột nào khác.

Dùng alias trong WHERE. Nhớ rằng WHERE chạy trước SELECT, nên alias cột chưa tồn tại ở đó. Phải lặp lại biểu thức trong WHERE, nhưng được phép dùng alias trong ORDER BY.

Nhầm cú pháp ghép chuỗi giữa hai hệ. PostgreSQL dùng ||, MySQL dùng CONCAT(). Đừng bê nguyên query giữa hai môi trường mà không kiểm tra.

Mẹo định dạng số tiền VN: kết hợp ROUND để bỏ số lẻ và FORMAT (hoặc TO_CHAR trong PostgreSQL) để thêm dấu phân cách hàng nghìn, giúp báo cáo dễ đọc với người dùng Việt Nam.

Mẹo đặt tên alias: dùng tên không dấu, viết liền hoặc gạch dưới (gia_sau_vat) khi cột sẽ chảy sang Power BI/Sheets để tránh lỗi mã hóa; chỉ dùng tên tiếng Việt có dấu (trong ngoặc) khi kết quả hiển thị trực tiếp cho người đọc.

Bài tập thực hành

Giả sử bạn có bảng order_items với các cột: item_name, quantity, unit_price, discount_rate (tỷ lệ chiết khấu dạng thập phân, ví dụ 0.15 nghĩa là giảm 15%), và category.

  • Viết câu SELECT trả về item_name, quantity, unit_price, và một cột thanh_tien = số lượng nhân đơn giá.
  • Bổ sung cột thanh_tien_sau_giam áp dụng chiết khấu: thành tiền nhân với (1 trừ tỷ lệ chiết khấu). Chú ý đặt ngoặc đúng.
  • Thêm cột gia_co_vat = thành tiền sau giảm cộng thêm 8% VAT, làm tròn 0 chữ số thập phân, đặt alias rõ nghĩa.
  • Viết một câu query liệt kê tất cả các category khác nhau đang có trong bảng.
  • Viết một câu query đếm xem có bao nhiêu category khác nhau.
  • (Nâng cao) Tạo một cột nhãn ghép chuỗi dạng "Áo thun – SL: 3" bằng cách kết hợp item_namequantity (thử cả hai cú pháp PostgreSQL và MySQL).
Gợi ý kiểm tra: chạy với LIMIT 5 trước, đối chiếu một dòng bằng máy tính tay để chắc công thức đúng.

Tóm tắt

Bài này trang bị cho bạn bộ ba kỹ năng SELECT nâng cao mà BA dùng hằng ngày:

  • Column expressions cho phép tính toán ngay trong câu query — số học (+ - * /), tỷ lệ, ghép chuỗi — để dữ liệu trả về đã sẵn sàng cho báo cáo, thay vì tính tay trong Excel. Luôn nhớ thứ tự ưu tiên phép toán và dùng ngoặc cho các phép tính tỷ lệ.
  • Aliases (AS) đặt tên rõ ràng cho cột tính toán và bí danh ngắn cho bảng. Alias dùng được trong ORDER BY/GROUP BY nhưng không dùng được trong WHERE.
  • DISTINCT loại bỏ dòng trùng, áp dụng cho toàn bộ tổ hợp cột được liệt kê; kết hợp COUNT(DISTINCT ...) để đếm số giá trị duy nhất — câu trả lời chuẩn cho "bao nhiêu loại khác nhau".
Nắm chắc ba công cụ này, bạn sẽ viết query gọn hơn, kết quả sạch hơn, và quan trọng nhất: ít sai số liệu hơn khi trình bày trước sếp. Ở các bài tiếp theo, chúng ta sẽ đào sâu hơn vào WHERE chuyên sâu và pattern matching — nhưng nền tảng SELECT` vững vàng từ bài này sẽ theo bạn suốt cả khóa.