sql >> データベース >  >> NoSQL >> MongoDB

Mongoose.aggregate(pipeline)は、$ unwind、$ lookup、$groupを使用して複数のコレクションをリンクします

    これは、 MongoDbを初めて使用する人にとっては注意が必要なものでした。 のaggregate 。答えをいくつかのステップに分けて、複数のコレクションを参照して配列を集約しようとしている他の人にデモンストレーションします。

    ステップ1-コレクションでフィルタリングするための$match

    $ matchは、 db.collection.find({})と同じクエリを受け入れます 以下の場合、一致する結果の配列を返します。ここで4つの特定のレコードを選択します

    
    { '$match':
         { _id:
            { '$in':
               [
                  ObjectId('5f7bdb3eea134b5a5c976285'),
                  ObjectId('5f7bdb3eea134b5a5c976283'),
                  ObjectId('5f7bdb3eea134b5a5c976284'),
                  ObjectId('5f7bdb3eea134b5a5c976289')
               ]
            }
         }
    }
    
    $ match Result
    [ 
      { _id: ObjectId('5f7be0b37e2bdf5b19e4724d'),
        name: 'CAPTAIN_SAIL',
        classes: [ 'sail' ],
        license: 'WC-1',
        watercraftContexts:
         [ { _id: ObjectId('5f7be0b37e2bdf5b19e47241'),
             watercraftType: 'Sailboat',
             ref: 'sailboats' } ],
        __v: 0 },
      { _id: ObjectId('5f7be0b37e2bdf5b19e4724e'),
        name: 'CAPTAIN_YATCH',
        classes: [ 'yatch' ],
        license: 'WC-2',
        watercraftContexts:
         [ { _id: ObjectId('5f7be0b37e2bdf5b19e47242'),
             watercraftType: 'Yatch',
             ref: 'yatches' } ],
        __v: 0 },
      { _id: ObjectId('5f7be0b37e2bdf5b19e4724f'),
        name: 'CAPTAIN_SHIP',
        classes: [ 'ship' ],
        license: 'WC-3',
        watercraftContexts:
         [ { _id: ObjectId('5f7be0b37e2bdf5b19e47243'),
             watercraftType: 'Ship',
             ref: 'ships' } ],
        __v: 0 },
      { _id: ObjectId('5f7be0b37e2bdf5b19e47253'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         [ { _id: ObjectId('5f7be0b37e2bdf5b19e4724a'),
             watercraftType: 'Sailboat',
             ref: 'sailboats' },
           { _id: ObjectId('5f7be0b37e2bdf5b19e4724b'),
             watercraftType: 'Yatch',
             ref: 'yatches' },
           { _id: ObjectId('5f7be0b37e2bdf5b19e4724c'),
             watercraftType: 'Ship',
             ref: 'ships' } ],
        __v: 0 }
    ]
    

    ステップ2-$unwindして、$loopupで反復できるようにします

    この結果セットには、 {_id:、watercraftType:}のオブジェクトの配列があります。 配列をループして、これらの各オブジェクトをそれぞれのコレクションレコードと結合するには、配列を個々の独立したレコードに分割する必要があります。 $ unwind 機能は、次の集約ステージ用の新しいデータセットを作成します

      { '$unwind': '$watercraftContexts' },
    
    $unwind結果

    ご覧のとおり、 $ unwind 単一のwatercraftContextでレコードを作成するようになりました これで、 $ lookupを使用するように設定されました。

    [ { _id: ObjectId('5f7be2231da37c5b5915bf9b'),
        name: 'CAPTAIN_SAIL',
        classes: [ 'sail' ],
        license: 'WC-1',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf8f'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0 },
      { _id: ObjectId('5f7be2231da37c5b5915bf9c'),
        name: 'CAPTAIN_YATCH',
        classes: [ 'yatch' ],
        license: 'WC-2',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf90'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0 },
      { _id: ObjectId('5f7be2231da37c5b5915bf9d'),
        name: 'CAPTAIN_SHIP',
        classes: [ 'ship' ],
        license: 'WC-3',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf91'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0 },
      { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf98'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0 },
      { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf99'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0 },
      { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be2231da37c5b5915bf9a'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0 } ]
    
    ステップ4$lookup-外部コレクションの各レコードを結合します

    $ unwindを実行する必要があることに注意してください。 $ lookupを呼び出す前に 異なるコレクションごとに、参加する必要があります。複数のコレクションを結合する必要があるため、後で集計するために、コレクションによってキー設定されたオブジェクトに結果を保存する必要があります。

      // Only performs $lookup on 'ships' collection
      { '$lookup':
         { from: 'ships',  // Collection Name - Note: repeat for each collection
           localField: 'watercraftContexts._id', // The field with id to link
           foreignField: '_id',  // The field on the foreign collection to match
           as: 'watercrafts.ships' // The path where to store the lookup result
         }
      }
    

    ステップ5-他の結合に対して$unwindと$lookupを繰り返します

    上記を繰り返して追加の結合の手順を実行し、コレクション名でキー入力します。繰り返しを示すために、集計ステージを組み合わせました。

      { '$unwind': '$watercraftContexts' },
      { '$lookup':
         { from: 'yatches',
           localField: 'watercraftContexts._id',
           foreignField: '_id',
           as: 'watercrafts.yatches' } },
      { '$unwind': '$watercraftContexts' },
      { '$lookup':
         { from: 'sailboats',
           localField: 'watercraftContexts._id',
           foreignField: '_id',
           as: 'watercrafts.sailboats' } }
    
    ステップ4と5の結果

    注意深く見ると、キャプテンの1人に気づきます。 レコードは、異なる watercraftTypeで3回存在します 。 $ lookup 特定のコレクション名に一致するレコードのみを返します。これが、それらを Objectに保存する理由です。 collectionNameによってキー設定されています

    [
      { _id: ObjectId('5f7be7145320a65b942bb450'),
        name: 'CAPTAIN_SAIL',
        classes: [ 'sail' ],
        license: 'WC-1',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb444'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0,
        watercrafts:
         { ships: [],
           yatches: [],
           sailboats:
            [ { _id: ObjectId('5f7be7145320a65b942bb444'),
                class: 'sail',
                name: 'Gone with the Wind',
                __v: 0 } ] } },
      { _id: ObjectId('5f7be7145320a65b942bb451'),
        name: 'CAPTAIN_YATCH',
        classes: [ 'yatch' ],
        license: 'WC-2',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb445'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0,
        watercrafts:
         { ships: [],
           yatches:
            [ { _id: ObjectId('5f7be7145320a65b942bb445'),
                class: 'yatch',
                name: 'Liquid Gold',
                __v: 0 } ],
           sailboats: [] } },
      { _id: ObjectId('5f7be7145320a65b942bb452'),
        name: 'CAPTAIN_SHIP',
        classes: [ 'ship' ],
        license: 'WC-3',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb446'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        watercrafts:
         { ships:
            [ { _id: ObjectId('5f7be7145320a65b942bb446'),
                class: 'ship',
                name: 'Jenny',
                __v: 0 } ],
           yatches: [],
           sailboats: [] } },
      { _id: ObjectId('5f7be7145320a65b942bb456'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb44d'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0,
        watercrafts:
         { ships: [],
           yatches: [],
           sailboats:
            [ { _id: ObjectId('5f7be7145320a65b942bb44d'),
                class: 'sail',
                name: 'Swell Shredder',
                __v: 0 } ] } },
      { _id: ObjectId('5f7be7145320a65b942bb456'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb44e'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0,
        watercrafts:
         { ships: [],
           yatches:
            [ { _id: ObjectId('5f7be7145320a65b942bb44e'),
                class: 'yatch',
                name: 'Audrey',
                __v: 0 } ],
           sailboats: [] } },
      { _id: ObjectId('5f7be7145320a65b942bb456'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7be7145320a65b942bb44f'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        watercrafts:
         { ships:
            [ { _id: ObjectId('5f7be7145320a65b942bb44f'),
                class: 'ship',
                name: 'Jenny IV',
                __v: 0 } ],
           yatches: [],
           sailboats: [] } } ]
    

    ステップ6$project-プロジェクトを使用して結合のオブジェクトマップをフラット化します

    プロジェクトを使用して、既存のすべてのデータを選択し、結合結果のオブジェクトマップを単一の配列にフラット化できます。

      { '$project':
         // keys with the value 'true' will be included
         { name: true,
           license: true,
           classes: true,
           _id: true,
           watercraftContexts: true,
           __v: true,
           watercrafts:            // Re-assigns value of watercrafts
            { '$setUnion':         // Accepts an array of arrays to flatten
               [
                 '$watercrafts.ships',
                 '$watercrafts.yatches',
                 '$watercrafts.sailboats'
               ]
            }
         }
      }
    
    $projectの結果

    上記の$projectの結果 watercraftsを置き換えます watercraftsのフラット化された配列を持つオブジェクト 、ただし、 Captainのレコードが重複していることに注意することが重要です。 多くの異なるルックアップに一致します。次のステップでそれらをつなぎ合わせます。

    [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9695'),
        name: 'CAPTAIN_SAIL',
        classes: [ 'sail' ],
        license: 'WC-1',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
             class: 'sail',
             name: 'Gone with the Wind',
             __v: 0 } ] },
      { _id: ObjectId('5f7bea8d79dfe25bf3cb9696'),
        name: 'CAPTAIN_YATCH',
        classes: [ 'yatch' ],
        license: 'WC-2',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
             class: 'yatch',
             name: 'Liquid Gold',
             __v: 0 } ] },
      { _id: ObjectId('5f7bea8d79dfe25bf3cb9697'),
        name: 'CAPTAIN_SHIP',
        classes: [ 'ship' ],
        license: 'WC-3',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
             class: 'ship',
             name: 'Jenny',
             __v: 0 } ] },
      { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
             class: 'sail',
             name: 'Swell Shredder',
             __v: 0 } ] },
      { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
             class: 'yatch',
             name: 'Audrey',
             __v: 0 } ] },
      { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
        name: 'CAPTAIN_SAIL_YATCH_SHIP',
        classes: [ 'sail', 'yatch', 'ship' ],
        license: 'WC-7',
        watercraftContexts:
         { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        watercrafts:
         [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
             class: 'ship',
             name: 'Jenny IV',
             __v: 0 } ] } ]
    

    ステップ7$unwindと$group

    $ unwind これで、すべての watercraftsをグループ化できます。 同じキャプテンに所属 。 $ mergeObjectsも使用する必要があります キャプテンからの追加データを一時的に保存します 最終段階に備えるための新しい一時変数の下での収集。

      { '$unwind': '$watercrafts' },
      { '$group':
         { _id: '$_id',
           data:
            { '$mergeObjects':
               { name: '$name',
                 license: '$license',
                 classes: '$classes',
                 watercraftContexts: '$watercraftContexts',
                 __v: '$__v' } },
           watercrafts: { '$push': '$watercrafts' } } }
    
    $ unwind および$group 結果

    今、私たちは本当にどこかに到達しています。変換を最初の4つのキャプテンに減らしました s結合をフラット化して、単一の配列にします。

    [ { _id: ObjectId('5f7bed5e271dd95c306c25a4'),
        data:
         { name: 'CAPTAIN_SHIP',
           license: 'WC-3',
           classes: [ 'ship' ],
           watercraftContexts:
            { _id: ObjectId('5f7bed5e271dd95c306c2598'),
              watercraftType: 'Ship',
              ref: 'ships' },
           __v: 0 },
        watercrafts:
         [ { _id: ObjectId('5f7bed5e271dd95c306c2598'),
             class: 'ship',
             name: 'Jenny',
             __v: 0 } ] },
      { _id: ObjectId('5f7bed5e271dd95c306c25a8'),
        data:
         { name: 'CAPTAIN_SAIL_YATCH_SHIP',
           license: 'WC-7',
           classes: [ 'sail', 'yatch', 'ship' ],
           watercraftContexts:
            { _id: ObjectId('5f7bed5e271dd95c306c25a1'),
              watercraftType: 'Ship',
              ref: 'ships' },
           __v: 0 },
        watercrafts:
         [ { _id: ObjectId('5f7bed5e271dd95c306c259f'),
             class: 'sail',
             name: 'Swell Shredder',
             __v: 0 },
           { _id: ObjectId('5f7bed5e271dd95c306c25a0'),
             class: 'yatch',
             name: 'Audrey',
             __v: 0 },
           { _id: ObjectId('5f7bed5e271dd95c306c25a1'),
             class: 'ship',
             name: 'Jenny IV',
             __v: 0 } ] },
      { _id: ObjectId('5f7bed5e271dd95c306c25a2'),
        data:
         { name: 'CAPTAIN_SAIL',
           license: 'WC-1',
           classes: [ 'sail' ],
           watercraftContexts:
            { _id: Object('5f7bed5e271dd95c306c2596'),
              watercraftType: 'Sailboat',
              ref: 'sailboats' },
           __v: 0 },
        watercrafts:
         [ { _id: ObjectId('5f7bed5e271dd95c306c2596'),
             class: 'sail',
             name: 'Gone with the Wind',
             __v: 0 } ] },
      { _id: ObjectId('5f7bed5e271dd95c306c25a3'),
        data:
         { name: 'CAPTAIN_YATCH',
           license: 'WC-2',
           classes: [ 'yatch' ],
           watercraftContexts:
            { _id: ObjectId('5f7bed5e271dd95c306c2597'),
              watercraftType: 'Yatch',
              ref: 'yatches' },
           __v: 0 },
        watercrafts:
         [ { _id: ObjectId('5f7bed5e271dd95c306c2597'),
             class: 'yatch',
             name: 'Liquid Gold',
             __v: 0 } ] } ]
    

    ステップ8$replaceRootと$project

    残っているのは、 dataをマージすることだけです 各レコードのルートに移動し、一時変数 dataを削除します

      // Merges 'data' into the root of each record
      { '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
      // Use $project to remove data (include only the fields we want)
      { '$project':
         { name: true,
           license: true,
           classes: true,
           _id: true,
           watercraftContexts: true,
           __v: true,
           watercrafts: true } 
      }
    
    $ replaceRoot&$project結果

    これで、設定した結果が得られました...キャプテン 混合された関連タイプの配列watercrafts

    [ 
      { name: 'CAPTAIN_SAIL_YATCH_SHIP',
        license: 'WC-7',
        classes: [ 'sail', 'yatch', 'ship' ],
        watercraftContexts:
         { _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        _id: ObjectId('5f7bf3b3680b375ca1755ead'),
        watercrafts:
         [ { _id: ObjectId('5f7bf3b3680b375ca1755ea4'),
             class: 'sail',
             name: 'Swell Shredder',
             __v: 0 },
           { _id: ObjectId('5f7bf3b3680b375ca1755ea5'),
             class: 'yatch',
             name: 'Audrey',
             __v: 0 },
           { _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
             class: 'ship',
             name: 'Jenny IV',
             __v: 0 } ] },
      { name: 'CAPTAIN_SAIL',
        license: 'WC-1',
        classes: [ 'sail' ],
        watercraftContexts:
         { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
           watercraftType: 'Sailboat',
           ref: 'sailboats' },
        __v: 0,
        _id: ObjectId('5f7bf3b3680b375ca1755ea7'),
        watercrafts:
         [ { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
             class: 'sail',
             name: 'Gone with the Wind',
             __v: 0 } ] },
      { name: 'CAPTAIN_YATCH',
        license: 'WC-2',
        classes: [ 'yatch' ],
        watercraftContexts:
         { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
           watercraftType: 'Yatch',
           ref: 'yatches' },
        __v: 0,
        _id: ObjectId('5f7bf3b3680b375ca1755ea8'),
        watercrafts:
         [ { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
             class: 'yatch',
             name: 'Liquid Gold',
             __v: 0 } ] },
      { name: 'CAPTAIN_SHIP',
        license: 'WC-3',
        classes: [ 'ship' ],
        watercraftContexts:
         { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
           watercraftType: 'Ship',
           ref: 'ships' },
        __v: 0,
        _id: ObjectId('5f7bf3b3680b375ca1755ea9'),
        watercrafts:
         [ { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
             class: 'ship',
             name: 'Jenny',
             __v: 0 } ] } ]
    

    そして、あなたはそれを持っています...これを理解するのにたった2日しかかかりませんでした。同様の集約アソシエーションを試みている場合は、時間を節約できることを願っています。ハッピーコーディング!

    最終パイプライン

    [ 
      { '$match':
         { _id:
            { '$in':
               [ ObjectId('5f7bf3b3680b375ca1755ea9'),
                 ObjectId('5f7bf3b3680b375ca1755ea7'),
                 ObjectId('5f7bf3b3680b375ca1755ea8'),
                 ObjectId('5f7bf3b3680b375ca1755ead')
               ]
            }
         }
      },
      { '$unwind': '$watercraftContexts' },
      { '$lookup':
         { from: 'ships',
           localField: 'watercraftContexts._id',
           foreignField: '_id',
           as: 'watercrafts.ships' } },
      { '$unwind': '$watercraftContexts' },
      { '$lookup':
         { from: 'yatches',
           localField: 'watercraftContexts._id',
           foreignField: '_id',
           as: 'watercrafts.yatches' } },
      { '$unwind': '$watercraftContexts' },
      { '$lookup':
         { from: 'sailboats',
           localField: 'watercraftContexts._id',
           foreignField: '_id',
           as: 'watercrafts.sailboats' } },
      { '$project':
         { name: true,
           license: true,
           classes: true,
           _id: true,
           watercraftContexts: true,
           __v: true,
           watercrafts:
            { '$setUnion':
               [ '$watercrafts.ships',
                 '$watercrafts.yatches',
                 '$watercrafts.sailboats' ] } } },
      { '$unwind': '$watercrafts' },
      { '$group':
         { _id: '$_id',
           data:
            { '$mergeObjects':
               { name: '$name',
                 license: '$license',
                 classes: '$classes',
                 watercraftContexts: '$watercraftContexts',
                 __v: '$__v' } },
           watercrafts: { '$push': '$watercrafts' } } },
      { '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
      { '$project':
         { name: true,
           license: true,
           classes: true,
           _id: true,
           watercraftContexts: true,
           __v: true,
           watercrafts: true } }
    ]
    



    1. $ addToSetを配列に追加しますが、nullが返されます

    2. GridFs(MongoDb)でのファイルの作成

    3. MongoDB:ネストされた配列を集計関数にマップします

    4. nodejsからmongodbへの失われた接続を処理します