4 Şubat 2021 Perşembe

MongoDB aggregate ve lookup metodu - join İçindir

Giriş
lookup için açıklama şöyle. Sadece unsharded durumda çalışıyor.
Performs a left outer join to an unsharded collection in the same database to filter in documents from the “joined” collection for processing.
Bir başka açıklama şöyle. Sürüm 3.2 ile geliyor.
Until version 3.2 of MongoDB doing joins wasn’t possible, but with that version came the $lookup aggregation stage operator. The introduction of $lookup allows for left outer joins to be performed on collections in the same database which are not sharded.
lookup() eklenmeden önce denormalization tavsiye ediliyordu. Açıklaması şöyle. Eğer Elimizde bir Story nesnesi varsa, bunu kullanan "User Id" ve "User Name" alanları da Story içinde saklanıyordu. Burada User Name saklandığı için İlişkisel veri tabanı kullanmaya alışık kişiler denormalization ihlali olduğunu düşündükleri için itiraz ediyorlardı
Caching to Avoid N+1
When we display our list of stories, we'll need to show the name of the user who posted the story. If we were using a relational database, we could perform a join on users and stores, and get all our objects in a single query. But MongoDB does not support joins and so, at times, requires bit of denormalization. Here, this means caching the 'username' attribute.

A Note on Denormalization

Relational purists may be feeling uneasy already, as if we were violating some universal law. But let’s bear in mind that MongoDB collections are not equivalent to relational tables; each serves a unique design objective. A normalized table provides an atomic, isolated chunk of data. A document, however, more closely represents an object as a whole. In the case of a social news site, it can be argued that a username is intrinsic to the story being posted.
Söz Dizimi
Söz dizimi şöyledir
- from ile karşı collection belirtilir. 
- localField ile kendi collection nesnemdeki alan belirtilir. 
- foreignField ile karşı collection'daki alan belirtilir.
{ $lookup: {
  from: <collection to join>,
  localField: <field from the input documents>,
  foreignField: <field from the documents of the "from" collection>,
  as: <output array field>
} }
Örnek
Elimizde iki tane collection (Orders ve Inventory) olsun. Order nesnesiyle Inventory nesnesini birleştirmek için şöyle yaparız
// A collection Orders contains the following documents:
{ "_id" : 1, "item" : "abc", "price" : 12, "quantity" : 2 }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1 }
{ "_id" : 3  }

// Another collection Inventory contains the following documents:
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
{ "_id" : 2, "sku" : "def", description: "product 2", "instock" : 80 }
{ "_id" : 3, "sku" : "ijk", description: "product 3", "instock" : 60 }
{ "_id" : 4, "sku" : "jkl", description: "product 4", "instock" : 70 }
{ "_id" : 5, "sku": null, description: "Incomplete" }
{ "_id" : 6 }

// The following aggregation operation on the orders collection joins the documents from
// orders with the documents from the inventory collection using the fields item from the
// orders collection and the sku field from the inventory collection:
db.orders.aggregate([
    {
      $lookup:
        {
          from: "inventory",
          localField: "item",
          foreignField: "sku",
          as: "inventory_docs"
        }
   }
])
Çıktı olarak şunu alırız
// Returns these documents
{
  "_id" : 1,
   "item" : "abc",
  "price" : 12,
  "quantity" : 2,
  "inventory_docs" : [
    { "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
  ]
}
{
  "_id" : 2,
  "item" : "jkl",
  "price" : 20,
  "quantity" : 1,
  "inventory_docs" : [
    { "_id" : 4, "sku" : "jkl", "description" : "product 4", "instock" : 70 }
  ]
}
{
  "_id" : 3,
  "inventory_docs" : [
    { "_id" : 5, "sku" : null, "description" : "Incomplete" },
    { "_id" : 6 }
  ]
}
Örnek
Elimizde bir Product olsun. Her Product'in içinde items dizisi var.
[
  {
   "_id": "789",
   "product_name": "test product1",
   "items":[
      {
         "price": 55,
         "image": "default.png",
         "category":[
             "123",
          ],
      }
    ]
  }
...
]
Elimizde bir ProductCategory olsun
[
  {
   "_id": "123",
   "name": "sample category 1"
  },
  {
   "_id": "456",
   "name": "sample category 2"
  },
]
Her Product'ın içindeki item nesnesinin ait olduğu category'i istelim. Şöyle yaparız. Burada unwind items ile bu dizi dolaşılır, lookup ile item'a ait category çekilir.
Product.aggregate([
  { "$unwind": "$items" },
  { "$lookup": {
    "from": "product_category",
    "localField": "items.category",
    "foreignField": "_id",
    "as": "items.category"
  }},
  { "$group": {
    "_id": "$_id",
    "items": { "$push": "$items" }
  }}
])
Çıktı olarak şunu alırız
[
  {
   "_id": "789",
   "product_name": "test product1",
   "items":[
      {
         "price": 55,
         "image": "default.png",
         "category":[
            {
              "_id": "123",
              "name": "sample category 1"
            } 
          ]
      }
    ]
  }
...
]
Örnek
Elimizde şöyle bir veri olsun. Burada Tag yani etiketler var. Her tag'in ismi ve size alanı var. tag1 isimli etiketin size alanı m, s gibi değerlere olabiliyor. Yani birden fazla tag1 etiketi var.
// collections
db={
  Tag: [
    {
      "_id": 1,
      "name": "task",
      "size": "m",
      "tag": "tag1"
    },
    {
      "_id": 2,
      "name": "task",
      "size": "m",
      "tag": "tag1"
    },
    {
      "_id": 3,
      "name": "task",
      "size": "s",
      "tag": "tag1"
    },
    {
      "_id": 4,
      "name": "task",
      "size": "s",
      "tag": "tag2"
    },
    {
      "_id": 5,
      "name": "task",
      "size": "xl",
      "tag": "tag1"
    }
  ],
  UserTag: [
    {
      user: 1,
      tag: "tag1"
    },
    {
      user: 2,
      // pipeline doesn't care if a user has multiple tags
      tag: [
        "tag2",
        "tag1"
      ]
    }
  ]
}
2 numaralı kullanıcının etiketlerini çekmek için şöyle yaparız. Burada ilk $group işlemiyle her bir tag + size için gruplama yapılıyor ve count ile kaç tane oldukları sayılıyor. Daha sonra ikinci gruplamayla ilk gruplamanın çıktısı sadece tag alanına göre gruplanıyor ve size + count değerleri dizi şeklinde toplanıyor
db.UserTag.aggregate([
  { // match user
    $match: {
      user: 2
    }
  },
  { // lookup each of the users tags from the tag collection
    $lookup: {
      from: "Tag",
      localField: "tag",
      foreignField: "tag",
      as: "tags"
    }
  },
  {
    $unwind: "$tags"
  },
  { // group by the tag and size, sum results
    $group: {
      _id: {
        tag: "$tags.tag",
        size: "$tags.size"
      },
      count: {
        $sum: 1
      }
    }
  },
  {
    $group: {
      // group just by tag, send sizes into an array with their counts
      // can hardcode this to just extract the 4 types of course
      _id: "$_id.tag",
      sizes: {
        $push: {
          size: "$_id.size",
          count: "$count"
        }
      }
    }
  }
])
Çıktı olarak şunu alırız. Her bir tag ismi ayrı ayrı neslerdir. Bu tag'lerin size değerine göre kaç tane oldukları da sizes dizisinde görülebilir.
// output
[
  {
    "_id": "tag2",
    "sizes": [
      {
        "count": 1,
        "size": "s"
      }
    ]
  },
  {
    "_id": "tag1",
    "sizes": [
      {
        "count": 1,
        "size": "s"
      },
      {
        "count": 1,
        "size": "xl"
      },
      {
        "count": 2,
        "size": "m"
      }
    ]
  }
]

Hiç yorum yok:

Yorum Gönder