DHDataHọc
Lý thuyếtBài 11/237 phút đọc+10 XP

NTILE

Bạn sẽ học được

  • NTILE(n) chia dòng thành n nhóm có kích thước gần bằng nhau.
  • Cách phần dư được phân bổ vào các nhóm đầu.
  • Dùng NTILE để chia tứ phân vị, thập phân vị trong phân tích khách hàng.
  • Khác biệt giữa NTILE và RANK khi chia nhóm.
  • Những cạm bẫy khiến nhóm không đều như tên gọi.

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

NTILE(n) chia các dòng trong cửa sổ thành n nhóm, đánh số từ 1 đến n. Kích thước các nhóm chênh nhau tối đa một dòng, và phần dư được đưa vào các nhóm đầu tiên.

Ví dụ chia 9 sản phẩm thành 4 nhóm theo giá tăng dần:

code
SELECT name, price,
       NTILE(4) OVER (ORDER BY price) AS nhom_gia
FROM products
ORDER BY price;

Kết quả:

namepricenhom_gia
Sách SQL thực chiến180.0001
Áo thun cotton250.0001
Đèn bàn LED290.0001
Bình giữ nhiệt320.0002
Giá kê laptop390.0002
Chuột không dây450.0003
Tai nghe Bluetooth890.0003
Bàn phím cơ1.250.0004
Nồi chiên không dầu1.690.0004

Chín dòng chia bốn nhóm: 9 = 2 × 4 + 1. Nhóm 1 nhận thêm một dòng nên có 3, ba nhóm còn lại mỗi nhóm 2. Không có nhóm nào trống, không có nhóm nào lệch quá một dòng.

Đảo chiều sắp xếp là đảo chiều ý nghĩa:

code
SELECT name, price,
       NTILE(4) OVER (ORDER BY price DESC) AS nhom_gia_giam_dan
FROM products
ORDER BY price DESC;

Lúc này nhóm 1 là bốn sản phẩm đắt nhất — nhưng vẫn là ba dòng cho nhóm 1 (Bàn phím, Nồi chiên và một sản phẩm nữa) và hai dòng cho các nhóm sau, vì quy tắc chia dựa trên vị trí chứ không dựa trên giá trị. Nhóm 1 theo chiều giảm dần gồm Nồi chiên (1.690.000), Bàn phím (1.250.000), Tai nghe (890.000); nhóm 4 gồm Sách và Áo.

Phân biệt với RANK:

RANKNTILE
Đầu raHạng của từng giá trịSố nhóm
Số nhómBằng số giá trị khác nhauĐúng n nhóm
Đồng hạngCùng hạngCó thể bị tách nhóm
Kích thướcKhông kiểm soátChênh tối đa 1

Điểm cuối là cạm bẫy lớn nhất: hai sản phẩm cùng giá có thể rơi vào hai nhóm khác nhau nếu chúng nằm hai bên ranh giới. NTILE không biết gì về giá trị, chỉ đếm vị trí. Nếu yêu cầu là "mọi sản phẩm cùng giá phải cùng nhóm", NTILE không đáp ứng được — phải tự đặt ngưỡng.

Ví dụ chạy được

Chia khách thành bốn nhóm chi tiêu (tứ phân vị) — mẫu dùng nhiều nhất trong phân tích RFM:

code
WITH spend AS (
  SELECT c.id, c.name,
         COALESCE(SUM(oi.quantity * oi.unit_price), 0) AS total
  FROM customers c
  LEFT JOIN orders o ON o.customer_id = c.id
  LEFT JOIN order_items oi ON oi.order_id = o.id
  GROUP BY c.id, c.name
)
SELECT name, total,
       NTILE(4) OVER (ORDER BY total DESC) AS nhom
FROM spend
ORDER BY total DESC;

Mười hai khách chia đều 3 người mỗi nhóm:

nametotalnhom
Bình Trần6.750.0001
Giang Đặng5.490.0001
Chi Lê4.270.0001
Minh Dương4.030.0002
An Nguyễn3.530.0002
Dũng Phạm2.150.0002
Khánh Hồ1.640.0003
Linh Ngô1.190.0003
Inh Đỗ960.0003
Em Võ640.0004
Nga Lý580.0004
Hà Bùi04

COALESCE là bắt buộc: Hà Bùi chưa có đơn, SUM trên LEFT JOIN trả NULL, và NTILE ... ORDER BY NULL sẽ xếp cô vào cuối với NULLS LAST mặc định — nhưng cột total khi đó là NULL chứ không phải 0, gây hiểu nhầm trong báo cáo. Đây là lý do phải xử lý NULL trước khi chia nhóm.

Chia đơn hàng thành ba nhóm (tam phân vị):

code
WITH totals AS (
  SELECT o.id, SUM(oi.quantity * oi.unit_price) AS total
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  GROUP BY o.id
)
SELECT id, total,
       NTILE(3) OVER (ORDER BY total DESC) AS nhom
FROM totals
ORDER BY total DESC;

Mười sáu đơn chia ba nhóm: 16 = 5 × 3 + 1, nên nhóm 1 có 6 đơn (13, 7, 6, 16, 9, 1) và hai nhóm sau mỗi nhóm 5 đơn. Ranh giới nhóm 1–2 nằm giữa 2.100.000 và 2.050.000; ranh giới nhóm 2–3 nằm giữa 1.530.000 và 1.430.000. Không đơn nào bị tách khỏi nhóm của mình vì dữ liệu không có hai đơn cùng tổng.

Chia trong từng danh mục với PARTITION BY:

code
SELECT category, name, price,
       NTILE(2) OVER (PARTITION BY category ORDER BY price DESC) AS nua
FROM products
ORDER BY category, price DESC;

Gia dụng có 4 sản phẩm nên mỗi nửa 2 sản phẩm. Điện tử có 3 sản phẩm nên nửa trên 2, nửa dưới 1. Sách và Thời trang chỉ một sản phẩm nên luôn rơi vào nửa 1 — NTILE không tạo nhóm rỗng chỉ để đủ số.

Lưu ý về tham số: n phải là hằng số nguyên dương. Không viết được NTILE(COUNT(*)/4) hay NTILE(số_nhóm) lấy từ cột khác. Muốn chia theo tỷ lệ động, phải tính n trước rồi ghép vào câu SQL, hoặc dùng PERCENT_RANK/CUME_DIST thay thế.

Một ứng dụng thực tế: gắn nhãn ABC cho sản phẩm theo doanh thu.

code
WITH revenue AS (
  SELECT p.id, p.name, SUM(oi.quantity * oi.unit_price) AS doanh_thu
  FROM products p
  JOIN order_items oi ON oi.product_id = p.id
  GROUP BY p.id, p.name
)
SELECT name, doanh_thu,
       CASE NTILE(3) OVER (ORDER BY doanh_thu DESC)
         WHEN 1 THEN 'A'
         WHEN 2 THEN 'B'
         ELSE 'C'
       END AS hang_abc
FROM revenue
ORDER BY doanh_thu DESC;

Chỉ 8 sản phẩm có doanh thu (Giá kê chưa bán được lần nào nên không xuất hiện vì dùng JOIN). NTILE(3) trên 8 dòng cho kích thước 3, 3, 2. Nhóm A gồm ba sản phẩm bán chạy nhất — cách phân loại này giúp đội vận hành biết nên ưu tiên nhập hàng cho ai.

Thực hành

  1. Chạy NTILE(4) trên 9 sản phẩm và kiểm tra kích thước nhóm là 3, 2, 2, 2.
  2. Chạy tứ phân vị khách hàng và xác nhận mỗi nhóm đúng 3 người, Hà Bùi ở nhóm 4 với 0.
  3. Đổi NTILE(3) thành NTILE(5) trên 16 đơn và tính kích thước mỗi nhóm trước khi chạy — sau đó đối chiếu.
  4. Thêm sản phẩm giả lập cùng giá với một sản phẩm có sẵn, chạy lại NTILE(4) và quan sát hai sản phẩm cùng giá rơi vào hai nhóm khác nhau.
  5. Viết câu dùng NTILE(10) cho khách hàng và giải thích vì sao với 12 khách, thập phân vị không còn nhiều ý nghĩa.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Nhóm chênh nhau hơn một dòngHiểu sai cơ chế chiaNTILE luôn chênh tối đa 1
Nhóm 1 luôn lớn nhấtĐó là quy tắc phần dưĐảo ORDER BY nếu muốn nhóm khác nhận dư
Hai dòng cùng giá khác nhómNTILE chia theo vị tríTự đặt ngưỡng nếu cần cùng nhóm
argument of ntile must be greater than zeron bằng 0 hoặc âmDùng hằng số dương
NTILE báo lỗi cú pháp với biếnn không phải hằng sốTính n trước ở ứng dụng
Khách chưa mua bị xếp cuối với NULLSUM trên LEFT JOINCOALESCE(..., 0)

Lỗi thứ ba là lý do NTILE không thay thế được ngưỡng kinh doanh. Nếu chính sách là "khách VIP khi chi từ 5 triệu", NTILE(4) không diễn đạt được điều đó — nó luôn tạo đúng bốn nhóm bất kể giá trị tuyệt đối. Hãy chọn công cụ theo câu hỏi: chia tương đối dùng NTILE, chia theo ngưỡng tuyệt đối dùng CASE.

Tóm tắt

  • NTILE(n) chia thành n nhóm gần bằng nhau, dư vào nhóm đầu.
  • Không đảm bảo các giá trị bằng nhau nằm cùng nhóm.
  • PARTITION BY chia nhóm trong từng phần; phần nhỏ hơn n sẽ có nhóm rỗng.
  • n phải là hằng số nguyên dương.
  • Dùng cho tứ phân vị, thập phân vị, phân loại ABC — không dùng thay ngưỡng tuyệt đối.

Dùng ← → để chuyển bài