Mongodb lookup array nhưng vẫn đảm bảo thứ tự

Vấn đề

Trong mongodb aggregation framework, lệnh $lookup không đảm bảo thứ tự đầu ra sẽ giống đầu vào.

Vd: mình có 2 collections: usersquests.

Mỗi user sẽ có 1 mảng các quest_id chứa các nhiệm vụ mà user đó đã hoàn thành.

Mỗi khi user hoàn thành 1 quest, ta sẽ thêm quest đó vào đầu mảng, để khi lấy ra thì các quest làm gần nhất sẽ lên đầu.

quests collection:

users collection:

Với dữ liệu hiện tại, user đã hoàn thành B_quest xong đến A_quest.

Bây giờ thực hiện lệnh $lookup để lấy thông tin ra:

Kết quả:

Đến đây thì mọi thứ đều ổn. Cho đến khi user Binh CAO hoàn thành thêm C_quest, ta tiến hành add vào đầu mảng:

Được kết quả:

OK, bây giờ thực hiện $lookup, vẫn lệnh cũ quen thuộc:

Kết quả bất ngờ:

Ố ồ, sao vậy hè, đáng lý C_quest phải nằm trên cùng chứ nhỉ???

Giờ sao đây?

Mình nghi là do Mongodb nó index lại thời gian item được gắn vào mảng, và khi $lookup thì nó sẽ tìm theo đúng thứ tự thời gian đó (khi bạn thêm C_quest vào vị trí nào trong mảng thì nó cũng sẽ ra kết quả như trên). Tất nhiên, mình suy đoán thôi chứ chưa có câu trả lời chính thức từ Mongodb nhé.

Giải quyết

Sau 1 thời gian trăn trở, vò đầu bứt tóc móc mắt, móc mũi, cuối cùng mình nghĩ ra 1 cách, dù nó không phải cách chính thức nhưng cuối cùng cũng đạt được 2 mục đích mình đề ra:

  • lookup theo đúng thứ tự trong mảng
  • Mọi thứ làm ở tầng db hết, không làm gì trên tầng application

Quy trình

  1. Dùng hàm $unwind để xẻ mảng quests ra, bây giờ ta sẽ được 3 object, mỗi object chứa 1 quest._id duy nhất, và được đúng theo thứ tự trong mảng ban đầu
  2. Thực hiện $lookup như thường: đến lúc này, các quest object sẽ được nhúng vào 3 user object ban đầu, nên tiếp theo
  3. Dùng hàm $group để gom bọn nó lại 1 chỗ: các user object này thực chất là 1, nên mình group theo _id là xong.
  4. Các trường khác của users thì dùng từ khóa first để pick ra. Riêng thằng mảng quests thì mình sẽ $push thằng đầu tiên vào với từ khóa $arrayElemAt

Thực hiện

Kết quả:

Tada! Để hiểu rõ hơn, bạn cứ thử lần lượt bỏ từng lệnh từ dưới lên rồi chạy thử, sẽ thấy điều kì diệu xảy ra 😀

Thêm chút nữa

Mình có đem vấn đề này lên hỏi trên trang jira của Mongodb nhưng họ vẫn không có câu trả lời chính thức. Họ cũng không có cách native nào để giải quyết vấn đề này.

Có 1 anh trên đó đưa ra 1 giải pháp chắp vá nhưng nó cũng tương tự như của mình, mời bạn tham khảo: https://jira.mongodb.org/browse/SERVER-30385

Lập trình và hơn thế nữa

Spread the love
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Be the First to Comment!

Notify of
avatar