+1

Nghệ Thuật Ép Database Làm Việc: "Expressions in SELECT" & Quyền Năng Của JSON Aggregation

Phần lớn anh em lập trình viên Backend hiện nay đang bị "nghiện" các ORM (như Prisma, Eloquent, TypeORM) hoặc các hàm xử lý mảng (Array methods) của ngôn ngữ lập trình. Họ có một thói quen rất phổ biến: Lôi toàn bộ dữ liệu thô từ Database lên RAM của server, sau đó dùng vòng lặp (map, foreach) để xào nấu, tính toán, và định dạng lại. Hôm nay, một Vibe Coder sẽ đập tan thói quen đó. Chúng ta sẽ mở khóa một sức mạnh nguyên thủy của SQL: Expressions in SELECT (Biểu thức trong lệnh SELECT). Khi bạn làm chủ được nó, bạn sẽ tiết kiệm được hàng GB RAM cho server và khiến các truy vấn chạy nhanh như một tia chớp.

Pha cà phê chưa? Chúng ta bắt đầu nhé!

PHẦN 1: NỖI ĐAU CỦA TƯ DUY "THỢ GÕ" - MANG VIỆC VỀ NHÀ LÀM

Hãy tưởng tượng Tech Lead giao cho bạn một task: "Lấy danh sách người dùng, hiển thị Họ Tên đầy đủ (viết hoa chữ cái đầu), kèm theo trạng thái hiển thị là 'Khách VIP' nếu họ đã tiêu trên 10 triệu, ngược lại là 'Thường'."

Cách 1: Tư duy "Thợ gõ" (Xử lý bằng Code Backend - Node.js/PHP)

Họ sẽ viết một câu query rất ngây ngô: SELECT first_name, last_name, total_spent FROM users; Sau đó, ở Node.js, họ bắt đầu múa code:

// Kéo 100,000 dòng dữ liệu thô vào RAM (Có thể gây OOM Crash)
const rawUsers = await db.query('SELECT first_name, last_name, total_spent FROM users');

const formattedUsers = rawUsers.map(user => {
    // Thao tác chuỗi tốn CPU của Node.js
    const fullName = `${user.first_name.charAt(0).toUpperCase() + user.first_name.slice(1)} ${user.last_name}`;
    
    // Thao tác logic if/else
    let tier = 'Thường';
    if (user.total_spent >= 10000000) tier = 'Khách VIP';

    return { fullName, tier };
});

Tại sao cách này lại cực kỳ tồi tệ?

  1. Lãng phí băng thông mạng: Bạn đang bắt Database gửi những cột dữ liệu thô qua đường truyền mạng sang con server Backend. 100,000 cái tên tốn rất nhiều Megabyte.
  2. Lãng phí RAM: Con server Node.js phải chứa 100,000 Object thô, cộng thêm 100,000 Object đã format.
  3. Chậm chạp: Node.js/PHP là ngôn ngữ thông dịch (Interpreted). Thao tác cộng chuỗi, viết hoa, tính toán bằng code Backend không bao giờ có thể đọ lại tốc độ của lõi C/C++ được tối ưu hóa ở tầng thấp của Engine Database (như MySQL/PostgreSQL).

Vậy tại sao không bắt Database tính toán xong xuôi, gói ghém đẹp đẽ rồi mới ném lên cho Backend xài?

PHẦN 2: BƯỚC ĐẦU CỦA VIBE CODER - BASIC EXPRESSIONS & CONTROL FLOW

Expressions in SELECT đơn giản là việc bạn viết các công thức toán học, hàm xử lý chuỗi, hoặc các khối logic trực tiếp vào ngay đằng sau chữ SELECT.

2.1. String & Math Expressions (Định dạng & Toán học)

Thay vì đem về Backend mới tính, hãy tính luôn trong SQL

SELECT 
    id,
    -- Gộp chuỗi và viết hoa ngay trong SQL
    CONCAT(UPPER(SUBSTRING(first_name, 1, 1)), LOWER(SUBSTRING(first_name, 2)), ' ', last_name) AS full_name,
    
    -- Tính toán giá sau thuế
    (price * quantity) * 1.08 AS total_price_with_tax
FROM orders;

Database trả về đúng 2 cột full_nametotal_price_with_tax. Node.js chỉ việc nhận và ném thẳng ra API. Không tốn một giọt mồ hôi!

2.2. Ma Thuật Xử Lý NULL: COALESCE

Làm việc với Database, NULL là một cơn ác mộng. Nếu user không có avatar, cột đó mang giá trị NULL. Nếu gửi thẳng NULL cho Frontend, Frontend có thể văng lỗi trắng màn hình khi gọi <img src={user.avatar} />.

Hãy dùng COALESCE - Hàm này sẽ kiểm tra, nếu gặp NULL nó sẽ lập tức thay thế bằng giá trị dự phòng.

SELECT 
    username,
    COALESCE(avatar_url, 'https://default.com/avatar.png') AS avatar
FROM users;

2.3. Control Flow: Đưa If/Else Vào Database Bằng CASE WHEN

Đây là thứ giúp bạn giải quyết bài toán "Khách VIP" ở đầu bài mà không cần viết một dòng logic if/else nào trong Node.js / PHP.

SELECT 
    id,
    CONCAT(first_name, ' ', last_name) AS full_name,
    total_spent,
    CASE 
        WHEN total_spent >= 10000000 THEN 'Khách VIP'
        WHEN total_spent >= 5000000 THEN 'Thành Viên Vàng'
        WHEN total_spent IS NULL THEN 'Chưa mua hàng'
        ELSE 'Thường'
    END AS user_tier
FROM users;

Chỉ với cú pháp CASE WHEN, bạn biến SQL thành một ngôn ngữ lập trình thực thụ. Bạn đã đẩy toàn bộ gánh nặng phân loại (Categorization) xuống cho cỗ máy mạnh nhất hệ thống: Database Engine.

PHẦN 3: TUYỆT KỸ HẠNG NẶNG - JSON AGGREGATION (KẺ HỦY DIỆT VÒNG LẶP N+1)

Nếu 2 phần trên chỉ là "khởi động", thì đây mới là kỹ thuật phân định giữa một Coder tầm trung và một Software Engineer tinh hoa.

Bài toán kinh điển: Lấy ra danh sách 10 bài Post, MỖI bài Post kèm theo MẢNG (Array) chứa các Comments của bài đó.

Cách anh em ORM/Thợ gõ hay làm (Gây ra lỗi N+1 Query):

  1. Query 1 lần lấy 10 bài Post.
  2. Chạy vòng lặp for 10 lần, mỗi lần bắn 1 câu query để lấy Comments của bài Post tương ứng. -> Tổng cộng mất 11 vòng kết nối xuống DB (Network roundtrips). Hệ thống chậm rùa bò!

Cách khá hơn (Dùng JOIN):

SELECT posts.id, posts.title, comments.id AS c_id, comments.content 
FROM posts 
LEFT JOIN comments ON posts.id = comments.post_id;

Nhưng cách này tạo ra Data lặp (Data Duplication). Nếu 1 Post có 5 Comments, cột posts.idposts.title sẽ bị lặp lại 5 lần trong kết quả trả về. Dữ liệu siêu phình to, Backend nhận về một cái bảng dẹt (Flat table) và lại phải đi dùng vòng lặp để gom nhóm (group by) lại thành cấu trúc Mảng/Object.

GIẢI PHÁP VIBE CODER: Bắt SQL sinh ra JSON cho bạn!

Các phiên bản hiện đại của MySQL (5.7+) và PostgreSQL đều hỗ trợ biểu thức JSON cực kỳ mạnh mẽ: JSON_OBJECT (Tạo Object) và JSON_ARRAYAGG (Gom vào mảng).

Hãy xem câu query thần thánh này:

SELECT 
    posts.id,
    posts.title,
    -- TUYỆT KỸ BẮT ĐẦU TỪ ĐÂY
    COALESCE(
        JSON_ARRAYAGG(
            JSON_OBJECT(
                'id', comments.id,
                'content', comments.content,
                'author_name', comments.author_name
            )
        ), 
    '[]') AS comments_array
FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
GROUP BY posts.id;

Chuyện gì vừa xảy ra?

Thay vì trả về hàng chục dòng bị lặp lại, câu query này trả về CHÍNH XÁC 1 dòng cho mỗi bài Post. Cột comments_array lúc này không phải là text bình thường, nó CÓ CHỨA SẴN MỘT MẢNG JSON HOÀN CHỈNH!

// Kết quả trả về ngay từ Database:
{
  "id": 1,
  "title": "Vibe Coder Series",
  "comments_array": [
    {"id": 101, "content": "Bài hay quá!", "author_name": "Hiếu"},
    {"id": 102, "content": "Rất hữu ích", "author_name": "Hoàng"}
  ]
}

Tại Backend (Node.js / PHP), bạn không cần map, không cần groupBy, không cần ORM phức tạp. Bạn gọi Query, lấy cái cục JSON đó trả thẳng ra cho Frontend res.json(data). Nhanh hơn gấp 10 lần, tiết kiệm RAM gấp 10 lần!

3.1. Trải nghiệm trực quan: Từ Bảng Dẹt Biến Hình Thành Cây JSON

Để bạn cảm nhận sâu sắc sự kỳ diệu của JSON_ARRAYAGG, hãy thử tương tác với bộ mô phỏng quá trình gom nhóm dữ liệu của Database dưới đây:

image.png

image.png

PHẦN 4: TRADE-OFFS (SỰ ĐÁNH ĐỔI) & NGUYÊN TẮC CỦA VIBE CODER

Dùng Expressions in SELECT sướng thật, nhưng nếu bạn "nghiện" nó quá mức, bạn sẽ tạo ra những con quái vật SQL dài 500 dòng không ai dám đọc. Làm System Design luôn có hai mặt.

4.1. Chuyển gánh nặng từ RAM (App) sang CPU (DB)

Khi bạn bắt Database làm toán, xử lý chuỗi và sinh JSON, bạn đang giải phóng RAM cho Node.js / PHP, nhưng bù lại, con chip CPU của Database sẽ phải gánh mọi thứ.

  • Quy luật: Scale (mở rộng) Backend App thì dễ và rẻ (cứ mua thêm container, bật auto-scaling). Nhưng Scale Database thì cực kỳ khó, nguy hiểm và siêu đắt đỏ.
  • Lời khuyên: Hãy dùng Expressions cho việc Định dạng (Formatting), Gom nhóm (Aggregation) và các phép toán cơ bản. TUYỆT ĐỐI KHÔNG đem những Business Logic phức tạp, thuật toán mã hóa (Hashing/Bcrypt), hay tính toán Machine Learning vào trong SQL. Đừng để CPU của DB bị nghẽn!

4.2. Khó Debug và Test

Code Node.js / PHP bạn có thể cắm cờ Debug (Breakpoint), chạy từng dòng console.log() để xem biến. Còn một câu query SQL dài 30 dòng với đủ thể loại CASE WHENJSON_OBJECT? Nếu nó lỗi, bạn chỉ nhận được thông báo "Syntax error near line 15". Viết Unit Test cho câu query cũng phức tạp hơn nhiều so với việc test một hàm Javascript thuần.

LỜI KẾT

Tư duy "Cái gì cũng để Backend lo" là một trong những rào cản lớn nhất ngăn lập trình viên tiến lên cấp độ Senior. Việc hiểu và làm chủ Expressions in SELECT giúp bạn vắt kiệt sức mạnh của Database, tối ưu hóa đường truyền mạng và biến mã nguồn Backend trở nên thanh thoát, gọn gàng hơn bao giờ hết.

Lần tới, trước khi gõ Array.map() hay foreach, hãy dừng lại 3 giây và tự hỏi: "Liệu con quái thú MySQL/PostgreSQL của mình có thể xử lý việc này ngay từ trong trứng nước hay không?"

Chủ đề tiếp theo: Vũ Khí Cuối Cùng Của SQL - Window Functions

Bạn nghĩ GROUP BYJSON_ARRAYAGG là ghê gớm?

Giả sử bạn có bảng Lương Nhân Viên. Sếp yêu cầu: "Cho tôi danh sách toàn bộ nhân viên, NHƯNG bên cạnh mức lương của từng người, hãy in ra một cột hiển thị 'Mức lương trung bình của phòng ban' để họ tiện so sánh".

Nếu dùng GROUP BY thông thường, bạn bị ép phải gom tất cả nhân viên lại, làm mất chi tiết từng người. Bạn định query 2 lần rồi dùng Backend để merge data? Quên đi!

Ở bài viết tới, chúng ta sẽ mở khóa một cảnh giới hoàn toàn khác của SQL: Window Functions (Hàm Cửa Sổ) với từ khóa OVER (PARTITION BY ...). Nó cho phép bạn vừa lấy chi tiết từng dòng, vừa tính toán gom nhóm siêu việt mà không làm mất dữ liệu. Anh em nhớ chuẩn bị não bộ để đón nhận siêu kỹ năng này nhé!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.