MongoDB aggregation pipeline'da belgelerin şeklini değiştirmek için dört aşama kullanılır: $addFields ve onun takma adı $set mevcut alanları koruyup yeni alan ekler; $project hangi alanların bir sonraki aşamaya geçeceğini belirler ve hesaplanmış alan üretebilir; $unset ise belirtilen alanları çıkarır. Kısacası alan eklemek için $set, çıktıyı daraltmak için $project, birkaç alanı atmak için $unset en okunur seçimdir.
Bu yazıda dört aşamanın kurallarını, birbirleriyle ilişkilerini, ifade (expression) operatörleriyle hesaplama yapmayı, $$REMOVE değişkenini ve özellikle $project aşamasında sık karşılaşılan hata mesajlarının nedenlerini anlatıyoruz. Pipeline'ın genel yapısı için aggregation pipeline yazısına göz atabilirsiniz.
Örnek veri
db.kitaplar.insertMany( [
{ _id: 1, baslik: "Deniz Feneri", fiyat: 120, kdv: 0.10,
yazar: { ad: "Selin", soyad: "Kaya" }, etiketler: [ "roman", "deniz" ] },
{ _id: 2, baslik: "Kuzey Yolu", fiyat: 80, kdv: 0.10,
yazar: { ad: "Can", soyad: "Demir" } }
] )İkinci belgede etiketler alanı yok. Bu ayrıntı, aşağıda $size gibi operatörlerin eksik alanlarda nasıl davrandığını göstermek için önemli olacak.
$addFields ve $set: mevcut belgeye alan eklemek

$addFields, girdi belgesindeki bütün alanları korur ve verdiğiniz yeni alanları ekler. Belgelere göre bu aşama, mevcut bütün alanları tek tek dahil edip yenilerini ekleyen bir $project ile eşdeğerdir. $set aşaması ise $addFields için bir takma addır; ikisi arasında davranış farkı yoktur. Günlük kullanımda daha kısa olduğu için $set tercih edilebilir.
db.kitaplar.aggregate( [
{ $set: {
kdvliFiyat: { $multiply: [ "$fiyat", { $add: [ 1, "$kdv" ] } ] },
tamAd: { $concat: [ "$yazar.ad", " ", "$yazar.soyad" ] },
"yazar.ulke": "TR"
} }
] )Bu sorgu her kitaba KDV dahil fiyatı ve yazarın tam adını ekler, ayrıca gömülü yazar belgesine nokta gösterimiyle ulke alanını yazar. Değerler bir aggregation ifadesi olabilir: "$fiyat" gibi dolar işaretiyle başlayan dizeler alan yoludur, "TR" gibi düz dizeler sabittir.
Dikkat edilecek kurallar
- Yeni alanın adı mevcut bir alanla aynıysa (
_iddahil), mevcut değer ifadenin sonucuyla üzerine yazılır. Bu, bir alanı yerinde güncellemenin de yoludur. - Bir pipeline'da birden fazla
$addFieldsveya$setaşaması kullanabilirsiniz. Belgelerdeki örnek de film süresini önce bir aşamada saate çevirir, lisans ücretini ise ikinci bir$addFieldsaşamasında bu yeni alandan hesaplar. Ara sonuca dayanan hesapları bu şekilde ayrı aşamalara bölmek okunurluğu artırır. - Var olan bir diziye eleman eklemek için
$concatArrayskullanılır; alan adı aynı tutulursa dizi genişletilmiş haliyle yeniden yazılır. $$REMOVEdeğişkenine çözümlenen bir alan çıktıdan çıkarılır. Böylece$addFieldsile koşullu alan silme yapılabilir.- İç içe nesne de yazabilirsiniz:
{ $addFields: { a: { b: { } } } }geçerlidir.
db.kitaplar.aggregate( [
{ $set: {
etiketler: { $concatArrays: [ { $ifNull: [ "$etiketler", [ ] ] }, [ "stokta" ] ] },
kdv: { $cond: { if: { $gte: [ "$fiyat", 100 ] }, then: "$kdv", else: "$$REMOVE" } }
} }
] )İlk ifade, etiketler alanı eksik olan belgelerde $ifNull sayesinde boş diziden başlar ve her kitaba stokta etiketini ekler. $ifNull eksik alanları ve tanımsız değerleri de null gibi ele alır. İkinci ifade, fiyatı 100'ün altındaki kitaplarda kdv alanını $$REMOVE ile çıktıdan siler. $cond her iki yazımda da üç bölümün (if, then, else) tamamını ister; birini atlarsanız hata alırsınız.
$set aşaması ile $set güncelleme operatörü aynı şey değil
Aynı adı taşısalar da aggregation'daki $set aşaması ile updateOne içinde kullanılan $set güncelleme operatörü farklı belgelenmiş iki ayrı araçtır. Güncelleme operatörü bir alanı sabit değere ayarlar (updateOne ve updateMany yazısında anlatılıyor). Aggregation aşaması ise ifade değerlendirir: başka alanlardan hesap yapabilir, koşul kurabilir.
İki dünya, güncelleme komutuna dizi biçiminde bir pipeline verdiğinizde birleşir. Belgelere göre güncelleme pipeline'larında yalnızca şu aşamalar desteklenir: $addFields, $set, $project, $unset, $replaceRoot ve $replaceWith.
db.kitaplar.updateMany(
{ },
[ { $set: { kdvliFiyat: { $multiply: [ "$fiyat", { $add: [ 1, "$kdv" ] } ] } } } ]
)Köşeli parantezler bu güncellemeyi pipeline güncellemesi yapar; bu sayede kdvliFiyat her belgenin kendi fiyat ve kdv değerinden hesaplanır. Köşeli parantez yoksa ikinci argüman sıradan bir güncelleme belgesi olarak ele alınır; başka alanlardan değer hesaplayan ifadeler ancak pipeline biçiminde kullanılabilir.
$project: çıktının şeklini belirlemek

$project, belgeyi istediğiniz alanlara indirger ve aynı anda yeni alanlar hesaplayabilir. Kabul ettiği belirtim biçimleri şunlardır:
| Biçim | Anlamı |
|---|---|
| alan: 1 veya true | Alanı çıktıya dahil et (sıfır olmayan tamsayılar da true sayılır) |
| _id: 0 veya false | _id alanını çıktıdan çıkar |
| alan: ifade | Yeni alan ekle veya mevcut alanın değerini yeniden belirle |
| alan: 0 veya false | Alanı çıkar; bu durumda başka bir belirtim biçimi kullanılamaz |
db.kitaplar.aggregate( [
{ $project: {
_id: 0,
baslik: 1,
yazarSoyadi: "$yazar.soyad",
etiketSayisi: { $size: { $ifNull: [ "$etiketler", [ ] ] } },
indirimde: { $literal: true }
} }
] )Bu sorgu yalnızca başlığı, yazarın soyadını, etiket sayısını ve sabit indirimde alanını döndürür. Birkaç ayrıntı dikkat ister:
_idvarsayılan olarak her zaman çıktıdadır; istemiyorsanız açıkça_id: 0yazmalısınız.yazarSoyadi: "$yazar.soyad"bir alanı yeni adla göstermenin, yani yeniden adlandırmanın yoludur.$size, argümanı eksikse veya dizi değilse hata verir. İkinci kitaptaetiketlerolmadığı için$ifNullile boş dizi vermezsek sorgu hata ile durur.- Sayısal veya mantıksal bir sabiti doğrudan yazarsanız
$projectonu dahil etme veya çıkarma bayrağı sanır.indirimde: truebir sabit değil, alanı dahil etme isteğidir; sabit için$literalgerekir. - Belgede olmayan bir alanı dahil etmek isterseniz
$projectbunu sessizce yok sayar ve alanı eklemez.
Dahil etme ile çıkarmayı karıştırmak
$project aşamasındaki en yaygın hata, aynı aşamada hem alan dahil etmek hem de _id dışında bir alanı çıkarmaktır. Belgelere göre _id dışındaki bir alanı 0 ile çıkardığınızda başka hiçbir belirtim biçimi kullanamazsınız: alan dahil edemez, değer değiştiremez, yeni alan ekleyemezsiniz. Bu kural $$REMOVE ile yapılan koşullu çıkarma için geçerli değildir.
// Geçersiz: dahil etme (baslik: 1) ile çıkarma (fiyat: 0) bir arada
db.kitaplar.aggregate( [ { $project: { baslik: 1, fiyat: 0 } } ] )
// Geçerli: yalnızca çıkarma
db.kitaplar.aggregate( [ { $project: { fiyat: 0, kdv: 0 } } ] )Gömülü alanlar ve yol çakışması
Gömülü bir alanı nokta gösterimiyle ("yazar.ad": 1) veya iç içe belgeyle (yazar: { ad: 1 }) belirtebilirsiniz. İç içe biçimi seçtiyseniz içeride ayrıca nokta kullanamazsınız; yazar: { "bilgi.ad": 1 } geçersizdir. Aynı projeksiyonda hem bir gömülü belgeyi hem de onun içindeki bir alanı belirtmek de yasaktır: { yazar: 1, "yazar.ad": 1 } bir Path collision hatası üretir ve alanların sırası sonucu değiştirmez.
Diğer kurallar
$projectaşamasına boş belge ({ }) verirseniz MongoDB hata döndürür.$projectiçinde dizi indeksi kullanamazsınız;"etiketler.0": 1ile ilk elemanı seçemezsiniz. Bunun için$arrayElemAtgibi bir ifade kullanın.- Köşeli parantezle doğrudan yeni dizi alanı oluşturabilirsiniz:
{ $project: { ikili: [ "$baslik", "$fiyat" ] } }. Belgede olmayan alanlara başvuran elemanların yerinenullkonur.
$project aşamasını nereye koymalı?
Belgeler, $project aşamasının genellikle pipeline'ın son aşaması olmasını, yani istemciye dönecek alanları belirlemek için kullanılmasını önerir. Pipeline'ın başında veya ortasında alan sayısını azaltmak için $project eklemek büyük olasılıkla performansı artırmaz, çünkü veritabanı bu iyileştirmeyi kendisi otomatik olarak yapar. Yani $project aşamasını bir hız aracı olarak değil, çıktının sözleşmesini tanımlayan bir araç olarak düşünün.
$unset aşaması: alan çıkarmanın kısa yolu
$unset, belirttiğiniz alanları belgeden çıkarır. Tek alan için dize, birden fazla alan için dizi alır ve gömülü alanlar için nokta gösterimini destekler. Belgelere göre $unset, alanları 0 ile çıkaran bir $project aşamasının takma adıdır.
db.kitaplar.aggregate( [
{ $match: { _id: 2 } },
{ $unset: [ "kdv", "yazar.soyad" ] }
] )
// çıktı: { _id: 2, baslik: "Kuzey Yolu", fiyat: 80, yazar: { ad: "Can" } }Çıktıda kdv alanı ve gömülü yazar belgesindeki soyad alanı yok; diğer her şey olduğu gibi kaldı. Belge dizisi içindeki bir alanı da aynı yolla çıkarabilirsiniz: "kopyalar.depo" gibi bir yol, dizideki her gömülü belgeden depo alanını siler.
Aggregation'daki $unset aşaması da $set gibi aynı adlı güncelleme operatöründen farklıdır: güncelleme operatörü veritabanındaki belgeden alanı kalıcı olarak siler, aggregation aşaması ise yalnızca pipeline çıktısını değiştirir ve veriye dokunmaz.
Hangisini ne zaman kullanmalı?

| Aşama | Diğer alanlar | Hesaplama | Tipik kullanım |
|---|---|---|---|
| $addFields | Korunur | Evet | Mevcut belgeye hesaplanmış alan eklemek |
| $set | Korunur | Evet | $addFields ile aynı; kısa yazım |
| $project | Yalnızca belirtilenler kalır | Evet | Pipeline sonunda çıktı şeklini belirlemek |
| $unset | Belirtilenler dışında korunur | Hayır | Hassas veya gereksiz alanları atmak |
Belgeler, alan çıkarmak için hem $addFields ($$REMOVE ile) hem de $project kullanılabileceğini ve en iyi seçimin orijinal belgenin ne kadarını korumak istediğinize bağlı olduğunu söyler. Belgenin çoğunu tutup birkaç alanı atacaksanız $unset, birkaç alanı tutup gerisini atacaksanız $project daha kısa ve okunur olur.
MongoDB 9.0 ile gelen bellek ölçümü
MongoDB 9.0 sürüm notlarına göre klasik sorgu motoru, explain çıktısında executionStats ve üzeri ayrıntı düzeyinde expressionEvaluationPeakMemoryBytes alanını raporluyor. Bu alanın raporlandığı aşamalar arasında $project, $addFields, $set ve $unset da var. Aynı sürümde tek bir ifade değerlendirmesinin kullanabileceği bellek internalQueryMaxSingleExpressionMemoryUsageBytes parametresiyle (varsayılan 100 megabayt) ve tek bir sorgu işleminin toplam belleği ayrı bir parametreyle sınırlanıyor. Çok büyük dizileri birleştiren veya dizeleri art arda ekleyen ifadeler yazıyorsanız bu sınırları MongoDB 9.0 yenilikleri yazısında inceleyin; planı ölçmek için explain() rehberine bakın.
Sık yapılan hatalar
- Dahil etme ve çıkarmayı karıştırmak:
{ baslik: 1, fiyat: 0 }geçersizdir; yalnızca_id: 0dahil etmeyle birlikte kullanılabilir. - Sabit yerine bayrak yazmak:
$projectiçindealan: 1sabit 1 değil, dahil etme isteğidir. Sabit için$literalkullanın. - Eksik dizide $size: alan eksikse hata alırsınız;
$ifNullile varsayılan dizi verin. - $concat ile eksik alan: argümanlardan biri
nullise veya eksik bir alana başvuruyorsa$concatsonucunullolur. Gerekirse her parçayı$ifNullile sarın. - Pipeline güncellemesinde köşeli parantezi unutmak: hesaplanmış güncelleme için ikinci argüman dizi olmalıdır.
- Aynı adlı alanı farkında olmadan ezmek:
$setmevcut alanın üzerine yazar, uyarı vermez.
Sık Sorulan Sorular
$addFields ile $set arasında performans farkı var mı?
Belgelere göre $set, $addFields aşamasının takma adıdır; aynı aşamanın iki adıdır. Hangisini seçtiğiniz okunurluk tercihidir.
$project ile alanları erken atmak sorguyu hızlandırır mı?
Belgeler bunun büyük olasılıkla performansı iyileştirmeyeceğini, çünkü veritabanının gereksiz alanları zaten otomatik olarak ayıkladığını belirtir. $project aşamasını çıktıyı tanımlamak için pipeline sonunda kullanın; gerçek hız kazancı genellikle erken $match ve doğru indeksten gelir.
Bir alanı yeniden adlandırmanın en kolay yolu nedir?
Aggregation içinde yeni alanı eski alanın yoluyla tanımlayıp eskisini çıkarabilirsiniz: { $set: { yeniAd: "$eskiAd" } } ardından { $unset: "eskiAd" }. Veritabanındaki belgeleri kalıcı olarak değiştirmek istiyorsanız güncelleme tarafındaki $rename operatörünü kullanın; o konu güncelleme yazısının kapsamında.
Hesaplanmış alanları bir koleksiyona kaydedebilir miyim?
Evet. Pipeline sonunda $merge veya $out kullanarak sonuçları yazabilir ya da pipeline güncellemesiyle alanı belgelerin kendisine ekleyebilirsiniz. Ayrıntılar için $out ve $merge 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 · Aggregation pipeline · $group ve accumulator · $lookup ile join · updateOne ve updateMany · $out ve $merge