MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Как да се присъедините към две допълнителни колекции с условия

Това, което ви липсва тук, е този $lookup произвежда "масив" в изходното поле, определено от as в неговите аргументи. Това е общата концепция на MongoDB „връзки“, тъй като „връзка“ между документи се представя като „подсвойство“, което е „в рамките“ на самия документ, като е или единично, или „масив“ за много.

Тъй като MongoDB е "безсхемен", общата презумпция за $lookup е, че имате предвид "много" и следователно резултатът е "винаги" масив. Така че търсейки "същия резултат като в SQL", трябва да $unwind този масив след $lookup . Дали е "един" или "много" няма значение, тъй като все още е "винаги" масив:

db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])

Тук трябва да отбележите другите неща, които липсват в превода:

Последователността е „важна“

Конвейерите за агрегиране са по-„кратко изразителни“ от SQL. Всъщност те се считат най-добре като „последователност от стъпки“ прилага се към източника на данни, за да се съпоставят и трансформират данните. Най-добрият аналог на това са "тръбните" инструкции от командния ред, като:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Където е "тръбата" | може да се разглежда като "тръбопроводен етап" в агрегация на MongoDB "тръбопровод".

Като такива искаме да $match за да филтрираме нещата от колекцията "източник" като нашата първа операция. И това като цяло е добра практика, тъй като премахва всички документи, които не отговарят на изискваните условия, от допълнителни условия. Точно като това, което се случва в нашия пример за "команден ред", където приемаме "вход", след което "pipe" към grep за „премахване“ или „филтриране“.

Пътищата имат значение

Следващото нещо, което правите тук, е да се присъедините чрез $lookup . Резултатът е "масив" от елементите от "from" аргумент за колекция, съвпадащ с предоставените полета за извеждане в "as" "път на полето" като "масив".

Избраното тук име е важно, тъй като сега "документът" от изходната колекция счита, че всички елементи от "присъединяване" сега съществуват на този даден път. За да направя това лесно, използвам същото име на "колекция" като "join" за новия "път".

Така, като се започне от първото "присъединяване" изходът е до "tb2" и това ще съдържа всички резултати от тази колекция. Също така трябва да се отбележи важно нещо със следната последователност от $unwind и след това $match , относно това как MongoDB всъщност обработва заявката.

Определени последователности "наистина" имат значение

Тъй като "изглежда" има "три" етапа на конвейера, като $lookup след това $unwind и след това $match . Но на практика MongoDB наистина прави нещо друго, което се демонстрира в изхода на { "explain": true } добавен към .aggregate() команда:

    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 

Така че освен първата точка от "последователността", която се прилага, където трябва да поставите $match изявления там, където са необходими и правят „най-доброто“, това всъщност става „наистина важно“ с концепцията за „съединяване“. Тук трябва да се отбележи, че нашите последователности от $lookup след това $unwind и след това $match , всъщност се обработват от MongoDB само като $lookup етапи, като другите операции са "навити" в един етап на конвейера за всеки.

Това е важно разграничение спрямо другите начини за "филтриране" на резултатите, получени чрез $lookup . Тъй като в този случай действителните условия на "заявка" за "join" от $match се изпълняват върху колекцията, за да се присъединят "преди" резултатите да бъдат върнати на родителя.

Това в комбинация с $unwind (което се превежда на unwinding ), както е показано по-горе, е как MongoDB всъщност се справя с възможността „присъединяването“ да доведе до създаване на масив от съдържание в изходния документ, което го кара да надвиши ограничението от 16MB BSON. Това би се случило само в случаите, когато резултатът, към който се присъединява, е много голям, но същото предимство е в това, когато „филтърът“ действително се прилага, като е в целевата колекция „преди“ резултатите да бъдат върнати.

Това е този вид обработка, която най-добре "корелира" със същото поведение като SQL JOIN. Следователно това е и най-ефективният начин за получаване на резултати от $lookup където има други условия, които да се прилагат към JOIN, освен просто "локалните" на "чужди" ключови стойности.

Също така имайте предвид, че другата промяна в поведението е от това, което по същество е LEFT JOIN, извършено от $lookup където "изходният" документ винаги ще се запазва независимо от наличието на съвпадащ документ в "целевата" колекция. Вместо това $unwind добавя към това чрез "отхвърляне" на всички резултати от "източника", които не са имали нищо съвпадащо с "целта" от допълнителните условия в $match .

Всъщност те дори се изхвърлят предварително поради подразбиращия се preserveNullAndEmptyArrays: false което е включено и би отхвърлило всичко, където "местните" и "чуждите" ключове дори не съвпадат между двете колекции. Това е добро нещо за този конкретен тип заявка, тъй като „join“ е предназначено за „равно“ на тези стойности.

Заключение

Както беше отбелязано по-горе, MongoDB обикновено третира "връзките" много по-различно от това как бихте използвали "Релационна база данни" или RDBMS. Общата концепция за „отношения“ всъщност е „вграждане“ на данните или като единично свойство, или като масив.

Всъщност може да желаете такъв изход, което също е част от причината, поради която това без $unwind подредете тук изхода на $lookup всъщност е "масив". Въпреки това с помощта на $unwind в този контекст всъщност е най-ефективното нещо, което трябва да се направи, както и даването на гаранция, че „присъединените“ данни всъщност не причиняват превишаване на гореспоменатото ограничение на BSON в резултат на това „присъединяване“.

Ако всъщност искате масиви от изходни данни, тогава най-доброто нещо, което можете да направите тук, би било да използвате $group етап на конвейера и вероятно като няколко етапа, за да се "нормализират" и "отменят резултатите" на $unwind

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Където всъщност бихте посочили за този случай всички полета, които сте изисквали от "tb1" чрез имената на свойствата им с помощта на $first да запази само "първото" появяване (по същество се повтаря от резултатите от "tb2" и "tb3" unwound ) и след това $push "подробностите" от "tb3" в "масив", за да представи връзката към "tb1" .

Но общата форма на конвейера за агрегиране, както е дадена, е точното представяне на това как ще бъдат получени резултатите от оригиналния SQL, който е "денормализиран" изход в резултат на "съединяването". Дали искате да "нормализирате" резултатите отново след това зависи от вас.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. импортиране на JSON в mongoDB с помощта на pymongo

  2. MongoDB агрегат, как да добавитеToSet всеки елемент от масива в груповия конвейер

  3. Как да свържете Robomongo към MongoDB

  4. Най-добрият начин за хостване на MongoDB на DigitalOcean

  5. Не мога да създам работещ проект meteor.js върху скитническа кутия