sql >> Databáze >  >> NoSQL >> MongoDB

$vyhledat více úrovní bez $unwind?

Existuje samozřejmě několik přístupů v závislosti na dostupné verzi MongoDB. Ty se liší podle různých použití $lookup až po povolení manipulace s objekty na .populate() výsledek přes .lean() .

Žádám vás, abyste si pečlivě přečetli sekce a uvědomte si, že při zvažování vašeho implementačního řešení nemusí být vše tak, jak se zdá.

MongoDB 3.6, "vnořený" $lookup

S MongoDB 3.6 $lookup operátor získá další možnost zahrnout pipeline výraz na rozdíl od jednoduchého spojení „místní“ k „cizí“ hodnotě klíče, to znamená, že můžete v podstatě provést každý $lookup jako „vnořené“ do těchto výrazů kanálu

Venue.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
  { "$lookup": {
    "from": Review.collection.name,
    "let": { "reviews": "$reviews" },
    "pipeline": [
       { "$match": { "$expr": { "$in": [ "$_id", "$$reviews" ] } } },
       { "$lookup": {
         "from": Comment.collection.name,
         "let": { "comments": "$comments" },
         "pipeline": [
           { "$match": { "$expr": { "$in": [ "$_id", "$$comments" ] } } },
           { "$lookup": {
             "from": Author.collection.name,
             "let": { "author": "$author" },
             "pipeline": [
               { "$match": { "$expr": { "$eq": [ "$_id", "$$author" ] } } },
               { "$addFields": {
                 "isFollower": { 
                   "$in": [ 
                     mongoose.Types.ObjectId(req.user.id),
                     "$followers"
                   ]
                 }
               }}
             ],
             "as": "author"
           }},
           { "$addFields": { 
             "author": { "$arrayElemAt": [ "$author", 0 ] }
           }}
         ],
         "as": "comments"
       }},
       { "$sort": { "createdAt": -1 } }
     ],
     "as": "reviews"
  }},
 ])

To může být opravdu docela silné, jak vidíte z pohledu původního kanálu, ve skutečnosti ví pouze o přidávání obsahu do "reviews" pole a pak každý následující „vnořený“ výraz potrubí také vždy vidí své „vnitřní“ prvky ze spojení.

Je výkonný a v některých ohledech může být o něco jasnější, protože všechny cesty pole jsou relativní k úrovni vnoření, ale začíná to odsazení ve struktuře BSON a musíte si být vědomi toho, zda odpovídáte polím. nebo singulárních hodnot při procházení strukturou.

Všimněte si, že zde můžeme také dělat věci, jako je "zploštění vlastnosti autora", jak je vidět v "comments" položky pole. Vše $lookup cílovým výstupem může být „pole“, ale v rámci „sub-pipeline“ můžeme toto pole jednotlivých prvků přetvořit na jedinou hodnotu.

Standardní MongoDB $lookup

Stále si ponecháte "připojit se k serveru" a můžete to udělat pomocí $lookup , ale vyžaduje to pouze střední zpracování. Toto je dlouhodobý přístup s dekonstrukcí pole pomocí $unwind a pomocí $group fáze k přestavbě polí:

Venue.aggregate([
  { "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
  { "$lookup": {
    "from": Review.collection.name,
    "localField": "reviews",
    "foreignField": "_id",
    "as": "reviews"
  }},
  { "$unwind": "$reviews" },
  { "$lookup": {
    "from": Comment.collection.name,
    "localField": "reviews.comments",
    "foreignField": "_id",
    "as": "reviews.comments",
  }},
  { "$unwind": "$reviews.comments" },
  { "$lookup": {
    "from": Author.collection.name,
    "localField": "reviews.comments.author",
    "foreignField": "_id",
    "as": "reviews.comments.author"
  }},
  { "$unwind": "$reviews.comments.author" },
  { "$addFields": {
    "reviews.comments.author.isFollower": {
      "$in": [ 
        mongoose.Types.ObjectId(req.user.id), 
        "$reviews.comments.author.followers"
      ]
    }
  }},
  { "$group": {
    "_id": { 
      "_id": "$_id",
      "reviewId": "$review._id"
    },
    "name": { "$first": "$name" },
    "addedBy": { "$first": "$addedBy" },
    "review": {
      "$first": {
        "_id": "$review._id",
        "createdAt": "$review.createdAt",
        "venue": "$review.venue",
        "author": "$review.author",
        "content": "$review.content"
      }
    },
    "comments": { "$push": "$reviews.comments" }
  }},
  { "$sort": { "_id._id": 1, "review.createdAt": -1 } },
  { "$group": {
    "_id": "$_id._id",
    "name": { "$first": "$name" },
    "addedBy": { "$first": "$addedBy" },
    "reviews": {
      "$push": {
        "_id": "$review._id",
        "venue": "$review.venue",
        "author": "$review.author",
        "content": "$review.content",
        "comments": "$comments"
      }
    }
  }}
])

To opravdu není tak skličující, jak byste si mohli zpočátku myslet, a řídí se jednoduchým vzorem $lookup a $unwind jak postupujete každým polem.

"author" detail je samozřejmě ojedinělý, takže jakmile je „odmotán“, chcete to jednoduše nechat tak, provést přidání pole a zahájit proces „navrácení“ do polí.

Jsou pouze dva úrovně rekonstruovat zpět na původní Venue dokumentu, takže první úroveň podrobností je podle Review znovu vytvořit "comments" pole. Vše, co potřebujete, je $push cestu "$reviews.comments" za účelem jejich shromažďování a pokud "$reviews._id" pole je v "grouping _id", jediné další věci, které musíte zachovat, jsou všechna ostatní pole. To vše můžete vložit do _id také, nebo můžete použít $first .

Díky tomu existuje pouze jedna další $group fázi, abyste se dostali zpět na Venue sám. Tentokrát je seskupovací klíč "$_id" samozřejmě se všemi vlastnostmi samotného místa konání pomocí $first a zbývající "$review" podrobnosti se vrátí do pole pomocí $push . Samozřejmě "$comments" výstup z předchozí $group se změní na "review.comments" cesta.

Pracovat na jediném dokumentu a jeho souvislostech to opravdu není tak špatné. $unwind provozovatel potrubí může obecně být problémem s výkonem, ale v kontextu tohoto použití by to ve skutečnosti nemělo mít takový dopad.

Protože se data stále "připojují na server", stále existují mnohem menší provoz než druhá zbývající alternativa.

Manipulace s JavaScriptem

Samozřejmě dalším případem je, že místo změny dat na samotném serveru ve skutečnosti manipulujete s výsledkem. Ve většině Případy bych byl pro tento přístup, protože jakékoli "doplnění" dat pravděpodobně nejlépe zvládne klient.

Problém samozřejmě s použitím populate() je to, i když to může 'vypadat' mnohem jednodušší proces, ve skutečnosti to NENÍ PŘIPOJENÍ jakýmkoliv způsobem. Vše populate() ve skutečnosti je "skrýt" základní proces odeslání vícenásobného dotazy do databáze a poté čekání na výsledky prostřednictvím asynchronního zpracování.

Tedy "vzhled" spojení je ve skutečnosti výsledkem několika požadavků na server a následného provedení "manipulace na straně klienta" dat pro vložení podrobností do polí.

Takže kromě toho jasného varování že výkonnostní charakteristiky nejsou zdaleka na stejné úrovni jako server $lookup , další výhradou je samozřejmě to, že „mongoose Documents“ ve výsledku nejsou ve skutečnosti obyčejné objekty JavaScriptu, které by bylo možné dále upravovat.

Chcete-li tedy použít tento přístup, musíte přidat .lean() metodu na dotaz před spuštěním, aby dal mongoose pokyn vrátit „prosté objekty JavaScriptu“ namísto Document typy, které jsou přetypovány pomocí metod schémat připojených k modelu. Samozřejmě je třeba poznamenat, že výsledná data již nemají přístup k žádným „metodám instancí“, které by jinak byly spojeny se samotnými souvisejícími modely:

let venue = await Venue.findOne({ _id: id.id })
  .populate({ 
    path: 'reviews', 
    options: { sort: { createdAt: -1 } },
    populate: [
     { path: 'comments', populate: [{ path: 'author' }] }
    ]
  })
  .lean();

Nyní venue je prostý objekt, můžeme jednoduše zpracovat a upravit podle potřeby:

venue.reviews = venue.reviews.map( r => 
  ({
    ...r,
    comments: r.comments.map( c =>
      ({
        ...c,
        author: {
          ...c.author,
          isAuthor: c.author.followers.map( f => f.toString() ).indexOf(req.user.id) != -1
        }
      })
    )
  })
);

Takže je to opravdu jen otázka procházení každým z vnitřních polí dolů až na úroveň, kde můžete vidět followers pole v rámci author podrobnosti. Porovnání pak může být provedeno s ObjectId hodnoty uložené v tomto poli po prvním použití .map() vrátit hodnoty "řetězec" pro porovnání s req.user.id což je také řetězec (pokud tomu tak není, přidejte také .toString() on that ), protože je obecně jednodušší tyto hodnoty porovnávat tímto způsobem pomocí kódu JavaScript.

Znovu, i když musím zdůraznit, že to „vypadá jednoduše“, ale ve skutečnosti je to věc, které se opravdu chcete vyhnout kvůli výkonu systému, protože tyto dodatečné dotazy a přenos mezi serverem a klientem stojí hodně času na zpracování. a to i vzhledem k režii požadavku načítá skutečné náklady na dopravu mezi poskytovateli hostingu.

Shrnutí

To jsou v zásadě vaše přístupy, které můžete použít, kromě „rolování vlastních“, kdy ve skutečnosti provádíte „vícenásobné dotazy“ do databáze sami namísto použití pomocníka .populate() je.

Pomocí vyplnění výstupu pak můžete jednoduše manipulovat s daty ve výsledku stejně jako s jakoukoli jinou datovou strukturou, pokud použijete .lean() na dotaz převést nebo jinak extrahovat data prostého objektu z vrácených dokumentů mongoose.

I když se zdá, že agregované přístupy jsou mnohem složitější, je jich "hodně" další výhody této práce na serveru. Větší sady výsledků lze třídit, lze provádět výpočty pro další filtrování a samozřejmě získáte "jedinou odpověď" na "jediný požadavek" na server, to vše bez další režie.

Je zcela diskutabilní, že samotné kanály by mohly být jednoduše konstruovány na základě atributů již uložených ve schématu. Takže napsat vlastní metodu k provedení této "konstrukce" na základě připojeného schématu by nemělo být příliš obtížné.

Z dlouhodobějšího hlediska samozřejmě $lookup je lepší řešení, ale pravděpodobně si budete muset dát trochu více práce s počátečním kódováním, pokud samozřejmě nebudete jen jednoduše kopírovat z toho, co je zde uvedeno;)




  1. MongoDB přidat do pole pro připojení kolekce od základního

  2. Push and Set Operations v Same MongoDB Update

  3. Jak třídit pole uvnitř záznamu kolekce v MongoDB?

  4. MongoDB vytáhne prvek z pole do hloubky dvou úrovní