Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 8 — JQL Nâng Cao — Advanced Queries cho BA

JIRA and Confluence for BA Bài 8/60

Mở đầu — vì sao bài này quan trọng

Ở Bài 7, bạn đã làm quen với JQL cơ bản: tìm issue theo project, assignee, status, kết hợp AND/OR. Đó là nền móng giúp bạn "không bị lạc" trong một dự án có vài trăm issue. Nhưng thực tế công việc của một Business Analyst hiếm khi dừng ở mức "tìm cho ra cái issue đó". BA thật sự cần JQL để trả lời câu hỏi nghiệp vụ theo thời gian thực: "Có bao nhiêu yêu cầu thay đổi (change request) chưa được phân tích trong tuần này?", "Những user story nào liên quan tới module Thanh toán mà đã quá hạn?", "Stakeholder vừa hỏi: tuần qua team đã đóng được những gì?".

Để trả lời nhanh và chính xác những câu đó, bạn cần JQL nâng cao — phần mà chúng ta gọi là advanced queries. Đây là ranh giới phân biệt một BA "biết dùng JIRA" với một BA "làm chủ JIRA". Khi bạn viết được một câu JQL gọn gàng thay vì click lọc thủ công 15 phút, bạn vừa tiết kiệm thời gian cho mình, vừa cung cấp được dữ liệu đáng tin cậy cho cả team và quản lý.

Bài 8 tập trung vào ba trụ cột của JQL nâng cao mà BA dùng hằng ngày: date math (tính toán thời gian động), subquery / membership operators (IN, NOT IN, WAS, CHANGED), và JQL functions kết hợp với từ khóa ORDER BY, EMPTY, currentUser(). Chúng ta sẽ không lan sang các function rất chuyên sâu của Bài 38, mà tập trung vào bộ công cụ "đủ mạnh để giải quyết 90% nhu cầu thực tế của BA".

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

1. Date math — truy vấn thời gian động

Điểm yếu lớn nhất của người mới là viết ngày cứng (hard-coded), ví dụ created >= "2026-06-01". Câu này hôm nay đúng, nhưng mai phải sửa tay. JQL cung cấp cú pháp date math cho phép truy vấn tương đối, tự cập nhật mỗi lần chạy.

Các đơn vị thời gian: w (tuần), d (ngày), h (giờ), m (phút). Dấu trừ nghĩa là "trong quá khứ".

created >= -7d AND created <= now()

Câu trên nghĩa là: tất cả issue được tạo trong 7 ngày gần nhất tính tới thời điểm hiện tại. now() là hàm trả về thời điểm chạy query.

JQL còn có nhóm hàm "mốc thời gian" cực hữu ích cho báo cáo theo chu kỳ:

created >= startOfMonth() AND created <= endOfMonth()
created >= startOfWeek() AND created <= endOfWeek()
created >= startOfDay() AND created <= endOfDay()

startOfMonth() trả về 00:00 ngày đầu tháng hiện tại; endOfMonth() trả về cuối ngày cuối tháng. Bạn có thể truyền tham số để dịch chuyển mốc: startOfMonth(-1) là đầu tháng trước, startOfWeek(1) là đầu tuần sau. Đây chính là chìa khóa làm báo cáo "tháng này so với tháng trước" mà không bao giờ phải sửa ngày.

Một mẫu rất hay dùng để soi việc trễ hạn:

due <= 3d AND status != Done

Nghĩa là: những issue có due date trong vòng 3 ngày tới (hoặc đã quá hạn) nhưng chưa hoàn thành — danh sách "cần để mắt gấp".

Và để tìm những issue "bị bỏ quên" — lâu không ai động vào:

updated <= -14d AND status NOT IN (Done, Closed)

2. Membership operators — IN, NOT IN

Thay vì viết status = "To Do" OR status = "In Progress" OR status = "In Review", bạn gói gọn:

status IN ("To Do", "In Progress", "In Review")

NOT IN là phủ định, rất hợp để loại trừ các trạng thái kết thúc:

project = PAY AND status NOT IN (Done, Closed, Cancelled)

IN còn có thể nhận subquery dạng function, ví dụ assignee IN membersOf("ba-team") để lấy mọi người trong nhóm ba-team.

3. Toán tử lịch sử — WAS, CHANGED, WAS IN

Đây là phần "vũ khí bí mật" mà nhiều BA chưa biết. JQL không chỉ truy vấn trạng thái hiện tại của issue, mà còn truy vấn lịch sử thay đổi.

  • WAS: issue đã từng ở trạng thái nào đó.
  • CHANGED: một trường đã từng bị thay đổi.
  • WAS IN / WAS NOT IN: tương tự IN nhưng cho quá khứ.
status WAS "In Review" DURING (startOfMonth(), endOfMonth())
status CHANGED FROM "In Progress" TO "Reopened"
assignee CHANGED AFTER -7d
status CHANGED TO Done BY currentUser() AFTER startOfWeek()

Những câu này trả lời các câu hỏi mà query trạng thái hiện tại không bao giờ trả lời được, ví dụ: "Có bao nhiêu story bị reopened (mở lại) trong sprint này?" — dấu hiệu chất lượng phân tích/đặc tả có vấn đề.

4. EMPTY, IS NULL và currentUser()

EMPTY (hoặc NULL) tìm các trường còn trống — cực hợp để kiểm tra chất lượng dữ liệu:

project = PAY AND fixVersion IS EMPTY AND type = Story
assignee IS EMPTY AND status = "In Progress"

currentUser() trỏ tới chính người đang chạy query, giúp bạn tạo filter dùng chung cho cả team mà mỗi người mở ra lại thấy việc của riêng mình:

assignee = currentUser() AND status != Done ORDER BY priority DESC

5. ORDER BY — sắp xếp kết quả

ORDER BY luôn đứng cuối câu, sắp xếp kết quả theo một hay nhiều trường, với ASC (tăng dần) hoặc DESC (giảm dần):

... ORDER BY priority DESC, due ASC

Tình huống thực tế

Tình huống 1 — Tiki: báo cáo "tuần qua team làm được gì" cho stakeholder

Lan là BA cho team Logistics của một sàn thương mại điện tử lớn (giả định theo bối cảnh Tiki). Mỗi sáng thứ Hai, Product Owner đều hỏi: "Tuần rồi team đóng được những gì, có gì bị mở lại không?". Trước đây Lan phải mở board, lọc tay, đếm từng cái — mất gần 20 phút và hay sót.

Sau khi học JQL nâng cao, Lan tạo hai filter lưu sẵn:

-- Việc đã hoàn thành tuần trước
project = LOGI AND status CHANGED TO Done DURING (startOfWeek(-1), endOfWeek(-1))
ORDER BY resolved DESC

-- Việc bị mở lại (cảnh báo chất lượng) project = LOGI AND status CHANGED FROM Done TO ("To Do", "In Progress") DURING (startOfWeek(-1), endOfWeek(-1))

Diễn giải: startOfWeek(-1)endOfWeek(-1) khoanh đúng tuần trước, tự dịch chuyển mỗi tuần nên Lan không bao giờ phải sửa. Filter thứ hai dùng CHANGED FROM ... TO ... để bắt đúng những issue bị reopened.

Bài học: Date math động + toán tử lịch sử biến một báo cáo thủ công 20 phút thành cú click 5 giây. Quan trọng hơn, con số "số issue reopened" trở thành một chỉ số chất lượng để Lan chủ động đề xuất cải tiến quy trình review.

Tình huống 2 — VPBank: rà soát yêu cầu thiếu dữ liệu trước khi vào sprint

Minh là BA tại một ngân hàng số (bối cảnh giả định kiểu VPBank), phụ trách dự án CARD về phát hành thẻ. Quy ước team: mọi User Story trước khi đưa vào sprint phải có Acceptance Criteria (một custom field), được gán fixVersion, và có Story Points. Nhưng thực tế nhiều story bị bỏ trống các trường này, dẫn tới dev hiểu sai yêu cầu.

Minh viết một query "kiểm soát chất lượng backlog":

project = CARD AND type = Story AND status = "To Do"
AND (
  "Acceptance Criteria" IS EMPTY
  OR fixVersion IS EMPTY
  OR "Story Points" IS EMPTY
)
ORDER BY priority DESC

Diễn giải: IS EMPTY quét đúng các story còn thiếu thông tin. Dùng OR trong ngoặc để bắt story nào thiếu bất kỳ trường nào trong ba trường bắt buộc.

Bài học: JQL không chỉ để tìm việc, mà còn là công cụ data quality gate. Trước mỗi buổi refinement, Minh chạy query này và chỉ những story "sạch dữ liệu" mới được đưa lên bàn. Tỷ lệ story bị dev hỏi lại giảm rõ rệt sau hai sprint.

Tình huống 3 — FPT Software: theo dõi change request quá hạn của nhiều khách hàng

Hương là BA tại một công ty outsourcing lớn (bối cảnh FPT Software), cùng lúc đỡ ba dự án khách hàng: ACME, GLOBE, NEXA. Mỗi tuần có hàng chục change request đổ về và cô cần biết cái nào đang sắp/đã trễ SLA để báo cho khách trước khi họ phàn nàn.

project IN (ACME, GLOBE, NEXA)
AND type = "Change Request"
AND status NOT IN (Done, Rejected, Closed)
AND due <= 3d
AND reporter IN membersOf("client-stakeholders")
ORDER BY due ASC, priority DESC

Diễn giải: project IN (...) gom cả ba dự án trong một query; due <= 3d bắt những CR sắp tới hạn hoặc quá hạn; membersOf("client-stakeholders") đảm bảo chỉ tính các CR do phía khách hàng tạo. ORDER BY due ASC đẩy cái gấp nhất lên đầu.

Bài học: Với BA quản lý nhiều dự án song song, IN + date math + membersOf() cho phép một query duy nhất bao quát toàn bộ danh mục công việc liên-dự-án, thay vì mở từng board một.

Hướng dẫn từng bước

Quy trình xây dựng một advanced query mà tôi khuyên bạn áp dụng:

  • Phát biểu câu hỏi nghiệp vụ bằng tiếng Việt trước. Ví dụ: "Những bug ưu tiên cao của dự án PAY, được tạo trong tháng này, hiện chưa ai nhận." Viết rõ câu hỏi giúp bạn không lạc khi dịch sang JQL.
  • Xác định phạm vi (scope) bằng các điều kiện "cứng": project, type. Bắt đầu bằng project = PAY AND type = Bug.
  • Thêm chiều thời gian bằng date math: AND created >= startOfMonth(). Luôn ưu tiên hàm động thay vì ngày cứng.
  • Thêm điều kiện trạng thái / con người với IN, NOT IN, IS EMPTY, currentUser(): AND priority = High AND assignee IS EMPTY.
  • Cần truy vấn lịch sử? Dùng WAS / CHANGED nếu câu hỏi nhắc tới "đã từng", "bị mở lại", "ai đã chuyển sang".
  • Sắp xếp kết quả bằng ORDER BY đặt ở cuối: ORDER BY priority DESC, created ASC.
  • Chạy thử và soi từng phần. Nếu kết quả lạ, tách query ra: bỏ bớt điều kiện, chạy từng mảnh để biết mảnh nào gây sai.
  • Lưu lại thành Saved Filter khi câu query đã đúng và sẽ dùng lặp lại — đây sẽ là chủ đề kỹ hơn của Bài 35.
Ráp lại, query hoàn chỉnh cho ví dụ trên:

project = PAY AND type = Bug AND created >= startOfMonth()
AND priority = High AND assignee IS EMPTY
ORDER BY created ASC

Lỗi thường gặp & mẹo

Lỗi 1 — Dùng ngày cứng thay vì date math. Viết created >= "2026-06-01" khiến filter "chết" sau khi sang tháng. Mẹo: gần như mọi câu hỏi báo cáo đều nên dùng startOf...() / endOf...() hoặc -Nd.

Lỗi 2 — Quên đặt tên trường có dấu cách trong ngoặc kép. Story Points IS EMPTY sẽ báo lỗi; phải viết "Story Points" IS EMPTY. Quy tắc: tên field hay value có dấu cách hoặc ký tự đặc biệt thì bọc trong dấu nháy đôi.

Lỗi 3 — Nhầm lẫn giữa trạng thái hiện tại và lịch sử. status = Done chỉ lấy issue đang Done; nếu muốn "đã từng Done rồi bị mở lại" thì phải dùng status WAS Done AND status != Done. Rất nhiều BA đếm sai số liệu vì lẫn hai khái niệm này.

Lỗi 4 — Logic AND/OR không có ngoặc. a AND b OR c dễ cho kết quả ngoài ý muốn vì thứ tự ưu tiên. Mẹo: luôn bọc nhóm OR trong ngoặc: a AND (b OR c).

Lỗi 5 — Lạm dụng WAS/CHANGED trên instance lớn. Truy vấn lịch sử nặng hơn truy vấn trạng thái hiện tại; trên dự án hàng chục nghìn issue có thể chậm. Mẹo: luôn thu hẹp bằng project và một mốc thời gian DURING (...) để giới hạn phạm vi quét.

Mẹo vàng: Khi gõ JQL, chuyển sang chế độ Advanced search (gõ JQL trực tiếp) thay vì Basic. JIRA có gợi ý tự động (autocomplete) cho cả tên field lẫn giá trị — vừa nhanh vừa tránh gõ sai tên.

Bài tập thực hành

Hãy thực hành trên một dự án thật (hoặc dùng instance dùng thử của Atlassian). Với mỗi yêu cầu, hãy phát biểu câu hỏi tiếng Việt rồi viết JQL:

  • Tìm tất cả issue của dự án bạn được tạo trong tuần nàychưa ai được gán.
  • Liệt kê mọi Story đã quá hạn (due đã qua) nhưng chưa Done, sắp xếp theo độ ưu tiên giảm dần.
  • Tìm những issue đã từng ở trạng thái "In Review" trong tháng trước (gợi ý: WAS ... DURING (startOfMonth(-1), endOfMonth(-1))).
  • Đếm số issue bị mở lại (status CHANGED FROM Done) trong 30 ngày gần nhất.
  • Tìm các Story thiếu Story Points hoặc thiếu fixVersion ở dự án của bạn.
  • Viết một query "việc của tôi cần làm hôm nay": assignee = currentUser(), chưa Done, có due <= endOfDay().
  • (Thử thách) Một query gộp hai dự án, lấy mọi bug ưu tiên cao được cập nhật trong 3 ngày qua, sắp xếp theo thời gian cập nhật mới nhất.
Sau khi viết xong, hãy lưu hai trong số các query trên thành Saved Filter để dùng lại — bạn sẽ thấy giá trị thật sự khi mở lại chúng vào tuần sau mà không cần sửa gì.

Tóm tắt

JQL nâng cao là bộ kỹ năng phân biệt một BA chủ động với một BA bị động trong JIRA. Hãy nhớ ba trụ cột chính của bài này:

  • Date math (-7d, now(), startOfMonth(), endOfWeek(-1)) giúp query tự cập nhật theo thời gian — không bao giờ viết ngày cứng cho báo cáo định kỳ.
  • Membership & history operators (IN, NOT IN, WAS, CHANGED, WAS IN) cho phép gom nhiều giá trị và đặc biệt là truy vấn lịch sử thay đổi — chìa khóa để đo chất lượng như tỷ lệ reopened.
  • EMPTY / currentUser() / ORDER BY giúp kiểm soát chất lượng dữ liệu, tạo filter cá nhân hóa dùng chung, và trình bày kết quả gọn gàng.
Quy trình áp dụng: phát biểu câu hỏi nghiệp vụ → khoanh scope → thêm chiều thời gian → thêm điều kiện trạng thái/con người → (nếu cần) truy vấn lịch sử → sắp xếp → lưu filter. Khi bạn quen tay, mỗi câu hỏi của stakeholder sẽ được trả lời trong vài giây bằng dữ liệu đáng tin, thay vì những phỏng đoán hay thao tác lọc thủ công mệt mỏi. Ở các bài sau, chúng ta sẽ biến những query này thành Saved Filter (Bài 35) và đưa lên Dashboard (Bài 10) để cả team cùng nhìn thấy.