Menu
ESC

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

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

Đang tải...

Views: simplify queries cho BA và user

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

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

Hãy tưởng tượng bạn là một BA tại một công ty thương mại điện tử Việt Nam. Mỗi sáng thứ Hai, sếp marketing nhắn: "Cho chị danh sách khách hàng đang active ở Hà Nội và TP.HCM nhé." Bạn mở SQL, viết một query dài 30 dòng với đủ JOIN, WHERE lọc trạng thái, lọc thành phố, lọc ngày mua hàng gần nhất. Tuần sau, sếp lại hỏi câu tương tự nhưng thêm "doanh thu 90 ngày của họ". Bạn lại copy query cũ, sửa, dán. Rồi đến tháng thứ ba, bạn phát hiện logic định nghĩa "active" bị sai ở một chỗ — và bạn nhận ra mình đã copy cái query sai đó vào 12 file khác nhau.

Đây chính là nỗi đau mà View sinh ra để giải quyết. View cho phép bạn lưu lại một câu SELECT phức tạp dưới một cái tên gọn gàng, sau đó dùng nó như một bảng bình thường. Logic nghiệp vụ được định nghĩa một lần, một chỗ — ai cũng dùng chung, sửa một nơi là sửa toàn bộ.

Với vai trò BA, View còn quan trọng hơn nữa: nó là cây cầu giữa bạn và những người không biết SQL. Khi bạn tạo một View tên khach_hang_active, người làm marketing chỉ cần gõ SELECT * FROM khach_hang_active là có dữ liệu sạch, đúng định nghĩa, không cần hiểu JOIN hay WHERE. View biến SQL phức tạp thành thứ ai cũng "self-serve" được. Đây là bài học về việc đóng gói tri thức nghiệp vụ thành công cụ tái sử dụng — một trong những kỹ năng phân biệt BA giỏi với người chỉ biết gõ query.

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

View là gì — "câu SELECT được đặt tên"

Về bản chất, View là một câu lệnh SELECT được lưu trong database dưới một cái tên. Khi bạn truy vấn View, database sẽ chạy ngầm câu SELECT đó rồi trả kết quả cho bạn. View không lưu dữ liệu — nó chỉ lưu câu query. Dữ liệu vẫn nằm ở các bảng gốc; View chỉ là một "ống kính" nhìn vào chúng theo cách bạn đã định nghĩa.

Cú pháp tạo View cơ bản:

CREATE VIEW active_customers AS
SELECT id, name, email, city, registered_at
FROM customers
WHERE status = 'active'
  AND deleted_at IS NULL;

Sau khi tạo xong, bạn dùng nó y hệt một bảng:

SELECT * FROM active_customers WHERE city = 'Hà Nội';

SELECT COUNT(*) FROM active_customers;

Database sẽ tự động "ghép" điều kiện bạn thêm vào (WHERE city = 'Hà Nội') với logic gốc trong View. Bạn không phải nhớ lại định nghĩa "active" là gì — nó đã được khóa cứng trong View rồi.

View khác bảng (table) ở điểm nào

Đây là điểm BA hay nhầm, nên cần phân biệt rõ:

  • Bảng lưu dữ liệu thật trên đĩa. Bạn INSERT vào bảng thì dữ liệu nằm đó.
  • View không lưu dữ liệu. Mỗi lần bạn SELECT từ View, query gốc chạy lại từ đầu, lấy dữ liệu mới nhất từ các bảng gốc.
Hệ quả thực tế: dữ liệu trong View luôn tươi mới. Nếu hôm nay có thêm 50 khách hàng active mới, ngày mai bạn SELECT từ active_customers sẽ thấy ngay 50 người đó — không cần làm gì thêm. Đây là khác biệt cốt lõi giữa View và một "bảng tạm" mà ai đó tạo ra rồi quên cập nhật.

Đánh đổi là: vì query chạy lại mỗi lần, nếu logic gốc nặng (JOIN nhiều bảng lớn), mỗi lần SELECT từ View sẽ tốn thời gian tương đương chạy query đầy đủ. View không tự động làm query nhanh hơn — nó làm query gọn hơn về mặt viết, không phải nhanh hơn về mặt chạy.

Ba lý do BA nên dùng View

1. Đóng gói logic phức tạp. Một định nghĩa nghiệp vụ như "đơn hàng hợp lệ" (đã thanh toán, chưa hủy, không phải đơn test) có thể là 5 điều kiện. Gói vào View valid_orders, mọi báo cáo doanh thu đều dựa trên đó. Khi định nghĩa thay đổi, sửa một View là xong.

2. Tạo lớp self-serve cho người không biết SQL. Bạn dựng sẵn các View "sạch" với tên dễ hiểu bằng tiếng Việt, người dùng nghiệp vụ chỉ cần SELECT * FROM là dùng được, không lo viết sai JOIN.

3. Kiểm soát truy cập và che dữ liệu nhạy cảm. Bạn có thể cấp quyền cho người dùng đọc View thay vì đọc bảng gốc. View chỉ chọn ra những cột an toàn — số CMND, lương, số tài khoản nằm trong bảng gốc nhưng không lọt ra View.

Tình huống thực tế

Tình huống 1: Tiki và định nghĩa "đơn hàng hợp lệ"

Một BA tại sàn thương mại điện tử kiểu Tiki gặp vấn đề kinh điển: mỗi phòng ban tính doanh thu một kiểu. Phòng tài chính tính cả đơn đã hoàn tiền, phòng vận hành bỏ đơn hủy nhưng giữ đơn test của nội bộ, phòng marketing thì tính luôn cả đơn chưa thanh toán. Cuối quý, ba con số doanh thu lệch nhau gần 8%, và cả buổi họp dành để tranh cãi "số nào đúng".

BA quyết định chấm dứt mớ hỗn độn bằng một View thống nhất:

CREATE VIEW valid_orders AS
SELECT
    o.order_id,
    o.customer_id,
    o.order_date,
    o.total_amount,
    o.city
FROM orders o
WHERE o.status = 'completed'
  AND o.is_test = FALSE
  AND o.refunded_at IS NULL;

Từ đó, mọi báo cáo doanh thu đều bắt đầu bằng FROM valid_orders. Phòng tài chính, vận hành, marketing đều dùng chung một định nghĩa. Khi ban lãnh đạo quyết định "đơn hoàn tiền một phần vẫn tính phần không hoàn", BA chỉ sửa đúng một dòng trong View — và toàn bộ 15 dashboard cập nhật theo ngay lập tức.

Bài học: View là nơi "đóng dấu" định nghĩa nghiệp vụ. Khi mọi người dùng chung một View, tranh cãi "số nào đúng" biến mất, vì chỉ có một nguồn sự thật duy nhất.

Tình huống 2: Be và lớp self-serve cho phòng vận hành

Tại một công ty gọi xe kiểu Be, phòng vận hành thường xuyên cần biết "tài xế nào đang hoạt động tốt ở từng khu vực" để điều phối khuyến mãi. Nhưng query này phức tạp: phải JOIN bảng drivers, trips, ratings, lọc theo 30 ngày gần nhất, tính số chuyến và điểm trung bình. Nhân viên vận hành không biết SQL, nên mỗi lần họ phải nhờ BA — tạo thành điểm nghẽn, BA bị "spam" yêu cầu cả ngày.

BA dựng một View đóng gói toàn bộ logic đó:

CREATE VIEW driver_performance_30d AS
SELECT
    d.driver_id,
    d.full_name,
    d.region,
    COUNT(t.trip_id)        AS total_trips,
    AVG(r.score)            AS avg_rating,
    SUM(t.fare)             AS total_revenue
FROM drivers d
JOIN trips t   ON t.driver_id = d.driver_id
LEFT JOIN ratings r ON r.trip_id = t.trip_id
WHERE t.trip_date >= CURRENT_DATE - INTERVAL '30 days'
  AND t.status = 'completed'
GROUP BY d.driver_id, d.full_name, d.region;

Giờ đây nhân viên vận hành — chỉ cần biết một câu SELECT đơn giản — có thể tự lấy dữ liệu:

SELECT * FROM driver_performance_30d
WHERE region = 'Quận 7'
ORDER BY total_trips DESC
LIMIT 20;

Họ không cần hiểu JOIN, GROUP BY hay khoảng thời gian 30 ngày. Mọi độ phức tạp được giấu sau cái tên driver_performance_30d. BA giảm được 70% số yêu cầu lặt vặt, dành thời gian cho phân tích sâu hơn.

Bài học: View là công cụ trao quyền. Bạn viết SQL khó một lần, rồi biến nó thành thứ người không biết SQL cũng dùng được — đó là cách BA nhân rộng giá trị của mình.

Tình huống 3: MoMo và che dữ liệu PII bằng View

Tại một ví điện tử kiểu MoMo, đội phân tích cần xem dữ liệu giao dịch để phát hiện xu hướng, nhưng bảng gốc transactions chứa thông tin cực nhạy cảm: số điện thoại đầy đủ, số tài khoản ngân hàng, CMND. Cho cả đội đọc bảng gốc là rủi ro tuân thủ nghiêm trọng.

BA phối hợp với đội bảo mật tạo một View "đã làm sạch":

CREATE VIEW transactions_analytics AS
SELECT
    txn_id,
    txn_date,
    amount,
    txn_type,
    city,
    CONCAT('*', RIGHT(phone, 3))  AS phone_masked,
    customer_segment
FROM transactions;

Số điện thoại 0901234567 hiển thị thành *567 — đủ để phân biệt nhưng không lộ thông tin cá nhân. Cột CMND và số tài khoản đơn giản là không có trong View. Sau đó đội bảo mật cấp quyền: đội phân tích chỉ được đọc transactions_analytics, không được đụng vào bảng transactions gốc.

Bài học: View là một lớp kiểm soát truy cập tự nhiên. Bạn quyết định cột nào lộ ra, cột nào che, cột nào ẩn hoàn toàn. Đây là pattern chuẩn để cân bằng giữa "cho phân tích viên làm việc" và "bảo vệ dữ liệu khách hàng".

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

Giả sử bạn muốn tạo một View báo cáo khách hàng VIP cho công ty. Đây là quy trình:

Bước 1 — Viết và kiểm tra câu SELECT trước. Đừng vội tạo View. Hãy viết câu SELECT thuần và chạy thử cho đến khi kết quả đúng:

SELECT
    c.customer_id,
    c.full_name,
    c.city,
    SUM(o.total_amount) AS lifetime_spend
FROM customers c
JOIN valid_orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.full_name, c.city
HAVING SUM(o.total_amount) >= 50000000;

Bước 2 — Bọc nó vào CREATE VIEW. Khi đã hài lòng, thêm dòng CREATE VIEW ... AS ở đầu:

CREATE VIEW vip_customers AS
SELECT
    c.customer_id,
    c.full_name,
    c.city,
    SUM(o.total_amount) AS lifetime_spend
FROM customers c
JOIN valid_orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_id, c.full_name, c.city
HAVING SUM(o.total_amount) >= 50000000;

Lưu ý: View này build trên một View khác (valid_orders) — hoàn toàn hợp lệ. View có thể xếp chồng lên nhau, giúp bạn chia logic thành các lớp gọn gàng.

Bước 3 — Truy vấn và kiểm tra. Dùng thử như bảng:

SELECT city, COUNT(*) AS so_vip, SUM(lifetime_spend) AS tong_chi_tieu
FROM vip_customers
GROUP BY city
ORDER BY tong_chi_tieu DESC;

Bước 4 — Sửa View khi cần. Nếu định nghĩa VIP thay đổi (ví dụ nâng ngưỡng lên 80 triệu), dùng CREATE OR REPLACE VIEW:

CREATE OR REPLACE VIEW vip_customers AS
SELECT ...
HAVING SUM(o.total_amount) >= 80000000;

CREATE OR REPLACE ghi đè định nghĩa cũ mà không cần xóa trước — tiện cho việc chỉnh sửa logic.

Bước 5 — Xóa View khi không cần. Khi một View đã lỗi thời:

DROP VIEW IF EXISTS vip_customers;

Vì View không chứa dữ liệu, xóa View hoàn toàn an toàn — bảng gốc và dữ liệu không hề bị ảnh hưởng. Đây là điểm khác biệt yên tâm so với DROP TABLE.

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

Lỗi 1 — Tưởng View lưu dữ liệu và sẽ làm query nhanh hơn. View chỉ lưu câu query. Mỗi lần SELECT, query gốc chạy lại toàn bộ. Nếu View JOIN 5 bảng triệu dòng, truy vấn nó vẫn nặng y như chạy query đầy đủ. View giúp gọn code, không giúp tăng tốc. Nếu bạn cần tốc độ thật sự cho dữ liệu nặng, đó là việc của bảng pre-aggregated hoặc materialized view — một chủ đề riêng, không phải View thường.

Lỗi 2 — Đặt tên cột không rõ ràng trong View. Khi View có cột tính toán như SUM(amount), hãy luôn đặt alias rõ ràng (AS total_revenue). Nếu không, cột sẽ có tên kỳ quặc do database tự sinh, khiến người dùng View bối rối.

Lỗi 3 — Xếp chồng View quá nhiều tầng. View build trên View build trên View... đến tầng thứ năm thì không ai hiểu logic gốc nữa, và mỗi lần debug là một cơn ác mộng. Mẹo: giữ tối đa 2–3 tầng, và đặt tên rõ ràng cho từng tầng (ví dụ tầng "sạch" là valid_orders, tầng "tổng hợp" là monthly_revenue).

Lỗi 4 — Quên rằng đổi cấu trúc bảng gốc có thể làm hỏng View. Nếu ai đó đổi tên cột total_amount thành amount trong bảng orders, mọi View dùng cột đó sẽ lỗi khi truy vấn. Mẹo: trước khi đổi schema bảng, hãy kiểm tra xem có View nào phụ thuộc vào nó không.

Mẹo vàng — Đặt tên View theo quy ước nhất quán. Dùng tiền tố để phân loại: v_ cho view thường, hoặc đặt tên theo mục đích nghiệp vụ bằng tiếng Anh rõ nghĩa (active_customers, valid_orders, vip_customers). Tên tốt là một nửa giá trị của View — nó là thứ người dùng nhìn thấy đầu tiên.

Mẹo về tài liệu hóa. Mỗi View bạn tạo nên có một dòng giải thích "View này định nghĩa cái gì, ai dùng, lọc theo điều kiện gì". Vì View đóng gói tri thức nghiệp vụ, việc ghi lại định nghĩa đó cực kỳ quan trọng cho người tiếp quản sau bạn.

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

Giả sử bạn có ba bảng trong một schema thương mại điện tử: customers(customer_id, full_name, city, status, created_at), orders(order_id, customer_id, order_date, total_amount, status, is_test), và products(product_id, name, category, price).

Bài 1. Tạo View active_hcm_customers chứa các khách hàng có status = 'active'city = 'TP.HCM', chỉ lấy các cột customer_id, full_name, created_at. Sau đó viết một câu SELECT đếm xem có bao nhiêu khách hàng như vậy.

Bài 2. Tạo View clean_orders chỉ chứa đơn hàng "sạch": status = 'completed'is_test = FALSE. Sau đó tạo View thứ hai monthly_revenue build trên clean_orders, tính tổng doanh thu theo từng tháng (gợi ý: dùng hàm trích tháng từ order_date và GROUP BY).

Bài 3. Định nghĩa VIP trong công ty vừa thay đổi: từ "tổng chi tiêu ≥ 50 triệu" sang "tổng chi tiêu ≥ 100 triệu ở Hà Nội hoặc TP.HCM". Hãy dùng CREATE OR REPLACE VIEW để cập nhật một View VIP có sẵn theo định nghĩa mới.

Bài 4 (tư duy BA). Giả sử đội marketing không biết SQL nhưng cần "danh sách khách hàng đã mua trên 5 đơn trong 90 ngày qua để gửi voucher tri ân". Hãy thiết kế một View phù hợp, đặt tên dễ hiểu, và viết một dòng tài liệu mô tả View đó làm gì. Suy nghĩ: bạn nên che hay lộ thông tin gì để vừa giúp marketing làm việc vừa an toàn dữ liệu?

Gợi ý chấm điểm: với Bài 4, một View tốt sẽ chỉ lộ customer_id, full_name, email (cột cần để gửi voucher), và che các cột nhạy cảm khác. Tên gợi ý: loyal_customers_90d.

Tóm tắt

View là một câu SELECT được đặt tên và lưu trong database, dùng như một bảng nhưng không chứa dữ liệu — mỗi lần truy vấn, query gốc chạy lại để trả dữ liệu mới nhất. Với BA, View giải quyết ba bài toán lớn: đóng gói logic nghiệp vụ thành một nguồn sự thật duy nhất (như định nghĩa "đơn hàng hợp lệ" của Tiki), trao quyền self-serve cho người không biết SQL (như View hiệu suất tài xế của Be), và kiểm soát truy cập, che dữ liệu nhạy cảm (như View che số điện thoại của MoMo).

Cú pháp cốt lõi gồm CREATE VIEW ... AS SELECT ... để tạo, CREATE OR REPLACE VIEW để sửa, và DROP VIEW để xóa an toàn. Hãy nhớ: View làm code gọn hơn, không phải chạy nhanh hơn; đặt alias rõ ràng cho cột tính toán; tránh xếp chồng quá nhiều tầng; và luôn tài liệu hóa định nghĩa nghiệp vụ mà View đóng gói. Nắm vững View, bạn biến những query phức tạp thành công cụ tái sử dụng — đó là cách một BA nhân rộng giá trị của mình lên toàn tổ chức.