Như bạn đã đề cập một cách chính xác, có nhiều cách tiếp cận khác nhau với độ phức tạp khác nhau vốn có để thực hiện chúng. Về cơ bản, điều này bao gồm cách chúng được thực hiện và cái nào bạn triển khai thực sự phụ thuộc vào dữ liệu và trường hợp sử dụng của bạn là phù hợp nhất.
Đối sánh phạm vi hiện tại
MongoDB 3.6 $ tra cứu
Cách tiếp cận đơn giản nhất có thể được sử dụng bằng cách sử dụng cú pháp mới của $ tra cứu
toán tử với MongoDB 3.6 cho phép một đường ống
được đưa ra dưới dạng biểu thức để "tự nối" vào cùng một bộ sưu tập. Về cơ bản, điều này có thể truy vấn lại bộ sưu tập cho bất kỳ mục nào có thời gian bắt đầu
"hoặc" thời gian kết thúc
của tài liệu hiện tại nằm giữa các giá trị tương tự của bất kỳ tài liệu nào khác, tất nhiên là không bao gồm tài liệu gốc:
db.getCollection('collection').aggregate([
{ "$lookup": {
"from": "collection",
"let": {
"_id": "$_id",
"starttime": "$starttime",
"endtime": "$endtime"
},
"pipeline": [
{ "$match": {
"$expr": {
"$and": [
{ "$ne": [ "$$_id", "$_id" },
{ "$or": [
{ "$and": [
{ "$gte": [ "$$starttime", "$starttime" ] },
{ "$lte": [ "$$starttime", "$endtime" ] }
]},
{ "$and": [
{ "$gte": [ "$$endtime", "$starttime" ] },
{ "$lte": [ "$$endtime", "$endtime" ] }
]}
]},
]
},
"as": "overlaps"
}},
{ "$count": "count" },
]
}},
{ "$match": { "overlaps.0": { "$exists": true } } }
])
$ lookup
duy nhất
thực hiện "kết hợp" trên cùng một bộ sưu tập cho phép bạn giữ các giá trị "tài liệu hiện tại" cho "_id"
, "starttime"
và "endtime"
các giá trị tương ứng qua "let"
tùy chọn của giai đoạn đường ống. Chúng sẽ có sẵn dưới dạng "biến cục bộ" bằng cách sử dụng $$
tiền tố trong "đường ống"
tiếp theo của biểu thức.
Trong "đường dẫn phụ" này, bạn sử dụng $ match
giai đoạn đường ống và $ expr
toán tử truy vấn, cho phép bạn đánh giá các biểu thức logic của khung tổng hợp như một phần của điều kiện truy vấn. Điều này cho phép so sánh giữa các giá trị khi nó chọn các tài liệu mới phù hợp với các điều kiện.
Các điều kiện chỉ cần tìm "tài liệu đã xử lý" có "_id"
trường không bằng "tài liệu hiện tại", $ và
trong đó "starttime"
$ hoặc
"endtime"
các giá trị của "tài liệu hiện tại" nằm giữa các thuộc tính giống nhau của "tài liệu đã xử lý". Lưu ý ở đây rằng những điều này cũng như $ gte tương ứng
và $ lte
toán tử là "toán tử so sánh tổng hợp"
chứ không phải "toán tử truy vấn"
biểu mẫu, như kết quả trả về được đánh giá bởi $ expr
phải là boolean
trong ngữ cảnh. Đây là những gì mà các toán tử so sánh tổng hợp thực sự làm và nó cũng là cách duy nhất để chuyển các giá trị vào để so sánh.
Vì chúng tôi chỉ muốn "số lượng" kết quả phù hợp, $ count
giai đoạn đường ống được sử dụng để làm điều này. Kết quả của tổng thể $ lookup
sẽ là một mảng "phần tử đơn" có số đếm hoặc "mảng trống" không khớp với các điều kiện.
Một trường hợp thay thế sẽ là "bỏ qua" $ count
và chỉ cần cho phép trả lại các tài liệu phù hợp. Điều này cho phép dễ dàng xác định, nhưng là một "mảng được nhúng trong tài liệu", bạn cần phải lưu ý đến số lượng "chồng chéo" sẽ được trả về dưới dạng toàn bộ tài liệu và điều này không gây ra vi phạm giới hạn BSON là 16MB. Trong hầu hết các trường hợp, điều này sẽ ổn, nhưng đối với những trường hợp bạn mong đợi một số lượng lớn các chồng chéo cho một tài liệu nhất định thì đây có thể là một trường hợp thực tế. Vì vậy, đó thực sự là điều cần lưu ý hơn.
$ lookup
giai đoạn đường ống trong ngữ cảnh này sẽ "luôn luôn" trả về một mảng trong kết quả, ngay cả khi trống. Tên của thuộc tính đầu ra "hợp nhất" vào tài liệu hiện có sẽ là "trùng lặp"
như được chỉ định trong "as"
thuộc tính $ lookup
sân khấu.
Theo dõi $ tra cứu
, sau đó, chúng tôi có thể thực hiện một $ so khớp
đơn giản
với một biểu thức truy vấn thông thường sử dụng $ tồn tại
kiểm tra 0
giá trị chỉ số của mảng đầu ra. Trường hợp thực sự có một số nội dung trong mảng và do đó "chồng chéo" thì điều kiện sẽ đúng và tài liệu được trả về, hiển thị số lượng hoặc tài liệu "chồng chéo" theo lựa chọn của bạn.
Các phiên bản khác - Truy vấn "tham gia"
Trường hợp thay thế mà MongoDB của bạn thiếu hỗ trợ này là "tham gia" theo cách thủ công bằng cách đưa ra cùng một điều kiện truy vấn được nêu ở trên cho mỗi tài liệu được kiểm tra:
db.getCollection('collection').find().map( d => {
var overlaps = db.getCollection('collection').find({
"_id": { "$ne": d._id },
"$or": [
{ "starttime": { "$gte": d.starttime, "$lte": d.endtime } },
{ "endtime": { "$gte": d.starttime, "$lte": d.endtime } }
]
}).toArray();
return ( overlaps.length !== 0 )
? Object.assign(
d,
{
"overlaps": {
"count": overlaps.length,
"documents": overlaps
}
}
)
: null;
}).filter(e => e != null);
Về cơ bản đây là cùng một logic ngoại trừ chúng ta thực sự cần phải "quay lại cơ sở dữ liệu" để đưa ra truy vấn để khớp với các tài liệu chồng chéo. Lần này, đó là "toán tử truy vấn" được sử dụng để tìm vị trí các giá trị tài liệu hiện tại nằm giữa các giá trị của tài liệu đã xử lý.
Bởi vì kết quả đã được trả về từ máy chủ, không có giới hạn BSON giới hạn về việc thêm nội dung vào đầu ra. Bạn có thể bị hạn chế về bộ nhớ, nhưng đó là một vấn đề khác. Nói một cách đơn giản, chúng tôi trả về mảng chứ không phải con trỏ qua .toArray ()
vì vậy chúng tôi có các tài liệu phù hợp và có thể chỉ cần truy cập độ dài mảng để lấy số lượng. Nếu bạn thực sự không cần tài liệu, hãy sử dụng .count ()
thay vì .find ()
hiệu quả hơn nhiều vì không có chi phí tìm nạp tài liệu.
Đầu ra sau đó được hợp nhất đơn giản với tài liệu hiện có, trong đó điểm khác biệt quan trọng khác là vì các luận văn là "nhiều truy vấn" nên không có cách nào đưa ra điều kiện là chúng phải "khớp" với một thứ gì đó. Vì vậy, điều này khiến chúng ta cân nhắc sẽ có kết quả trong đó số lượng (hoặc độ dài mảng) là 0
và tất cả những gì chúng ta có thể làm vào lúc này là trả về null
giá trị mà sau này chúng ta có thể .filter ()
từ mảng kết quả. Các phương pháp lặp con trỏ khác sử dụng cùng một nguyên tắc cơ bản là "loại bỏ" các kết quả mà chúng ta không muốn. Nhưng không có gì ngăn truy vấn được chạy trên máy chủ và quá trình lọc này là "xử lý bài đăng" ở một số hình thức này hay hình thức khác.
Giảm độ phức tạp
Vì vậy, các cách tiếp cận trên hoạt động với cấu trúc như được mô tả, nhưng tất nhiên sự phức tạp tổng thể đòi hỏi rằng đối với mỗi tài liệu, về cơ bản bạn phải kiểm tra mọi tài liệu khác trong bộ sưu tập để tìm kiếm sự chồng chéo. Do đó, trong khi sử dụng $ lookup
cho phép một số "hiệu quả" trong việc giảm chi phí vận chuyển và phản hồi, nó vẫn gặp phải vấn đề tương tự mà về cơ bản bạn vẫn đang so sánh từng tài liệu với mọi thứ.
Giải pháp tốt hơn "nơi bạn có thể làm cho nó phù hợp" thay vào đó là lưu trữ "giá trị cứng" * đại diện cho khoảng thời gian trên mỗi tài liệu. Ví dụ, chúng tôi có thể "giả định" rằng có những khoảng thời gian "đặt trước" chắc chắn là một giờ trong ngày trong tổng số 24 khoảng thời gian đặt phòng. "Có thể" này được đại diện như sau:
{ "_id": "A", "booking": [ 10, 11, 12 ] }
{ "_id": "B", "booking": [ 12, 13, 14 ] }
{ "_id": "C", "booking": [ 7, 8 ] }
{ "_id": "D", "booking": [ 9, 10, 11 ] }
Với dữ liệu được tổ chức như vậy trong đó có một bộ chỉ báo cho khoảng thời gian, độ phức tạp sẽ giảm đi đáng kể vì nó thực sự chỉ là vấn đề "nhóm" trên giá trị khoảng thời gian từ mảng trong "booking"
tài sản:
db.booking.aggregate([
{ "$unwind": "$booking" },
{ "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
{ "$match": { "docs.1": { "$exists": true } } }
])
Và đầu ra:
{ "_id" : 10, "docs" : [ "A", "D" ] }
{ "_id" : 11, "docs" : [ "A", "D" ] }
{ "_id" : 12, "docs" : [ "A", "B" ] }
Điều đó xác định chính xác điều đó cho 10
và 11
khoảng thời gian cả hai "A"
và "D"
chứa chồng chéo, trong khi "B"
và "A"
chồng chéo trên 12
. Các khoảng thời gian và việc khớp tài liệu khác bị loại trừ thông qua cùng một $ tồn tại
ngoại trừ lần này trên 1
chỉ mục (hoặc phần tử mảng thứ hai đang hiện diện) để thấy rằng có "nhiều hơn một" tài liệu trong nhóm, do đó chỉ ra sự chồng chéo.
Điều này chỉ đơn giản là sử dụng $ unwind
giai đoạn đường ống tổng hợp để "giải cấu trúc / không chuẩn hóa" nội dung mảng để chúng ta có thể truy cập các giá trị bên trong để phân nhóm. Đây chính xác là những gì xảy ra trong $ group
giai đoạn mà "khóa" được cung cấp là id khoảng thời gian đặt trước và $ push
toán tử được sử dụng để "thu thập" dữ liệu về tài liệu hiện tại được tìm thấy trong nhóm đó. $ khớp
như đã giải thích trước đó.
Điều này thậm chí có thể được mở rộng cho bản trình bày thay thế:
db.booking.aggregate([
{ "$unwind": "$booking" },
{ "$group": { "_id": "$booking", "docs": { "$push": "$_id" } } },
{ "$match": { "docs.1": { "$exists": true } } },
{ "$unwind": "$docs" },
{ "$group": {
"_id": "$docs",
"intervals": { "$push": "$_id" }
}}
])
Với đầu ra:
{ "_id" : "B", "intervals" : [ 12 ] }
{ "_id" : "D", "intervals" : [ 10, 11 ] }
{ "_id" : "A", "intervals" : [ 10, 11, 12 ] }
Đó là một minh chứng đơn giản hóa, nhưng nếu dữ liệu bạn có sẽ cho phép nó cho loại phân tích cần thiết thì đây là cách tiếp cận hiệu quả hơn nhiều. Vì vậy, nếu bạn có thể giữ "mức độ chi tiết" được cố định thành "đặt" các khoảng thời gian thường được ghi lại trên mỗi tài liệu, thì phân tích và báo cáo có thể sử dụng phương pháp thứ hai để xác định nhanh chóng và hiệu quả các điểm trùng lặp đó.
Về cơ bản, đây là cách bạn sẽ thực hiện những gì bạn đã đề cập về cơ bản như là một cách tiếp cận "tốt hơn" và cách đầu tiên là một cải tiến "nhẹ" so với những gì bạn đã giả thuyết ban đầu. Xem cái nào thực sự phù hợp với tình huống của bạn, nhưng điều này sẽ giải thích việc triển khai và sự khác biệt.