MongoDB koleksiyonundaki bir nesne dizisindeki yalnızca sorgulanan öğeyi alma


377

Koleksiyonumda aşağıdaki belgelerin bulunduğunu varsayalım:

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

Sorgu yap:

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

Veya

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

Eşleşen belgeyi (Belge 1) , ancak her zaman şu TÜM dizi öğeleriyle döndürür shapes:

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

Ancak, belgeyi (Belge 1) yalnızca aşağıdakileri içeren diziyle almak istiyorum color=red:

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

Bunu nasıl yapabilirim?

Yanıtlar:


416

MongoDB 2.2'nin yeni $elemMatchprojeksiyon operatörü, iade edilen belgeyi yalnızca ilk eşleşen shapesöğeyi içerecek şekilde değiştirmenin başka bir yolunu sunar :

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

İadeler:

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

2.2 aylarında da kullanarak bunu yapabilirsiniz $ projection operatornereye, $bir projeksiyon nesne alan adı sorgudan alanın ilk eşleşen dizi öğesinin dizinini temsil eder. Aşağıdakiler yukarıdakiyle aynı sonuçları döndürür:

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

MongoDB 3.2 Güncellemesi

3.2 sürümünden başlayarak, $filterprojeksiyon sırasında bir diziyi filtrelemek için yeni toplama operatörünü kullanabilirsiniz, bu da sadece birincisi yerine tüm eşleşmeleri dahil etme avantajına sahiptir .

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
    }}
])

Sonuçlar:

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

15
herhangi bir çözüm sadece ilk yerine onunla eşleşen her öğeyi döndürmek istiyorum?
Steve Ng

Korkarım Mongo 3.0 kullanıyorum. X :-(
charliebrownie

@charliebrownie Ardından, diğer yanıtlardan birini kullanın aggregate.
JohnnyHK

bu sorgu yalnızca "şekiller" dizisini döndürür ve diğer alanları döndürmez. Başka alanların nasıl iade edileceğini bilen var mı?
Mark Thien

1
Bu da işe db.test.find({}, {shapes: {$elemMatch: {color: "red"}}});
Paul

97

MongoDB 2.2+ içindeki yeni Toplama Çerçevesi , Harita / Küçültme'ye bir alternatif sunar. $unwindOperatör sizin ayırmak için kullanılabilir shapeseşleştirilebilir belgelerin akışına dizi:

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"
  }}
)

Sonuçlar:

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

6
@JohnnyHK: Bu durumda $elemMatchbaşka bir seçenektir. Buraya $ elemMatch'in işe yaramayacağı bir Google Grubu sorusu yoluyla geldim çünkü yalnızca doküman başına ilk eşleşmeyi döndürüyor.
Stennie

1
Teşekkürler, bu sınırlamanın farkında değildim, bu yüzden bilmek güzel. Yanıtladığınız yorumumu sildiğim için üzgünüm, bunun yerine başka bir cevap göndermeye karar verdim ve insanları karıştırmak istemedim.
JohnnyHK

3
@JohnnyHK: Endişeye gerek yok, şimdi şu soru için üç yararlı cevap var ;-)
Stennie

Diğer araştırmacılar için, buna ek olarak ekledim { $project : { shapes : 1 } }- işe yaramış gibi görünüyor ve ekteki dokümanlar büyükse ve sadece shapesanahtar değerleri görüntülemek istiyorsanız yardımcı olacaktır .
user1063287

2
@calmbird Örneği, ilk $ eşleşme aşamasını içerecek şekilde güncelledim. Daha verimli bir özellik önerisiyle ilgileniyorsanız, izleyebilirim / yükseltebilirim SERVER-6612: MongoDB sorun izleyicisindeki $ elemMatch projeksiyon belirteci gibi bir projeksiyonda birden fazla dizi değeri yansıtmayı destekleyin .
Stennie

30

Bir başka ilginç yol ise MongoDB 2.6'nın yeni toplama özelliklerinden biri olan $ redact kullanmaktır . 2.6 kullanıyorsanız, büyük dizileriniz varsa performans sorunlarına neden olabilecek bir $ çözülmesine ihtiyacınız yoktur.

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

$redact msgstr "dokümanların içeriğini dokümanlarda saklanan bilgilere dayanarak kısıtlar" . Böylece sadece belgenin içinde çalışır . Temel olarak belgenizi yukarıdan aşağıya doğru tarar ve ifiçinde bulunduğunuz durumla eşleşip eşleşmediğini denetler $cond, eşleşme varsa içeriği tutar ( $$DESCEND) veya kaldırır ( $$PRUNE).

Yukarıdaki örnekte, önce $matchtüm shapesdiziyi döndürür ve $ redact diziyi beklenen sonuca indirir.

Not {$not:"$color"}buna da üst dokümanı taramak, çünkü gereklidir ve eğer $redactbir bulmazsa colorüst düzeyde alanını bu dönecektir falsebiz istemiyoruz belgenin tamamını şerit olabilir.


1
mükemmel cevap. Bahsettiğiniz gibi $ unwind çok fazla RAM tüketecek. Yani bu karşılaştırıldığında daha iyi olacak.
manojpt

Kuşkuluyum. Örnekte, "şekiller" bir dizidir. "$ Redact" "şekiller" dizisindeki tüm nesneleri tarar mı ?? Bu performans açısından nasıl iyi olacak ??
manojpt

hepsi değil, ilk maçınızın sonucu. $matchİlk agrega
sahneniz

okkk .. "renk" alanında oluşturulan bir dizin, o zaman bile "şekiller" dizisindeki tüm nesneleri tarayacak ??? Bir dizideki birden çok nesneyi eşleştirmenin etkili bir yolu olabilir ???
manojpt

2
Parlak! $ Eq'nin burada nasıl çalıştığını anlamıyorum. Başlangıçta bıraktım ve bu benim için işe yaramadı. Her nasılsa, eşleşmeyi bulmak için şekiller dizisine bakar, ancak sorgu asla hangi diziye bakılacağını belirtmez. Örneğin, belgelerin şekilleri ve örneğin boyutları varsa; $ eq eşleşmeler için her iki diziye de bakar mı? $ Redact sadece belge içinde 'if' koşuluyla eşleşen bir şey mi arıyor?
Onosa

30

Dikkat: Bu cevap, MongoDB 2.2 ve sonraki sürümlerin yeni özellikleri kullanılmadan önce o zaman alakalı bir çözüm sunar . MongoDB'nin daha yeni bir sürümünü kullanıyorsanız diğer yanıtlara bakın.

Alan seçici parametresi tam özelliklerle sınırlıdır. Bir dizinin bir bölümünü seçmek için kullanılamaz, sadece dizinin tamamını seçmek için kullanılamaz. $ Pozisyon operatörünü kullanmayı denedim , ama işe yaramadı.

En kolay yol, istemcideki şekilleri filtrelemektir .

Doğrudan MongoDB'den doğru çıktıya gerçekten ihtiyacınız varsa , şekilleri filtrelemek için bir harita küçültme kullanabilirsiniz.

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

Eşleşen dizi öğesini kullanarak sorgulamak daha iyi $slicebir dizi önemli nesneyi döndürmek için yararlıdır.

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

$sliceöğenin dizinini bildiğinizde yardımcı olur, ancak bazen hangi dizi öğesinin ölçütlerinizle eşleştiğini istiyorsanız. Eşleşen elemanı işleçle döndürebilirsiniz $.


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

ÇIKTILAR

{

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

12

Mongodb'da bulmak için sözdizimi

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

ve yazdığınız ikinci sorgu, yani

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

bu $elemMatchişlemde sorgu bölümünde işleci kullandığınız halde , bu işleci projeksiyon bölümünde kullanırsanız, istenen sonucu elde edersiniz. Sorgunuzu şu şekilde yazabilirsiniz:

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

Bu size istenen sonucu verecektir.


1
Bu benim için çalışıyor. Ancak, "shapes.color":"red"sorgu parametresinde (find yönteminin ilk parametresi) gerekli olmadığı anlaşılmaktadır. Bunu ile değiştirebilir {}ve aynı sonuçları alabilirsiniz.
Erik Olson

2
@ErikOlson Öneriniz, kırmızı renkte olan tüm belgeyi bulmamız ve projeksiyonu sadece üzerlerine uygulamamız gereken yukarıdaki durumda doğrudur. Ancak diyelim ki birisi mavi renkte olan tüm belgeyi bulmayı gerektiriyorsa, ancak bu şekiller dizisinin yalnızca kırmızı renkte olan öğelerini döndürmesi gerekir. Bu durumda, yukarıdaki sorguya başka biri tarafından da atıfta bulunulabilir ..
Vicky

Bu en kolay gibi görünüyor, ama çalışmasını sağlayamıyorum. Yalnızca ilk eşleşen alt belgeyi döndürür.
newman

8

JohnnyHK'a teşekkürler .

Burada biraz daha karmaşık kullanım eklemek istiyorum.

// 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

Sorguyu çalıştırmanız yeterlidir

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

bu sorgunun çıktısı

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

beklediğiniz gibi renkle eşleşen diziden tam alanı verir: 'kırmızı'.


3

$ projesi ile birlikte daha akıllıca olacak diğer akıllıca eşleşen öğeleri belgedeki diğer öğeleri ile birlikte kulüp olacak.

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

Bunun bir girdi ve çıktı seti ile gerçekleştirildiğini açıklayabilir misiniz?
Alexander Mills

2

Aynı şekilde çoklu

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']]}
     }}
  }}
])

Bu cevap aslında tercih 4.x yoludur: $matcho zaman, uzay azaltmak için $filterbir (giriş alanını üzerine yazarak ne istediğinizi kullanım çıkışını tutmak için $filtersahada shapesetmek $projectiçin arka shapes. Stil not: en iyi olarak alan adını kullanmamayı asdaha sonra birlikte karışıklığa argüman yol açabilir, çünkü $$shapeve $shapeben tercih ederim. zzolarak assahada gerçekten göze çarpan, çünkü.
Buzz Moschetti

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

1
Stack Overflow'a hoş geldiniz! Sınırlı ve anında yardım sağlayabilecek kod snippet'i için teşekkür ederiz. Düzgün bir açıklama, bunun neden problem için iyi bir çözüm olduğunu açıklayarak uzun vadeli değerini büyük ölçüde artıracak ve diğer benzer soruları olan gelecekteki okuyucular için daha yararlı hale getirecektir. Yaptığınız varsayımlar dahil bazı açıklamalar eklemek için lütfen yanıtınızı düzenleyin.
sepehr

1

Toplama işlevini kullanma ve $projectbelgede belirli bir nesne alanı elde etme

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

sonuç:

{
    "_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

Soru 9.6 yıl önce sorulmasına rağmen, bu çok sayıda insana çok yardımcı oldu, ben de onlardan biri oldum. Tüm sorularınız, önerileriniz ve cevaplarınız için herkese teşekkürler. Buradaki cevaplardan birinden alıyorum .. Aşağıdaki yöntemin üst belgedeki diğer alanları da yansıtmak için kullanılabileceğini buldum.

Aşağıdaki belge için, bir çalışanın (emp # 7839) izin geçmişinin 2020 yılı için ayarlanıp ayarlanmadığını bulmak gerekiyordu. İzin geçmişi, üst Çalışan belgesi içinde gömülü bir belge olarak uygulanır.

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
                }
        ]
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.