aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

SQL ROW_NUMBER: lấy nguyên bản ghi mới nhất của mỗi khách hàng

Suong Nguyen by Suong Nguyen
27/10/2026
in Backend
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Một màn hình quản trị cần hiện đơn gần nhất của từng khách, gồm mã đơn, số tiền và trạng thái. MAX(created_at) tìm được thời điểm, nhưng chưa chọn cho bạn toàn bộ một bản ghi. Thêm MAX(amount) còn có thể lấy tiền từ một đơn khác.

ROW_NUMBER() giúp diễn đạt việc chọn hàng rõ ràng hơn: chia theo khách, sắp xếp các đơn trong từng nhóm, rồi lấy hàng đứng đầu. Bài đối chiếu cú pháp PostgreSQL 17 ngày 27/10/2026. Các truy vấn minh họa được chạy trên SQLite có hỗ trợ window function; chưa chạy trực tiếp trên PostgreSQL và không có benchmark hiệu năng.

1. Tạo dữ liệu có trường hợp dễ nhầm

Dùng database thử nghiệm riêng. Với SQLite CLI, mở sqlite3 :memory: rồi chạy các đoạn SQL theo thứ tự trong cùng phiên. Chuỗi thời gian có cùng định dạng UTC để thứ tự từ điển cũng đúng thứ tự thời gian của fixture.

CREATE TABLE orders (
  id INTEGER PRIMARY KEY,
  customer_id INTEGER NOT NULL,
  created_at TEXT NOT NULL,
  amount INTEGER NOT NULL,
  status TEXT NOT NULL
);
INSERT INTO orders VALUES
  (10, 1, '2026-10-25T08:00:00Z', 900, 'paid'),
  (11, 1, '2026-10-26T08:00:00Z', 100, 'paid'),
  (12, 1, '2026-10-26T08:00:00Z', 200, 'pending'),
  (20, 2, '2026-10-24T08:00:00Z', 500, 'paid');

Khách 1 có đơn cũ đắt nhất và hai đơn mới cùng thời điểm. Đây là hai điều kiện quan trọng để kiểm tra truy vấn, thay vì chỉ dùng dữ liệu mà mỗi khách có đúng một đơn.

Trong PostgreSQL thật, nên dùng kiểu thời gian phù hợp như timestamptz và số tiền theo đơn vị nhỏ nhất hoặc kiểu số phù hợp nghiệp vụ. Khai báo TEXT ở đây để ví dụ chạy thống nhất trên SQLite, không phải đề xuất schema production cho mọi hệ quản trị.

2. Xếp hạng rồi chọn hàng đầu tiên

WITH ranked AS (
  SELECT orders.*,
         ROW_NUMBER() OVER (
           PARTITION BY customer_id
           ORDER BY created_at DESC, id DESC
         ) AS position
  FROM orders
)
SELECT id, customer_id, created_at, amount, status
FROM ranked
WHERE position = 1
ORDER BY customer_id;

Kết quả gồm đơn 12 của khách 1, số tiền 200, trạng thái pending; và đơn 20 của khách 2, số tiền 500. Tất cả cột của mỗi dòng đều đến từ cùng một đơn.

PARTITION BY bắt đầu lại việc đánh số cho từng khách. ORDER BY trong OVER xác định hàng nào được ưu tiên trong nhóm. ORDER BY customer_id cuối truy vấn chỉ sắp thứ tự các dòng kết quả; nó không thay thế thứ tự trong window.

Điều kiện id DESC là quy tắc phân xử khi thời gian bằng nhau. Trong fixture, ID là duy nhất nên thứ tự đã xác định. ID lớn hơn không tự chứng minh thời gian tạo muộn hơn trong mọi hệ thống phân tán; hãy chọn quy tắc phù hợp và ghi rõ nó trong yêu cầu sản phẩm.

3. Đặt điều kiện trạng thái ở đâu?

Hai yêu cầu dưới đây khác nhau:

  • Đơn paid gần nhất: chỉ cho đơn paid tham gia xếp hạng.
  • Đơn gần nhất nếu nó đã paid: xếp hạng tất cả, rồi xem trạng thái của hàng đứng đầu.

Với yêu cầu đầu tiên, đặt WHERE status = 'paid' bên trong CTE, sau FROM orders. Khách 1 sẽ nhận đơn 11. Với yêu cầu thứ hai, giữ nguyên CTE và đổi điều kiện ngoài thành WHERE position = 1 AND status = 'paid'; khách 1 sẽ không có mặt vì đơn mới nhất là pending.

Đừng chuyển điều kiện giữa hai vị trí chỉ để làm câu SQL trông ngắn hơn. Hãy dùng dữ liệu có đơn pending mới hơn một đơn paid để xác nhận ý nghĩa.

4. Vì sao không dùng nhiều MAX?

SELECT customer_id, MAX(created_at), MAX(amount)
FROM orders
GROUP BY customer_id
ORDER BY customer_id;

Khách 1 có thời điểm 2026-10-26T08:00:00Z và số tiền 900. Hai giá trị này tồn tại trong dữ liệu, nhưng không nằm trên cùng một đơn. Truy vấn aggregate đúng với ý nghĩa lấy cực đại độc lập; nó sai với yêu cầu lấy nguyên bản ghi mới nhất.

Một cách join trở lại theo MAX(created_at) cũng cần xử lý thời điểm trùng nhau, nếu không có thể trả cả đơn 11 và 12. Nếu nghiệp vụ muốn giữ tất cả hàng đồng hạng, hãy chọn quy tắc khác thay vì ép chúng thành một bằng ROW_NUMBER().

Kiểm tra và lỗi thường gặp

Window function được tính sau bước lọc thông thường của cùng tầng truy vấn, nên không đặt trực tiếp ROW_NUMBER() vào WHERE cùng tầng. CTE hoặc subquery tạo tầng lọc bên ngoài.

Thử thêm khách không có đơn, thời gian bằng nhau và khách chỉ có pending. Truy vấn này bắt đầu từ bảng đơn nên không hiện khách chưa có đơn. Muốn hiện họ cần thêm bảng khách và thiết kế phép join riêng.

Khi dữ liệu lớn, xem execution plan trên database thật và bộ lọc thật. Bài chưa kết luận một index hay cách viết luôn nhanh hơn. Trước hết, hãy bảo đảm các cột trả về cùng thuộc hàng mà nghiệp vụ đã chọn.

Nguồn tham khảo

  • PostgreSQL 17: Window functions
  • PostgreSQL 17: Hướng dẫn window functions và tầng lọc
  • SQLite: Window functions

Tags: PostgreSQLSQLWindow functions
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

LEFT JOIN và COUNT: đừng đếm khách chưa có đơn thành một đơn hàng

by Suong Nguyen
26/10/2026
0

Phân biệt COUNT(*) và COUNT(cột) trên LEFT JOIN, đặt điều kiện ON đúng mục tiêu và kiểm tra các hàng không có dữ liệu khớp.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

SQL NOT IN gặp NULL: vì sao truy vấn loại trừ trả về rỗng?

by Suong Nguyen
25/10/2026
0

Dựng một fixture nhỏ để hiểu NOT IN với NULL, chọn NOT EXISTS theo nghiệp vụ và kiểm tra cả cột bên trái lẫn bên phải.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Đọc PostgreSQL EXPLAIN ANALYZE: đừng nhầm cost với mili giây

by Suong Nguyen
24/10/2026
0

Đọc cost, rows, loops và buffers theo đúng ý nghĩa trước khi quyết định thêm index cho truy vấn PostgreSQL.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Optimistic locking trong PostgreSQL: tránh ghi đè thay đổi của người khác

by Suong Nguyen
23/10/2026
0

Dùng cột version và UPDATE có điều kiện để phát hiện một biểu mẫu đang lưu trên phiên bản dữ liệu đã cũ.

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native SectionList: nhóm dữ liệu với key và tiêu đề riêng cho từng phần
  • Phỏng vấn JavaScript: Object.is khác === thế nào với NaN, +0 và -0?
  • Android Sharesheet: chia sẻ văn bản và đường dẫn bằng ACTION_SEND

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply