MongoDB'de bir dizi alanını değiştirmek için diziyi uygulamada baştan kurup geri yazmanız gerekmez. $push diziye eleman ekler, $addToSet yalnızca dizide olmayan değeri ekler, $pull koşula uyan elemanları çıkarır, $pop ilk ya da son elemanı siler. Var olan elemanları değiştirmek için ise üç konumsal operatör vardır: sorguya uyan ilk eleman için $, tüm elemanlar için $[], arrayFilters koşuluna uyan elemanlar için $[<identifier>].

Bu operatörler updateOne(), updateMany() ve findAndModify() gibi güncelleme yöntemlerinin ikinci parametresinde kullanılır ve tek belge içinde atomik çalışır. Yöntemlerin genel kullanımı, sonuç belgesi ve upsert için updateOne ve updateMany yazısına, dizileri sorgulamak için dizi sorgulama yazısına bakabilirsiniz. Aşağıdaki tüm kurallar MongoDB 9.0 belgelerine dayanır.

$push: diziye eleman eklemek

$push: diziye eleman eklemek: $each, $position, $position, $slice

$push verilen değeri dizinin sonuna ekler. Alan belgede yoksa, değeri tek eleman olarak içeren yeni bir dizi oluşturur. Alan var ama dizi değilse işlem başarısız olur. En sık yapılan hata şudur: $push'a bir dizi verirseniz, dizinin elemanları tek tek değil, dizinin kendisi tek bir eleman olarak eklenir. Birden çok değeri ayrı ayrı eklemek için $each değiştiricisi gerekir.

javascript
// Tek değer ekle
db.gonderiler.updateOne( { _id: 1 }, { $push: { etiketler: "mongodb" } } )

// Dikkat: [ "a", "b" ] dizisi TEK eleman olarak eklenir
db.gonderiler.updateOne( { _id: 1 }, { $push: { etiketler: [ "a", "b" ] } } )

// Doğrusu: $each ile her değer ayrı eleman olur
db.gonderiler.updateOne( { _id: 1 }, { $push: { etiketler: { $each: [ "a", "b" ] } } } )

$each, $position, $slice ve $sort değiştiricileri

$push dört değiştiriciyle birlikte kullanılabilir. $each birden çok değer ekler. $position elemanların hangi konuma ekleneceğini belirler; verilmezse ekleme sona yapılır. $slice güncellemeden sonra dizinin boyunu sınırlar. $sort diziyi sıralar. Belgeye göre $position, $slice ve $sort yalnızca $each ile birlikte kullanılabilir; $each olmadan kullanılan $slice veya $sort hata verir. Yalnızca kırpma ya da sıralama yapmak istiyorsanız $each'e boş dizi ([]) verebilirsiniz.

Değiştiricilerin yazılış sırası önemli değildir; MongoDB bunları her zaman şu sırayla işler: önce elemanları doğru konuma ekler, sonra varsa sıralar, sonra varsa kırpar ve en son diziyi saklar. Bu sıra "sona ekle, puana göre sırala, en iyi beşi tut" gibi işlemleri tek güncellemeyle yapmanızı sağlar.

DeğiştiriciDeğerEtkisi
$eachDeğer dizisiBirden çok elemanı ayrı ayrı ekler
$position0 veya pozitifBaştan o konuma ekler; dizi boyundan büyükse sona ekler
$positionNegatifSondan sayar; -1 son elemandan hemen önce
$slice0Diziyi boşaltır
$slicePozitif nİlk n elemanı tutar
$sliceNegatif nSon n elemanı tutar
$sort1 veya -1Elemanları ya da belgelerin tamamını sıralar
$sort{ alan: 1 }Belge elemanlarını bir alana göre sıralar
javascript
// Başa ekle
db.ogrenciler.updateOne(
   { _id: 1 },
   { $push: { puanlar: { $each: [ 50, 60, 70 ], $position: 0 } } }
)

// Ekle, puana göre azalan sırala, en yüksek 3 sınavı tut
db.ogrenciler.updateOne(
   { _id: 2 },
   { $push: { sinavlar: {
        $each: [ { ad: "vize2", puan: 88 } ],
        $sort: { puan: -1 },
        $slice: 3
   } } }
)

// Yalnızca kırpma: son 10 olayı tut
db.cihazlar.updateOne( { _id: "c1" }, { $push: { olaylar: { $each: [ ], $slice: -10 } } } )

$sort için belge bir uyarı yapar: belge elemanlarını bir alana göre sıralarken sıralama belgesinde dizinin adını kullanmayın. { puan: -1 } doğru, { "sinavlar.puan": -1 } yanlıştır. $push ile $slice birlikteliği, "son N kayıt" türündeki sınırlı dizileri tutmanın standart yoludur ve dizinin sınırsız büyümesini engeller.

$addToSet: yinelenmeyen ekleme

$addToSet değeri yalnızca dizide yoksa ekler; zaten varsa o diziye hiçbir şey yapmaz. Etiket, rol ya da abone listesi gibi küme mantığındaki alanlar için uygundur. Bilmeniz gereken kurallar: $addToSet yalnızca eklenen değerlerin yinelenmemesini garanti eder, dizide zaten var olan yinelenmiş elemanlara dokunmaz. Elemanların belirli bir sırada olacağını garanti etmez. Alan yoksa diziyi oluşturur; alan dizi değilse işlem başarısız olur. Bir dizi verirseniz, $push'ta olduğu gibi dizi tek eleman olarak eklenir; elemanları ayrı ayrı eklemek için $each kullanılır.

javascript
db.envanter.insertOne( { _id: 1, item: "polarizing_filter", tags: [ "electronics", "camera" ] } )

// "accessories" yok, eklenir
db.envanter.updateOne( { _id: 1 }, { $addToSet: { tags: "accessories" } } )

// "camera" zaten var, etkisiz
db.envanter.updateOne( { _id: 1 }, { $addToSet: { tags: "camera" } } )

// Birden çok değer, yalnızca eksik olanlar eklenir
db.envanter.updateOne( { _id: 1 }, { $addToSet: { tags: { $each: [ "camera", "lens", "kit" ] } } } )

Eklenen değer bir belge olduğunda yineleme kontrolü birebirdir: dizideki bir belge ancak aynı alanlara, aynı değerlere ve aynı alan sırasına sahipse yinelenmiş sayılır. Belge, yalnızca bazı alanlara göre karşılaştırma yapılamayacağını da belirtir. Yani { ad: "Ali", rol: "editor" } ile { rol: "editor", ad: "Ali" } farklı belgeler kabul edilir. Belge dizilerinde benzersizlik önemliyse ya alan sırasını uygulamada sabit tutun ya da filtreyle kontrol edin: updateOne( { _id: 1, "uyeler.ad": { $ne: "Ali" } }, { $push: { uyeler: { ad: "Ali", rol: "editor" } } } ).

$pull ve $pullAll: eleman çıkarmak

$pull, değeri ya da koşulu sağlayan tüm elemanları diziden çıkarır. Koşul olarak sorgu operatörleri kullanabilirsiniz: { $pull: { oylar: { $gte: 6 } } } 6 ve üzerindeki tüm oyları siler. Aynı güncellemede birden çok dizi alanını hedefleyebilirsiniz.

javascript
db.profiller.insertOne( { _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] } )
db.profiller.updateOne( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
// Belgeye göre sonuç: { _id: 1, votes: [ 3, 5 ] }

// Bir dizide listedeki değerler, diğerinde tek değer
db.magazalar.updateMany(
   { },
   { $pull: { meyveler: { $in: [ "elma", "portakal" ] }, sebzeler: "havuç" } }
)

Dizi elemanları belge olduğunda $pull koşulu her elemana sanki koleksiyondaki üst düzey bir belgeymiş gibi uygular. Bu yüzden birden çok alanlı koşul için $elemMatch gerekmez; { $pull: { results: { score: 8, item: "B" } } } puanı 8 ve item değeri B olan elemanları çıkarır. Belge burada bir tuzağa dikkat çeker: aynı koşulu $elemMatch ile yazarsanız ({ results: { $elemMatch: { score: 8, item: "B" } } }) hiçbir eleman çıkarılmaz. $elemMatch, yalnızca elemanların içindeki iç içe bir diziye koşul yazarken kullanılır.

javascript
// Doğru: koşul her elemana belge gibi uygulanır
db.anket.updateMany( { }, { $pull: { results: { score: 8, item: "B" } } } )

// Yanlış: hiçbir eleman silinmez
db.anket.updateMany( { }, { $pull: { results: { $elemMatch: { score: 8, item: "B" } } } } )

// İç içe dizi: answers içinde q 2 ve a 8 veya üstü olan sonuçları çıkar
db.anket.updateMany( { }, { $pull: { results: { answers: { $elemMatch: { q: 2, a: { $gte: 8 } } } } } } )

Eşitlik karşılaştırmalarında da kurallar vardır: çıkarılacak değer bir diziyse yalnızca sırası dahil birebir aynı olan elemanlar silinir. Çıkarılacak değer bir belgeyse aynı alanlara ve değerlere sahip elemanlar silinir; $pull için alan sırası farklı olabilir. $pullAll ise koşul değil değer listesi alır ve listedeki değerlerin tüm örneklerini çıkarır; belge ya da dizi değerlerinde sıra dahil birebir eşleşme arar.

javascript
db.anket2.insertOne( { _id: 1, scores: [ 0, 2, 5, 5, 1, 0 ] } )
db.anket2.updateOne( { _id: 1 }, { $pullAll: { scores: [ 0, 5 ] } } )
// Belgeye göre sonuç: { "_id" : 1, "scores" : [ 2, 1 ] }

$pop: ilk veya son eleman

$pop: ilk veya son eleman

$pop, -1 değeriyle dizinin ilk elemanını, 1 değeriyle son elemanını siler. Alan dizi değilse işlem başarısız olur. Son eleman da silinirse alan boş bir dizi olarak kalır. Kuyruk benzeri yapılarda baştan eleman almak için kullanışlıdır.

javascript
db.ogrenciler.insertOne( { _id: 1, scores: [ 8, 9, 10 ] } )
db.ogrenciler.updateOne( { _id: 1 }, { $pop: { scores: -1 } } )
// Belgeye göre sonuç: { _id: 1, scores: [ 9, 10 ] }

Konumsal $ operatörü: eşleşen ilk eleman

Dizide hangi konumda olduğunu bilmediğiniz bir elemanı güncellemek için konumsal $ kullanılır: "dizi.$". $, sorgu belgesine uyan ilk elemanın yerini tutar. Bu yüzden dizi alanının mutlaka sorgu filtresinde yer alması gerekir. Gömülü belge dizilerinde nokta gösterimiyle devam edilir: "notlar.$.std".

javascript
// grades dizisindeki ilk 80 değerini 82 yap
db.ogrenciler.updateOne( { _id: 1, grades: 80 }, { $set: { "grades.$": 82 } } )

// Belge dizisi: grade 85 olan ilk elemanın std alanını 6 yap
db.ogrenciler.updateOne( { _id: 4, "grades.grade": 85 }, { $set: { "grades.$.std": 6 } } )

// Birden çok koşul için $elemMatch ile
db.ogrenciler.updateOne(
   { _id: 5, grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } } },
   { $set: { "grades.$.std": 6 } }
)

Konumsal $'ın belgede listelenen kısıtları şunlardır. Upsert işlemlerinde kullanılmamalıdır: eşleşen belge yoksa $ bir dizi elemanı gerektirdiği için upsert başarısız olur. Birden fazla diziden geçen (iç içe diziler gibi) sorgularda kullanılamaz, çünkü yer tutucunun yerine tek bir değer konur. $unset ile kullanıldığında elemanı silmez, null yapar. Sorgu diziyi $ne, $not veya $nin gibi bir olumsuzlama ile eşliyorsa konumsal operatör kullanılamaz; olumsuz kısım bir $elemMatch içindeyse kullanılabilir. Filtre birden çok dizi alanını içeriyorsa $ yanlış konumu güncelleyebilir.

$[]: tüm elemanlar

$[] (all positional) operatörü, eşleşen belgelerdeki dizinin bütün elemanlarını güncellemek içindir. Belgedeki örnekte { $inc: { "grades.$[]": 10 } } her öğrencinin tüm notlarını 10 artırır. Gömülü belge dizilerinde "dizi.$[].alan" biçimi kullanılır. $[], konumsal $'ın aksine iç içe dizilerde de kullanılabilir.

javascript
db.ogrenciler.insertMany( [
   { "_id" : 1, "grades" : [ 85, 82, 80 ] },
   { "_id" : 2, "grades" : [ 88, 90, 92 ] },
   { "_id" : 3, "grades" : [ 85, 100, 90 ] }
] )
db.ogrenciler.updateMany( { }, { $inc: { "grades.$[]": 10 } } )
// Belgeye göre sonuç:
// { "_id" : 1, "grades" : [ 95, 92, 90 ] }
// { "_id" : 2, "grades" : [ 98, 100, 102 ] }
// { "_id" : 3, "grades" : [ 95, 110, 100 ] }

// Belge dizisi: tüm sepet kalemlerinde indirim alanını sıfırla
db.sepetler.updateMany( { }, { $set: { "kalemler.$[].indirim": 0 } } )

Upsert ile kullanım kısıtlıdır: upsert ekleme ile sonuçlanacaksa, $[] kullanabilmek için sorgunun dizi alanında birebir eşitlik koşulu içermesi gerekir. Örneğin { myArray: [ 5, 8 ] } filtresiyle yapılan upsert, belge yoksa myArray: [ 10, 10 ] içeren bir belge ekler; filtre birebir eşitlik içermiyorsa ve eşleşen belge yoksa işlem hata verir.

$[&lt;identifier&gt;] ve arrayFilters: koşula uyan elemanlar

$[<identifier>] ve arrayFilters: koşula uyan elemanlar: Tanımlayıcı küçük harfle başlamalı ve yalnızca harf ve rakam içermelidir., Güncelleme belgesinde geçmeyen bir tanımlayıcı...

Yalnızca belirli koşulu sağlayan elemanları güncellemek için filtreli konumsal operatör $[<identifier>] ile arrayFilters seçeneği birlikte kullanılır. Güncelleme belgesinde bir tanımlayıcı (örneğin eleman) belirlenir, arrayFilters içinde bu tanımlayıcı için koşul yazılır. Belgedeki örnekte 100 ve üzerindeki tüm notlar 100'e sabitlenir.

javascript
db.ogrenciler.updateMany(
   { },
   { $set: { "grades.$[eleman]": 100 } },
   { arrayFilters: [ { "eleman": { $gte: 100 } } ] }
)
// Yukarıdaki $[] örneğinden sonra çalıştırılırsa, belgeye göre:
// { "_id" : 1, "grades" : [ 95, 92, 90 ] }
// { "_id" : 2, "grades" : [ 98, 100, 100 ] }
// { "_id" : 3, "grades" : [ 95, 100, 100 ] }

Belge dizilerinde tanımlayıcıdan sonra alan adı yazılır: "kalemler.$[k].durum" ve filtrede { "k.adet": { $gt: 0 } }. arrayFilters için belgedeki kurallar şunlardır:

  • Tanımlayıcı küçük harfle başlamalı ve yalnızca harf ve rakam içermelidir.
  • Güncelleme belgesinde geçmeyen bir tanımlayıcı için filtre belgesi yazılamaz.
  • Aynı tanımlayıcı güncellemede birden çok kez geçebilir, ama her farklı tanımlayıcı için tam olarak bir filtre belgesi olmalıdır. Aynı tanımlayıcıya iki ayrı filtre belgesi yazmak geçersizdir; birleşik koşulları tek belgede, gerekirse $or veya $and ile yazın.
  • arrayFilters içinde $expr, $text ve $where kullanılamaz.
  • Filtreli konumsal operatör iç içe dizilerde de kullanılabilir.
javascript
// Geçersiz: aynı tanımlayıcı için iki filtre belgesi
// arrayFilters: [ { "x.a": { $gt: 85 } }, { "x.b": { $gt: 80 } } ]

// Geçerli: koşullar tek belgede
db.notlar.updateMany(
   { },
   { $set: { "sinavlar.$[x].gecti": true } },
   { arrayFilters: [ { "x.a": { $gt: 85 }, "x.b": { $gt: 80 } } ] }
)

Upsert kuralı $[] ile aynıdır: ekleme yapılacaksa sorguda dizi alanı için birebir eşitlik gerekir. Gerekmezse ve belge bulunamazsa belgede gösterilene benzer bir hata döner:

javascript
db.array.updateOne(
   { },
   { $set: { "myArray.$[element]": 10 } },
   { arrayFilters: [ { element: 9 } ], upsert: true }
)
// çıktı: MongoServerError: The path 'myArray' must exist in the document in order to apply array updates.
OperatörHangi elemanlarSorguda dizi gerekir mi?İç içe dizi
$Sorguya uyan ilk elemanEvetHayır
$[]Tüm elemanlarHayırEvet
$[<identifier>]arrayFilters koşuluna uyanlarHayırEvet

Uygulamalı mini örnek: sipariş kalemleri

Bir siparişte kalemler belge dizisi olarak tutuluyor. Önce yeni kalem ekleyip, sonra belirli bir ürünün adedini artırıp, ardından stokta olmayan kalemleri işaretleyip en sonunda iptal edilenleri çıkaralım. Her adım tek bir atomik güncellemedir.

javascript
db.siparis.insertOne( { _id: 101, kalemler: [
   { sku: "A1", adet: 1, stokta: true },
   { sku: "B2", adet: 2, stokta: true }
] } )

// 1. Yeni kalem ekle
db.siparis.updateOne( { _id: 101 }, { $push: { kalemler: { sku: "C3", adet: 1, stokta: false } } } )

// 2. B2 kaleminin adedini 1 artır (konumsal $)
db.siparis.updateOne( { _id: 101, "kalemler.sku": "B2" }, { $inc: { "kalemler.$.adet": 1 } } )

// 3. Stokta olmayan tüm kalemleri beklemeye al (arrayFilters)
db.siparis.updateOne(
   { _id: 101 },
   { $set: { "kalemler.$[k].durum": "beklemede" } },
   { arrayFilters: [ { "k.stokta": false } ] }
)

// 4. Beklemedeki kalemleri siparişten çıkar
db.siparis.updateOne( { _id: 101 }, { $pull: { kalemler: { durum: "beklemede" } } } )

Bu dört adımın sonunda siparişte A1 (adet 1) ve B2 (adet 3) kalemleri kalır. İkinci adımda filtreye "kalemler.sku": "B2" koymak zorunludur; konumsal $ hangi elemanın güncelleneceğini bu koşuldan öğrenir. Üçüncü adımda koşul filtreye değil arrayFilters'a yazıldığı için birden çok eleman aynı anda güncellenebilir.

Sık yapılan hatalar

  • $push veya $addToSet'e dizi vermek: dizi tek eleman olarak eklenir; elemanlar için $each kullanın.
  • $slice, $sort veya $position'ı $each olmadan yazmak: hata verir; gerekirse $each: [] kullanın.
  • $pull içinde $elemMatch kullanmak: belge dizilerinde koşulu doğrudan yazın; $elemMatch yalnızca iç içe diziler için.
  • Konumsal $ için dizi alanını filtreye koymamak: operatör hangi elemanı güncelleyeceğini bilemez.
  • Konumsal $ ile upsert: eşleşme yoksa başarısız olur.
  • $unset ile eleman silmeye çalışmak: eleman null olur; silmek için $pull kullanın.
  • $addToSet'in belge karşılaştırmasında alan sırasını unutmak: farklı sıra farklı belge sayılır.
  • Diziyi sınırsız büyütmek: belge boyutu sınırı 16 mebibayttır; $push ile $slice kullanarak boyu sınırlayın ya da modeli gözden geçirin.

Sık Sorulan Sorular

Diziden belirli bir konumdaki elemanı nasıl silerim?

Doğrudan konumla silen bir operatör yoktur. İlk ya da son eleman için $pop kullanılır. Diğer durumlarda elemanı değerine veya koşuluna göre $pull ile çıkarırsınız. Konumsal $ ile $unset kullanmak elemanı silmez, null yapar.

$push ile $addToSet arasında ne zaman hangisini seçmeliyim?

Sıra önemliyse ve yinelenen değerlere izin veriyorsanız (olay günlüğü, yorum listesi) $push; değerler bir küme gibi davranmalıysa (etiketler, roller) $addToSet kullanın. $addToSet sıra garantisi vermez ve $position, $slice, $sort değiştiricileri yalnızca $push ile kullanılır.

Birden çok belgedeki dizileri aynı anda güncelleyebilir miyim?

Evet, tüm bu operatörler updateMany() ile de çalışır. Her belge kendi içinde atomik güncellenir, ancak işlemin bütünü atomik değildir; ayrıntılar updateOne ve updateMany yazısında. Çok sayıda farklı güncellemeyi tek istekte göndermek için bulkWrite kullanılabilir.

Dizi elemanını başka bir alanın değerine göre değiştirmek mümkün mü?

Bu tür hesaplamalı güncellemeler aggregation boru hattıyla yapılır; $map veya $filter gibi ifadeler $set aşamasında kullanılır. Not: arrayFilters boru hattıyla yapılan güncellemelerde kullanılamaz. Boru hattı aşamaları için $addFields ve $set yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: MongoDB öğrenme rehberi · updateOne, updateMany ve upsert · Dizi sorgulama ve $elemMatch · Veri modelleme · bulkWrite toplu işlemler · $addFields ve $set aşamaları