Chỉ truy xuất phần tử được truy vấn trong một mảng đối tượng trong bộ sưu tập MongoDB


377

Giả sử bạn có các tài liệu sau trong bộ sưu tập của tôi:

{  
   "_id":ObjectId("562e7c594c12942f08fe4192"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"blue"
      },
      {  
         "shape":"circle",
         "color":"red"
      }
   ]
},
{  
   "_id":ObjectId("562e7c594c12942f08fe4193"),
   "shapes":[  
      {  
         "shape":"square",
         "color":"black"
      },
      {  
         "shape":"circle",
         "color":"green"
      }
   ]
}

Làm truy vấn:

db.test.find({"shapes.color": "red"}, {"shapes.color": 1})

Hoặc là

db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1})

Trả về tài liệu trùng khớp (Tài liệu 1) , nhưng luôn có TẤT CẢ các mục trong shapes:

{ "shapes": 
  [
    {"shape": "square", "color": "blue"},
    {"shape": "circle", "color": "red"}
  ] 
}

Tuy nhiên, tôi chỉ muốn lấy tài liệu (Tài liệu 1) với mảng chứa color=red:

{ "shapes": 
  [
    {"shape": "circle", "color": "red"}
  ] 
}

Tôi có thể làm cái này như thế nào?

Câu trả lời:


416

$elemMatchToán tử trình chiếu mới của MongoDB 2.2 cung cấp một cách khác để thay đổi tài liệu được trả về để chỉ chứa phần tử được khớp đầu tiênshapes :

db.test.find(
    {"shapes.color": "red"}, 
    {_id: 0, shapes: {$elemMatch: {color: "red"}}});

Trả về:

{"shapes" : [{"shape": "circle", "color": "red"}]}

Trong 2.2, bạn cũng có thể thực hiện việc này bằng cách sử dụng $ projection operator, trong đó $tên trường đối tượng chiếu đại diện cho chỉ mục của phần tử mảng phù hợp đầu tiên của trường từ truy vấn. Sau đây trả về kết quả tương tự như trên:

db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1});

Cập nhật MongoDB 3.2

Bắt đầu với phiên bản 3.2, bạn có thể sử dụng $filtertoán tử tổng hợp mới để lọc một mảng trong khi chiếu, có lợi ích bao gồm tất cả các kết quả khớp, thay vì chỉ một đầu tiên.

db.test.aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
    {$match: {'shapes.color': 'red'}},
    {$project: {
        shapes: {$filter: {
            input: '$shapes',
            as: 'shape',
            cond: {$eq: ['$$shape.color', 'red']}
        }},
        _id: 0
    }}
])

Các kết quả:

[ 
    {
        "shapes" : [ 
            {
                "shape" : "circle",
                "color" : "red"
            }
        ]
    }
]

15
bất kỳ giải pháp nào nếu tôi muốn nó trả về mọi yếu tố phù hợp với nó thay vì chỉ đầu tiên?
Steve Ng

Tôi sợ rằng tôi đang sử dụng Mongo 3.0.X :-(
charliebrownie

@charliebrownie Sau đó sử dụng một trong những câu trả lời khác sử dụng aggregate.
JohnnyHK

truy vấn này chỉ trả về mảng "hình dạng" và nó sẽ không trả về các trường khác. Bất cứ ai cũng biết làm thế nào để trả lại các lĩnh vực khác?
Đánh dấu Thiên

1
Điều này cũng hoạt động:db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
Paul

97

Khung kết hợp mới trong MongoDB 2.2+ cung cấp một giải pháp thay thế cho Bản đồ / Thu nhỏ. Các $unwindnhà điều hành có thể được sử dụng để tách bạn shapesmảng vào một dòng của tài liệu có thể được xuất hiện:

db.test.aggregate(
  // Start with a $match pipeline which can take advantage of an index and limit documents processed
  { $match : {
     "shapes.color": "red"
  }},
  { $unwind : "$shapes" },
  { $match : {
     "shapes.color": "red"
  }}
)

Kết quả trong:

{
    "result" : [
        {
            "_id" : ObjectId("504425059b7c9fa7ec92beec"),
            "shapes" : {
                "shape" : "circle",
                "color" : "red"
            }
        }
    ],
    "ok" : 1
}

6
@JohnnyHK: Trong trường hợp này, $elemMatchlà một lựa chọn khác. Tôi thực sự đã đến đây bằng câu hỏi của Google Group trong đó $ elemMatch sẽ không hoạt động vì nó chỉ trả về kết quả khớp đầu tiên cho mỗi tài liệu.
Stennie

1
Cảm ơn, tôi đã không nhận thức được giới hạn đó nên rất tốt để biết. Xin lỗi vì đã xóa nhận xét của bạn mà bạn đang phản hồi, tôi quyết định đăng một câu trả lời khác và không muốn làm mọi người nhầm lẫn.
JohnnyHK

3
@JohnnyHK: Đừng lo lắng, hiện tại có ba câu trả lời hữu ích cho câu hỏi ;-)
Stennie

Đối với những người tìm kiếm khác, ngoài việc này tôi cũng đã thử thêm { $project : { shapes : 1 } }- có vẻ như hoạt động và sẽ hữu ích nếu các tài liệu kèm theo lớn và bạn chỉ muốn xem các shapesgiá trị chính.
dùng1063287

2
@calmbird Tôi đã cập nhật ví dụ để bao gồm giai đoạn $ khớp ban đầu. Nếu bạn quan tâm đến đề xuất tính năng hiệu quả hơn, tôi sẽ xem / upvote SERVER-6612: Hỗ trợ chiếu nhiều giá trị mảng trong một phép chiếu như trình xác định chiếu $ elemMatch trong trình theo dõi vấn đề MongoDB.
Stennie

30

Một cách xen kẽ khác là sử dụng $ redact , đây là một trong những tính năng tổng hợp mới của MongoDB 2.6 . Nếu bạn đang sử dụng 2.6, bạn không cần $ relax có thể gây ra vấn đề về hiệu suất nếu bạn có mảng lớn.

db.test.aggregate([
    { $match: { 
         shapes: { $elemMatch: {color: "red"} } 
    }},
    { $redact : {
         $cond: {
             if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]},
             then: "$$DESCEND",
             else: "$$PRUNE"
         }
    }}]);

$redact "Hạn chế nội dung của các tài liệu dựa trên thông tin được lưu trữ trong chính tài liệu đó" . Vì vậy, nó sẽ chỉ chạy bên trong tài liệu . Về cơ bản, nó quét tài liệu của bạn từ trên xuống dưới và kiểm tra xem nó có khớp với ifđiều kiện của bạn không $cond, nếu có trùng khớp, nó sẽ giữ nội dung ( $$DESCEND) hoặc remove ( $$PRUNE).

Trong ví dụ trên, đầu tiên $matchtrả về toàn bộ shapesmảng và $ redact đưa nó xuống kết quả mong đợi.

Lưu ý rằng điều đó {$not:"$color"}là cần thiết, bởi vì nó cũng sẽ quét tài liệu hàng đầu và nếu $redactkhông tìm thấy một colortrường ở cấp cao nhất, điều này sẽ trả về falsecó thể tước toàn bộ tài liệu mà chúng tôi không muốn.


1
câu trả lời hoàn hảo. Như bạn đã đề cập $ relax sẽ tiêu tốn rất nhiều RAM. Vì vậy, điều này sẽ tốt hơn khi so sánh.
manojpt

Tôi có một nghi ngờ. Trong ví dụ này, "hình" là một mảng. "$ Redact" sẽ quét tất cả các đối tượng trong mảng "hình dạng" chứ ?? Làm thế nào điều này sẽ tốt đối với hiệu suất ??
manojpt

không phải tất cả, nhưng kết quả của trận đấu đầu tiên của bạn. Đó là lý do tại sao bạn đặt $matchlàm giai đoạn tổng hợp đầu tiên của mình
anvarik

okkk .. nếu một chỉ mục được tạo trên trường "màu", thậm chí sau đó nó sẽ quét tất cả các đối tượng trong mảng "hình dạng" ??? Đó có thể là cách hiệu quả để khớp nhiều đối tượng trong một mảng ???
manojpt

2
Xuất sắc! Tôi không hiểu làm thế nào $ eq hoạt động ở đây. Tôi đã bỏ nó ban đầu và điều này không làm việc cho tôi. Bằng cách nào đó, nó tìm trong mảng các hình dạng để tìm sự trùng khớp, nhưng truy vấn không bao giờ chỉ định mảng nào cần tìm. Giống như, nếu các tài liệu có hình dạng và, ví dụ, kích thước; $ eq sẽ xem xét trong cả hai mảng cho phù hợp? Có phải $ redact chỉ tìm kiếm bất cứ thứ gì trong tài liệu phù hợp với điều kiện 'nếu'?
Onosa

30

Thận trọng: Câu trả lời này cung cấp một giải pháp có liên quan tại thời điểm đó , trước khi các tính năng mới của MongoDB 2.2 trở lên được giới thiệu. Xem các câu trả lời khác nếu bạn đang sử dụng phiên bản MongoDB mới hơn.

Tham số bộ chọn trường được giới hạn ở các thuộc tính hoàn chỉnh. Nó không thể được sử dụng để chọn một phần của một mảng, chỉ toàn bộ mảng. Tôi đã thử sử dụng toán tử vị trí $ , nhưng nó không hoạt động.

Cách dễ nhất là chỉ lọc các hình dạng trong máy khách .

Nếu bạn thực sự cần đầu ra chính xác trực tiếp từ MongoDB, bạn có thể sử dụng bản đồ thu nhỏ để lọc các hình dạng.

function map() {
  filteredShapes = [];

  this.shapes.forEach(function (s) {
    if (s.color === "red") {
      filteredShapes.push(s);
    }
  });

  emit(this._id, { shapes: filteredShapes });
}

function reduce(key, values) {
  return values[0];
}

res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } })

db[res.result].find()

24

Tốt hơn là bạn có thể truy vấn trong phần tử mảng phù hợp bằng cách sử dụng $sliceđể trả về đối tượng quan trọng trong một mảng.

db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1})

$slicelà hữu ích khi bạn biết chỉ số của phần tử, nhưng đôi khi bạn muốn phần tử mảng nào phù hợp với tiêu chí của bạn. Bạn có thể trả về phần tử phù hợp với $toán tử.


19
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

NGOÀI RA

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}

12

Cú pháp tìm trong mongodb là

    db.<collection name>.find(query, projection);

và truy vấn thứ hai mà bạn đã viết, đó là

    db.test.find(
    {shapes: {"$elemMatch": {color: "red"}}}, 
    {"shapes.color":1})

trong phần này bạn đã sử dụng $elemMatchtoán tử trong phần truy vấn, trong khi nếu bạn sử dụng toán tử này trong phần chiếu thì bạn sẽ nhận được kết quả mong muốn. Bạn có thể viết ra truy vấn của bạn như

     db.users.find(
     {"shapes.color":"red"},
     {_id:0, shapes: {$elemMatch : {color: "red"}}})

Điều này sẽ cung cấp cho bạn kết quả mong muốn.


1
Điều này làm việc cho tôi. Tuy nhiên, dường như "shapes.color":"red"trong tham số truy vấn (tham số đầu tiên của phương thức find) là không cần thiết. Bạn có thể thay thế nó {}và nhận được kết quả tương tự.
Erik Olson

2
@ErikOlson Đề xuất của bạn là đúng trong trường hợp trên, trong đó chúng ta cần tìm tất cả các tài liệu có màu đỏ và chỉ áp dụng phép chiếu trên chúng. Nhưng hãy nói rằng nếu ai đó yêu cầu tìm ra tất cả các tài liệu có màu xanh lam nhưng nó sẽ chỉ trả về các phần tử của mảng hình đó có màu đỏ. Trong trường hợp này, truy vấn trên cũng có thể được tham chiếu bởi người khác ..
Vicky

Đây có vẻ là dễ nhất, nhưng tôi không thể làm cho nó hoạt động được. Nó chỉ trả về văn bản con phù hợp đầu tiên.
newman

8

Cảm ơn JohnnyHK .

Ở đây tôi chỉ muốn thêm một số cách sử dụng phức tạp hơn.

// Document 
{ 
"_id" : 1
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 

{ 
"_id" : 2
"shapes" : [
  {"shape" : "square",  "color" : "red"},
  {"shape" : "circle",  "color" : "green"}
  ] 
} 


// The Query   
db.contents.find({
    "_id" : ObjectId(1),
    "shapes.color":"red"
},{
    "_id": 0,
    "shapes" :{
       "$elemMatch":{
           "color" : "red"
       } 
    }
}) 


//And the Result

{"shapes":[
    {
       "shape" : "square",
       "color" : "red"
    }
]}

7

Bạn chỉ cần chạy truy vấn

db.test.find(
{"shapes.color": "red"}, 
{shapes: {$elemMatch: {color: "red"}}});

đầu ra của truy vấn này là

{
    "_id" : ObjectId("562e7c594c12942f08fe4192"),
    "shapes" : [ 
        {"shape" : "circle", "color" : "red"}
    ]
}

như bạn mong đợi, nó sẽ cung cấp trường chính xác từ mảng khớp với màu: 'đỏ'.


3

cùng với dự án $, các yếu tố kết hợp khôn ngoan khác sẽ phù hợp hơn với các yếu tố khác trong tài liệu.

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)

bạn có thể mô tả rằng điều này thực hiện được với một bộ đầu vào và đầu ra?
Alexander Mills

2

Tương tự như vậy, bạn có thể tìm thấy cho nhiều

db.getCollection('localData').aggregate([
    // Get just the docs that contain a shapes element where color is 'red'
  {$match: {'shapes.color': {$in : ['red','yellow'] } }},
  {$project: {
     shapes: {$filter: {
        input: '$shapes',
        as: 'shape',
        cond: {$in: ['$$shape.color', ['red', 'yellow']]}
     }}
  }}
])

Câu trả lời này thực sự là cách 4.x ưa thích: $matchcắt giảm không gian, sau đó $filtergiữ lại những gì bạn muốn, ghi đè trường nhập (sử dụng đầu ra của $filtertrường shapesđể $projectquay lại shapes. Lưu ý kiểu: tốt nhất không sử dụng tên trường như cuộc astranh cãi vì điều đó có thể dẫn đến sự nhầm lẫn sau này $$shape$shape. Tôi thích zzaslĩnh vực này vì nó thực sự nổi bật.
Buzz Moschetti ngày

1
db.test.find( {"shapes.color": "red"}, {_id: 0})

1
Chào mừng bạn đến với Stack Overflow! Cảm ơn bạn về đoạn mã, có thể cung cấp một số trợ giúp hạn chế, ngay lập tức. Một lời giải thích phù hợp sẽ cải thiện đáng kể giá trị lâu dài của nó bằng cách mô tả lý do tại sao đây là một giải pháp tốt cho vấn đề và sẽ giúp nó hữu ích hơn cho những người đọc tương lai với những câu hỏi tương tự khác. Vui lòng chỉnh sửa câu trả lời của bạn để thêm một số giải thích, bao gồm các giả định bạn đã thực hiện.
sepehr

1

Sử dụng hàm tổng hợp và $projectđể có được trường đối tượng cụ thể trong tài liệu

db.getCollection('geolocations').aggregate([ { $project : { geolocation : 1} } ])

kết quả:

{
    "_id" : ObjectId("5e3ee15968879c0d5942464b"),
    "geolocation" : [ 
        {
            "_id" : ObjectId("5e3ee3ee68879c0d5942465e"),
            "latitude" : 12.9718313,
            "longitude" : 77.593551,
            "country" : "India",
            "city" : "Chennai",
            "zipcode" : "560001",
            "streetName" : "Sidney Road",
            "countryCode" : "in",
            "ip" : "116.75.115.248",
            "date" : ISODate("2020-02-08T16:38:06.584Z")
        }
    ]
}

0

Mặc dù câu hỏi đã được hỏi 9,6 năm trước, nhưng điều này đã giúp ích rất nhiều cho nhiều người, tôi là một trong số họ. Cảm ơn tất cả mọi người cho tất cả các truy vấn, gợi ý và câu trả lời của bạn. Chọn từ một trong những câu trả lời ở đây .. Tôi thấy rằng phương pháp sau đây cũng có thể được sử dụng để chiếu các trường khác trong tài liệu gốc. Điều này có thể hữu ích cho ai đó.

Đối với tài liệu sau đây, cần phải tìm hiểu xem một nhân viên (emp # 7839) có lịch sử nghỉ phép của anh ta cho năm 2020 hay không. Lịch sử nghỉ phép được thực hiện như một tài liệu nhúng trong tài liệu Nhân viên chính.

db.employees.find( {"leave_history.calendar_year": 2020}, 
    {leave_history: {$elemMatch: {calendar_year: 2020}},empno:true,ename:true}).pretty()


{
        "_id" : ObjectId("5e907ad23997181dde06e8fc"),
        "empno" : 7839,
        "ename" : "KING",
        "mgrno" : 0,
        "hiredate" : "1990-05-09",
        "sal" : 100000,
        "deptno" : {
                "_id" : ObjectId("5e9065f53997181dde06e8f8")
        },
        "username" : "none",
        "password" : "none",
        "is_admin" : "N",
        "is_approver" : "Y",
        "is_manager" : "Y",
        "user_role" : "AP",
        "admin_approval_received" : "Y",
        "active" : "Y",
        "created_date" : "2020-04-10",
        "updated_date" : "2020-04-10",
        "application_usage_log" : [
                {
                        "logged_in_as" : "AP",
                        "log_in_date" : "2020-04-10"
                },
                {
                        "logged_in_as" : "EM",
                        "log_in_date" : ISODate("2020-04-16T07:28:11.959Z")
                }
        ],
        "leave_history" : [
                {
                        "calendar_year" : 2020,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                },
                {
                        "calendar_year" : 2021,
                        "pl_used" : 0,
                        "cl_used" : 0,
                        "sl_used" : 0
                }
        ]
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.