Trino SQL Bài học 35: Nhóm dữ liệu phức hợp với kiểu dữ liệu ROW
Mục tiêu bài học:
-
Hiểu bản chất của kiểu dữ liệu
ROW(tương đương với Struct/Record trong lập trình). -
Phân biệt rạch ròi giới hạn của
MAPvà sức mạnh củaROWtrong việc lưu trữ dữ liệu đa kiểu. -
Cú pháp khởi tạo, ép kiểu (CAST) và truy xuất trường (field) bên trong
ROW. -
Ứng dụng "sát thủ" của
ROWkết hợp vớimax_by/min_byđể tối ưu hóa truy vấn thay vì dùng Window Functions.
1. ROW khác gì với MAP và ARRAY?
Ở các bài trước, bạn đã học:
-
ARRAY: Chứa nhiều giá trị, nhưng phải cùng một kiểu (Ví dụ: mảng toàn chuỗi).
-
MAP: Chứa các cặp Key-Value, nhưng tất cả Value phải cùng một kiểu (Ví dụ: MAP(VARCHAR, VARCHAR)).
Vậy nếu bạn muốn lưu thông tin của một người gồm: Tên (Chuỗi), Tuổi (Số nguyên) và Trạng thái hoạt động (Boolean) vào cùng một ô dữ liệu thì sao? Cả ARRAY và MAP đều bất lực vì tính khắt khe của hệ thống "Kiểu tĩnh" (Strongly Typed).
Kiểu ROW (Bản ghi / Cấu trúc) ra đời để giải quyết vấn đề này. Nó cho phép bạn gom nhóm một tập hợp cố định các trường (fields) lại với nhau, và mỗi trường có thể mang một kiểu dữ liệu hoàn toàn khác biệt.
2. Khởi tạo và Ép kiểu ROW (Initialization)
Khi bạn gộp nhiều giá trị lại bằng từ khóa ROW, Trino sẽ tạo ra một bản ghi "vô danh". Để sử dụng nó một cách chuyên nghiệp và rõ ràng, bạn phải dùng CAST để định nghĩa tên và kiểu dữ liệu cho từng trường bên trong.
SQL
-- Khởi tạo ROW vô danh và ép kiểu (CAST) để đặt tên trường
SELECT
'CUST-01' AS customer_id,
CAST(
ROW('Nguyễn Văn A', 22, TRUE)
AS ROW(full_name VARCHAR, age INTEGER, is_active BOOLEAN)
) AS profile_info;
Kết quả hiển thị trên công cụ truy vấn (như DBeaver) thường sẽ có dạng chuỗi nội bộ: {full_name=Nguyễn Văn A, age=22, is_active=true}.
3. Truy xuất dữ liệu bên trong ROW
Việc truy xuất dữ liệu từ một ROW cực kỳ đơn giản và thanh lịch. Bạn chỉ cần sử dụng dấu chấm (.) kèm theo tên trường, y hệt như cách gọi thuộc tính của một Object trong lập trình hướng đối tượng.
SQL
WITH customer_data AS (
SELECT
101 AS user_id,
CAST(ROW('Hà Nội', 'VN') AS ROW(city VARCHAR, country VARCHAR)) AS location
)
SELECT
user_id,
location.city, -- Truy xuất trường city
location.country -- Truy xuất trường country
FROM customer_data;
Lưu ý: Nếu bạn không dùng CAST để đặt tên trường ở bước khởi tạo, Trino sẽ tự động gán tên mặc định là field0, field1,... Tuy nhiên, việc dựa dẫm vào tên mặc định là một "Anti-pattern" (cách code tồi) vì nó làm mất đi tính tự giải thích của mã nguồn.
4. Bóc tách ROW thành các cột phẳng (Flattening)
Khi bạn muốn bung toàn bộ các trường bên trong một cột ROW thành các cột độc lập (Flat Columns) trong bảng kết quả, bạn không cần phải gõ thủ công .field_1, .field_2. Trino hỗ trợ cú pháp .* (Dấu chấm sao) cực kỳ tiện lợi.
SQL
WITH raw_events AS (
SELECT
'E-99' AS event_id,
CAST(ROW(1920, 1080, 'Chrome') AS ROW(width INT, height INT, browser VARCHAR)) AS device_context
FROM raw_logs
)
SELECT
event_id,
device_context.* -- Tự động bung ra thành 3 cột: width, height, browser
FROM raw_events;
5. "Vũ khí tối thượng": Kết hợp ROW và max_by / min_by
Đây là kỹ thuật đẳng cấp của các Data Engineer giúp truy vấn chạy nhanh hơn hàng chục lần so với việc dùng Window Functions hay Self-Join.
Bài toán: Tìm tên và số điện thoại của khách hàng đã thực hiện Đơn hàng có giá trị cao nhất trong toàn bộ hệ thống.
Cách truyền thống: Phải dùng RANK() OVER (ORDER BY totalprice DESC) (Bài 27), bọc CTE, rồi lọc WHERE rank = 1. Quá trình này rất tốn RAM. Cách đỉnh cao với ROW: Sử dụng hàm max_by(giá_trị_cần_trả_về, tiêu_chí_sắp_xếp). Bằng cách gói "Tên" và "SĐT" vào một ROW, ta có thể rút ra toàn bộ thông tin cùng lúc chỉ bằng một hàm tổng hợp đơn giản!
SQL
SELECT
max_by(
-- Gom những thông tin muốn lấy thành một khối ROW
CAST(ROW(c.name, c.phone) AS ROW(customer_name VARCHAR, customer_phone VARCHAR)),
o.totalprice -- Tiêu chí: Lấy khối ROW của người có totalprice cao nhất
) AS top_spender_info
FROM tpch.tiny.orders AS o
JOIN tpch.tiny.customer AS c ON o.custkey = c.custkey;
Kết quả trả về sẽ là 1 dòng duy nhất chứa kiểu ROW. Bạn có thể thêm .* ở bên ngoài để bung nó ra thành cột. Trino chỉ cần quét bảng đúng 1 lần (Single Pass), tốc độ nhanh chớp nhoáng!
All Rights Reserved