Looker 4 — Dimensions, measures & tính toán nâng cao
Field là đơn vị nhỏ nhất của LookML
Ở Looker 2 — LookML cơ bản bạn đã thấy hình dạng của một view: nó chứa các field, mỗi field thuộc một trong hai loại lớn — dimension (thuộc tính để nhóm/lọc) và measure (phép tổng hợp). Đó mới là bộ xương. Bài này đi vào phần thịt: một dimension có nhiều type, một measure có cả một bảng type để chọn, và LookML còn cho phép người dùng cuối tự "lái" logic SQL qua parameter và Liquid templating.
Quy ước vẫn giữ nguyên: Looker là công cụ BI, không phải SQL sandbox. Mọi đoạn LookML ở đây là minh hoạ khái niệm, không phải để chạy trực tiếp ở đâu đó.
Dimension: nhiều type hơn bạn tưởng
Một dimension ánh xạ tới một cột hoặc một biểu thức SQL, và luôn có một type khai báo kiểu dữ liệu ngữ nghĩa. Ba type nền tảng:
string— chuỗi văn bản dùng để nhóm/lọc:region,customer_segment,product_name.number— số, có thể dùng trong biểu thức tính toán vàvalue_format.yesno— dimension boolean. Bạn viết một biểu thức SQL trả về true/false; Looker hiển thị "Yes"/"No". Rất tiện để đóng gói một điều kiện nghiệp vụ thành một field có tên rõ ràng, ví dụis_high_value= đơn hàng trên 10 triệu.
Điểm chung quan trọng: dimension không tổng hợp (non-aggregate). Nó là giá trị theo từng dòng. Trong SQL sinh ra, dimension xuất hiện ở SELECT và GROUP BY, còn measure nằm trong các hàm SUM/COUNT/....
tier — nhóm giá trị số thành khoảng
type: tier biến một cột số liên tục thành các khoảng (bins) rời rạc. Thay vì nhìn 3.000 mức tuổi khác nhau, người dùng thấy các nhóm "0–18", "18–35", "35–50", "50+". Bạn khai báo các điểm cắt qua tiers: và kiểu khoảng qua style (mặc định là classic — dạng "18 to 35").
# (minh hoạ) nhóm giá trị đơn hàng thành các bậc
dimension: order_amount_tier {
type: tier
tiers: [1000000, 5000000, 20000000, 50000000]
style: integer
sql: ${amount} ;;
}
tier cực hữu ích cho phân khúc (segmentation) mà không phải viết CASE WHEN dài dòng.
dimension_group kiểu time — trái tim của phân tích thời gian
Đây là tính năng đắt giá nhất của Looker về mặt tiết kiệm công. Từ một cột timestamp, dimension_group với type: time tự sinh ra cả một loạt dimension thời gian, mỗi cái là một mức chi tiết (granularity) khác nhau: giây, phút, giờ, ngày, tuần, tháng, quý, năm, thứ trong tuần, tên tháng, quý tài khoá... Bạn khai báo một lần, Looker đẻ ra hàng loạt field sẵn dùng.
Bốn tham số cần nắm:
sql— biểu thức trả về cột thời gian nguồn (thường là${TABLE}.created_at).timeframes— danh sách các mức bạn muốn sinh. Nếu bỏ trống, Looker sinh tất cả. Các giá trị hay dùng:raw(giá trị timestamp thô, chỉ dùng nội bộ trong LookML),date,week,month,quarter,year,day_of_week,month_name,hour,time,fiscal_quarter...datatype— định dạng của cột nguồn trong database:timestamp,datetime,date,epoch, hayyyyymmdd.convert_tz— có tự chuyển múi giờ hay không (mặc địnhyes).
Quan trọng nhất là quy tắc đặt tên field sinh ra: tên = <tên nhóm>_<timeframe>. Một dimension_group: created với timeframes: [date, week, month] sẽ tạo ra ba field độc lập: created_date, created_week, created_month. Người dùng trong Explore thấy chúng nhóm lại dưới nhãn "Created Date", "Created Week"... và tự do chọn mức granularity để "khoan" (drill) theo thời gian mà không cần bạn định nghĩa thủ công từng cái.
# (minh hoạ) một dimension_group sinh ra nhiều field thời gian
dimension_group: created {
type: time
timeframes: [raw, date, week, month, quarter, year, day_of_week]
datatype: timestamp
convert_tz: yes
sql: ${TABLE}.created_at ;;
}
Ngoài ra còn type: duration (đo khoảng cách giữa hai mốc thời gian qua sql_start, sql_end và intervals: [day, hour, ...]) — ví dụ số ngày từ lúc đặt hàng đến lúc giao.
${TABLE} và ${...} — hai cách tham chiếu
Trong sql: bạn thấy hai dạng thay thế (substitution):
${TABLE}— thay bằng tên bảng thực trong warehouse mà view đang trỏ tới. Dùng để trỏ tới cột gốc:${TABLE}.amount.${field_name}— thay bằng SQL đã được định nghĩa của một field khác trong mô hình. Dùng để tái sử dụng logic: một measure tham chiếu tới dimension${amount}thay vì lặp lại${TABLE}.amount.
Sự khác biệt này rất quan trọng ở phần measure: measure gần như luôn tham chiếu dimension qua ${} để tránh trùng lặp và giữ một nguồn sự thật.
Measure: bảng type và ràng buộc phụ thuộc
Measure là phép tổng hợp — nó "co" nhiều dòng thành một con số. LookML chia measure thành ba nhóm:
1. Aggregate (tổng hợp thực sự) — thao tác trực tiếp trên dimension/cột: count, sum, average, min, max, count_distinct, sum_distinct, average_distinct, median, percentile, list. Đây là các measure sinh ra SUM(), COUNT(DISTINCT ...), v.v. trong SQL. Chỉ nhóm này hoạt động với tham số filters (xem phần measure lọc bên dưới).
2. Non-aggregate (biến đổi, không tổng hợp) — number, string, date, yesno. Nhóm này không tự tổng hợp; nó biến đổi trên các measure khác. type: number là loại quan trọng nhất: dùng để tạo tỷ lệ giữa hai measure.
3. Post-SQL (tính sau khi query chạy) — running_total, percent_of_total, percent_of_previous. Nhóm này chạy sau khi SQL đã tính xong, tham chiếu tới measure số khác.
Ràng buộc phụ thuộc và cái bẫy "aggregate lồng aggregate"
Một quy tắc SQL nền tảng: bạn không được lồng hàm tổng hợp vào hàm tổng hợp — SUM(SUM(...)) là lỗi. Vì thế khi muốn tính tỷ lệ giữa hai tổng (ví dụ biên lợi nhuận = tổng lãi / tổng doanh thu), bạn không viết một measure aggregate mới, mà dùng type: number tham chiếu tới hai measure đã có qua ${}:
# (minh hoạ) tỷ lệ giữa hai measure — dùng type: number
measure: total_revenue {
type: sum
sql: ${amount} ;;
value_format_name: usd
}
measure: total_gross_profit {
type: sum
sql: ${gross_profit} ;;
}
measure: gross_margin_pct {
type: number
sql: ${total_gross_profit} / NULLIF(${total_revenue}, 0) ;;
value_format_name: percent_2
}
NULLIF(..., 0) để tránh chia cho 0. type: number ở đây chỉ chia hai kết quả tổng hợp đã tính, nên tránh được lỗi lồng aggregate.
Một chú ý về sự đúng đắn khi có join: khi một fact table nối tới bảng có quan hệ một-nhiều, phép SUM ngây thơ có thể bị nhân bản dòng và cộng dư. Looker xử lý bằng symmetric aggregates — kỹ thuật tính tổng đúng ngay cả khi dòng bị fan-out do join. Chi tiết cơ chế này thuộc về Looker 3 — Explores & joins; ở đây chỉ cần nhớ: measure và join gắn chặt với nhau về mặt đúng đắn số liệu.
Filtered measures — tổng hợp có điều kiện
Đây là tính năng khiến LookML gọn hơn SQL thuần rất nhiều. Tham số filters: cho phép một aggregate measure chỉ tính trên tập con dòng thoả điều kiện — mà không cần lọc cả Explore. Ví dụ kinh điển: doanh thu chỉ tính trên đơn thành công.
# (minh hoạ) measure lọc theo trạng thái đơn
measure: completed_revenue {
type: sum
sql: ${amount} ;;
filters: [status: "completed"]
value_format_name: usd
description: "Tổng doanh thu chỉ tính các đơn ở trạng thái completed"
}
Bạn có thể đặt nhiều điều kiện, dùng cú pháp lọc của Looker ("completed", ">1000000", "-cancelled"...). Nhờ đó, cùng một view có thể có total_revenue, completed_revenue, refunded_amount... đứng cạnh nhau, mỗi cái là một lát cắt nghiệp vụ được đặt tên rõ ràng.
Running/window — table calc hay SQL?
Với các phép có tính cửa sổ (running total, tỷ trọng, so kỳ trước), có hai con đường:
- Post-SQL measure (
running_total,percent_of_total...) hoặc SQL window function viết tay trongsql:— kết quả nằm trong SQL, tái dùng được ở mọi Explore. - Table calculation — tính phía client, xem phần cuối bài.
Parameters & templated filters — để người dùng "lái" SQL
parameter là một loại field đặc biệt: nó chỉ để chọn, không xuất hiện trong kết quả và không tổng hợp gì. Người dùng chọn đúng một giá trị, và giá trị đó được tiêm vào SQL để thay đổi hành vi truy vấn. Đây là cách tạo dynamic measure/dimension.
Bạn giới hạn lựa chọn bằng allowed_value (mỗi cái có label hiển thị và value thực tế đưa vào SQL):
# (minh hoạ) parameter chọn cột để cộng dồn
parameter: metric_to_sum {
type: string
allowed_value: { label: "Doanh thu"; value: "amount" }
allowed_value: { label: "Chi phí"; value: "cost" }
allowed_value: { label: "Lợi nhuận"; value: "profit" }
}
measure: dynamic_total {
type: sum
sql: ${TABLE}.{% parameter metric_to_sum %} ;;
}
Thẻ Liquid {% parameter metric_to_sum %} chèn thẳng giá trị người dùng chọn vào SQL — người dùng đổi giữa các cột mà không cần bạn định nghĩa ba measure riêng.
Với logic phức tạp hơn, dùng ._parameter_value trong khối điều kiện Liquid:
# (minh hoạ) parameter chọn kỳ → chọn dimension thời gian tương ứng
parameter: date_granularity {
type: unquoted
allowed_value: { label: "Ngày"; value: "day" }
allowed_value: { label: "Tháng"; value: "month" }
allowed_value: { label: "Quý"; value: "quarter" }
}
dimension: period {
sql:
{% if date_granularity._parameter_value == 'day' %} ${created_date}
{% elsif date_granularity._parameter_value == 'month' %} ${created_month}
{% else %} ${created_quarter}
{% endif %} ;;
}
Templated filters — {% condition %}
Khác với parameter (đưa một giá trị vào SQL), templated filter đưa cả một mệnh đề lọc do người dùng chọn vào đúng vị trí bạn muốn trong SQL. Cú pháp:
# (minh hoạ) áp filter của người dùng vào đúng chỗ trong sql
filter: date_filter {
type: date
}
measure: filtered_count {
type: count
sql:
{% condition date_filter %} ${created_raw} {% endcondition %} ;;
}
Looker dịch {% condition date_filter %} ${created_raw} {% endcondition %} thành mệnh đề WHERE tương ứng với lựa chọn của người dùng (ví dụ "trong 30 ngày qua"), áp lên cột created_raw. Đây là cách kiểm soát chính xác filter được áp vào đâu — đặc biệt hữu ích trong derived table và join phức tạp (xem Looker 5 — Derived tables & PDT).
Liquid templating — label, html & sql động
Liquid là ngôn ngữ template mà Looker dùng bên trong các tham số như sql, sql_on, label, description, html, link. Về ý tưởng nó tương tự Jinja của dbt (chèn logic vào chuỗi bằng {{ }} và {% %}), nhưng cú pháp và các biến là của Looker, không lẫn lộn hai bên.
{{ }}— xuất giá trị.{{ value }}là giá trị thô từ database;{{ rendered_value }}là giá trị đã định dạng theo Looker;{{ link }}là URL drill mặc định;{{ linked_value }}kết hợp cả hai.{% %}— logic điều khiển:{% if %},{% parameter %},{% condition %}...
Ứng dụng thực tế nhất là html: để tô màu/định dạng động ô kết quả, hoặc label: động:
# (minh hoạ) html động: tô đỏ khi giá trị âm
measure: net_margin {
type: number
sql: ${total_gross_profit} / NULLIF(${total_revenue}, 0) ;;
value_format_name: percent_1
html:
{% if value < 0 %}
<span style="color:#c0392b;font-weight:600">{{ rendered_value }}</span>
{% else %}
<span style="color:#27ae60">{{ rendered_value }}</span>
{% endif %} ;;
}
value_format và định dạng tiền tệ Việt Nam
Định dạng số có hai đường: value_format_name (dùng tên định dạng dựng sẵn: usd, percent_2, decimal_0...) và value_format (chuỗi định dạng tuỳ biến kiểu Excel). Với tiền VND — dùng dấu chấm phân cách nghìn và hậu tố "₫" — thường phải viết value_format tay:
# (minh hoạ) định dạng tiền đồng
measure: total_revenue_vnd {
type: sum
sql: ${amount} ;;
value_format: "#,##0 \"₫\""
}
Nguyên tắc: nhất quán một quy ước định dạng trong toàn mô hình (nghìn/triệu/tỷ, số chữ số thập phân, ký hiệu tiền tệ) để mọi dashboard nhìn đồng bộ.
Measure (SQL) vs Table calculation (client)
Hai công cụ dễ nhầm nhưng khác bản chất:
| Measure | Table calculation | |
|---|---|---|
| Định nghĩa ở đâu | LookML (dev) | Trong Explore/Look (người dùng) |
| Chạy ở đâu | SQL, trên warehouse | Phía client, sau khi có kết quả |
| Tái sử dụng | Mọi Explore/Dashboard | Chỉ trong Look/tile đó |
| Phù hợp cho | Metric chuẩn, dùng lại nhiều nơi | Tính nhanh, thử nghiệm ad-hoc, xếp hạng dòng |
Nguyên tắc chọn: nếu một con số là metric chuẩn của tổ chức hoặc cần dùng lại → làm measure trong LookML (một nguồn sự thật). Nếu là phép tính một lần, cục bộ trong một report cụ thể (ví dụ cột "% so với tổng bảng hiện tại", xếp hạng trong đúng bảng này) → dùng table calculation cho nhanh, khỏi động vào code.
Use case thực tế: doanh thu theo trạng thái + chọn kỳ
Gộp các mảnh trên thành một tình huống ngân hàng/bán lẻ quen thuộc: dashboard doanh thu cho phép người dùng (1) chỉ tính đơn thành công và (2) tự chọn xem theo ngày/tháng/quý.
# (minh hoạ) ghép filtered measure + parameter chọn kỳ
view: orders {
sql_table_name: analytics.orders ;;
dimension_group: created {
type: time
timeframes: [date, month, quarter, year]
sql: ${TABLE}.created_at ;;
}
dimension: amount { type: number sql: ${TABLE}.amount ;; hidden: yes }
dimension: status { type: string sql: ${TABLE}.status ;; }
parameter: period {
type: unquoted
allowed_value: { label: "Theo ngày"; value: "day" }
allowed_value: { label: "Theo tháng"; value: "month" }
allowed_value: { label: "Theo quý"; value: "quarter" }
}
dimension: period_bucket {
label: "Kỳ (động)"
sql:
{% if period._parameter_value == 'day' %} ${created_date}
{% elsif period._parameter_value == 'month' %} ${created_month}
{% else %} ${created_quarter}
{% endif %} ;;
}
measure: completed_revenue {
group_label: "Doanh thu"
label: "Doanh thu (đơn thành công)"
type: sum
sql: ${amount} ;;
filters: [status: "completed"]
value_format: "#,##0 \"₫\""
description: "Tổng amount, chỉ tính status = completed"
}
}
Người dùng chọn "Theo tháng" ở filter period, Looker sinh SQL nhóm theo created_month; completed_revenue tự thêm điều kiện status = 'completed' vào phép SUM. Không ai phải sửa code, và định nghĩa "doanh thu đơn thành công" vẫn đúng một chỗ cho mọi người.
Ghi nhớ
- Dimension = thuộc tính (non-aggregate, vào
GROUP BY); measure = tổng hợp (vào hàmSUM/COUNT/...). dimension_grouptype: time sinh nhiều field thời gian từ một cột; tên field =<nhóm>_<timeframe>(ví dụcreated_month). Đây là nền tảng cho drill theo thời gian.tiernhóm số thành khoảng;yesnođóng gói điều kiện nghiệp vụ thành field boolean.- Tỷ lệ giữa hai tổng → dùng
type: numbertham chiếu measure qua${}, tránh lỗi lồng aggregate. NhớNULLIF(...,0). - Filtered measure (
filters:) chỉ dùng được với aggregate measure — cách gọn để có nhiều lát cắt doanh thu trong cùng view. parametercho người dùng chọn một giá trị → tiêm vào SQL qua{% parameter %};{% condition %}áp cả mệnh đề lọc vào đúng chỗ (templated filter).- Liquid (
{{ }}/{% %}) tạosql/label/htmlđộng — ý tưởng giống Jinja của dbt nhưng biến và cú pháp là của Looker. - Measure (SQL, tái dùng) vs Table calculation (client, cục bộ) — chọn theo tính chất "chuẩn tổ chức" hay "một lần".
- Best practice: đặt tên rõ, gắn
group_labelvàdescription,hidden: yescho field kỹ thuật, và giữvalue_formatnhất quán (tiền ₫, %, số chữ số thập phân).
Đọc tiếp: quay lại Looker 2 — LookML cơ bản để ôn cú pháp, Looker 3 — Explores & joins cho symmetric aggregates, và Looker 5 — Derived tables & PDT khi cần đóng gói tính toán nặng thành bảng dẫn xuất.
Bài viết liên quan
Phân biệt ước lượng điểm và ước lượng khoảng, cách xây khoảng tin cậy (CI) bằng margin of error z*·SE / t*·SE, đánh đổi mức 90/95/99%, và cách DIỄN GIẢI ĐÚNG khoảng tin cậy (95% CI không phải xác suất tham số nằm trong khoảng). Có CI cho trung bình và cho tỷ lệ, ảnh hưởng của cỡ mẫu, cùng ví dụ ngân hàng về tỷ lệ nợ xấu và số dư trung bình.
Kimball dimensional modeling: bảng fact/dimension, star vs snowflake, grain, và Slowly Changing Dimension.
Khối OLAP, các thao tác drill-down/roll-up/slice & dice/pivot, OLAP vs OLTP và ROLAP/MOLAP/HOLAP.
Từ nguồn dữ liệu qua ETL/ELT vào Data Warehouse, Data Mart đến dashboard; staging, ODS, batch vs streaming.