MongoDB:在聚合函数中按子文档数组中对象的元素过滤

sqxo8psd  于 2022-11-03  发布在  Go
关注(0)|答案(1)|浏览(201)

由于对架构进行了一些更改,我不得不进行重构,这破坏了应用程序中的一个简单的 filter,在本例中是isToRead,而其他一切都继续工作。
“资产”中应显示的文档是:

{
  "_id": {
    "$oid": "ID"
  },
  "userId": "ID",
  "folderId": "ID",
  "title": "Title",
  "note": "<p><strong>Note.</strong></p>",
  "typeOfAsset": "web",
  "isFavourite": false,
  "createdAt": {
    "$date": {
      "$numberLong": "1666702053399"
    }
  },
  "updatedAt": {
    "$date": {
      "$numberLong": "1666702117855"
    }
  },
  "isActive": 3,
  "tags": [],
  "traits": [
    {
      "$oid": "6357dae53298948a18a17c60"
    }
  ]
  "__v": 0
}

......我尝试筛选的“Assets_Traits”中的 * 参考 * 文档应为:

{
  "_id": {
    "$oid": "6357dae53298948a18a17c60"
  },
  "userId": "ID",
  "numberOfViews": 1,
  "isToRead": true,
  "__v": 0
}

我将分享整个方法,其中包括各种尝试,无论出于什么原因,将不会工作。

let tags = args.tags ? args.tags.split(',') : []

let tagsToMatch = []

if (tags.length > 0) {
  tags.forEach(tag => {
    tagsToMatch.push(new mongoose.Types.ObjectId(tag))
  })
}

let search = {
  ...(args.phraseToSearch.length > 0 && {
    $search: {
      index: 'assets',
      compound: {
        must: [{
          phrase: {
            query: args.phraseToSearch,
            path: 'title',
            slop: 2,
            score: { boost: { value: 3 } }
          }
        }],
        should: [{
          phrase: {
            query: args.phraseToSearch,
            path: 'note',
            slop: 2
          }
        }]
      }
    }
  })
}

let project = {
  $project: {
    _id: 0,
    id: '$_id',
    userId: 1,
    folderId: 1,
    title: 1,
    note: 1,
    typeOfAsset: 1,
    isFavourite: 1,
    createdAt: 1,
    updatedAt: 1,
    isActive: 1,
    attributes: 1,
    preferences: 1,
    // ...(args.typeOfAttribute === 'isToRead' && {
    //   traits: {
    //     $filter: {
    //       input: "$traits",
    //       cond: { $eq: [ "$$this.isToRead", true ] }
    //     }
    //   }
    // }),
    tags: 1,
    traits: 1,
    score: {
      $meta: 'searchScore'
    }
  }
}

let match = {
  $match: {
    userId: args.userId,
    typeOfAsset: {
      $in: args.typeOfAsset === 'all' ? MixinAssets().all : [args.typeOfAsset] // [ 'file', 'folder', 'message', 'note', 'social', 'web' ]
    },
    ...(tagsToMatch.length > 0 && {
      tags: {
        $in: tagsToMatch
      }
    }),
    ...(args.typeOfAttribute === 'isToRead' && {

      // $expr: {
      //   $allElementsTrue: [{
      //     $map: {
      //       input: '$traits',
      //       as: 't',
      //       in: {
      //         $and: [
      //           { $eq: [ "$$t.userId", args.userId ] },
      //           { $eq: [ "$$t.isToRead", true ] }
      //         ]
      //       }
      //     }
      //   }]
      // }

      // $expr: {
      //   $filter: {
      //     input: "$traits",
      //     cond: {
      //       $and: [
      //         { $eq: [ "$$this.userId", args.userId ] },
      //         { $eq: [ "$$this.isToRead", true ] }
      //       ]
      //     }
      //   }
      // }

    }),
    isActive: 3
  }
}

let lookup = {}

switch (args.typeOfAttribute) {
  case 'areFavourites':
    match.$match.isFavourite = true
    break
  case 'isToRead':

    // match.$match.traits = {
    //   userId: args.userId,
    //   isToRead: true
    // }

    // match.$match.traits = {
    //   $elemMatch: {
    //     userId: args.userId,
    //     isToRead: true
    //   }
    // }

    // lookup = {
    //   $lookup: {
    //     from: 'assets_traits',
    //     let: { isToRead: '$isToRead' },
    //     pipeline: [{
    //       $match: {
    //         $expr: {
    //           $eq: [ '$isToRead', true ]
    //         }
    //       },
    //     }],
    //     localField: 'userId',
    //     foreignField: 'userId',
    //     as: 'traits'
    //   }
    // }

    break
  case 'inTrash':
    match.$match.isActive = 2
    break
}

let skip = {
  $skip: args.skip
}

let limit = {
  $limit: args.first
}

let group = {
  $group: {
    _id: null,
    count: { $sum: 1 }
  }
}

let sort = {
  $sort: {
    [args.orderBy]: args.orderDirection === 'asc' ? 1 : -1
  }
}

console.info('Queries:getAllAssetsForNarrative()', match.$match)

let allAssets = await Models.Assets.schema.aggregate(
  (search.hasOwnProperty('$search')) // Order of criteria is critical to the functioning of the aggregate method.
    ? [search, project, match, sort, skip, limit]
    : [match, project, sort, skip, limit]
    // : [match, project, { $unwind: '$traits' }, { $match: { traits: { $elemMatch: { isToRead: true } } } }, sort, skip, limit]
)

let [ totalNumberOfAssets ] = await Models.Assets.schema.aggregate(
  (search.hasOwnProperty('$search')) // Order of criteria is critical to the functioning of the aggregate method.
    ? [search, project, match, group]
    : [match, project, group]
    // : [match, project, { $unwind: '$traits' }, { $match: { traits: { $elemMatch: { isToRead: true } } } }, group]
)

await (() => {
  if (args.phraseToSearch.length > 0) {
    const SearchFactory = require('../services/search/search')
    const Search = SearchFactory(Models)
    Search.insertRecentSearch({
      userId: args.userId,
      phraseToSearch: args.phraseToSearch.toLowerCase()
    })
  }
})()

我删除了聚合函数的最后两个数组中的lookup,因为它变得太复杂了,我无法理解发生了什么。
奇怪的是,“标签”匹配,它也是一个 * 参考 *,而“资产_特质”不会返回或做任何事情。
typeOfAsset的值为:[ 'file', 'folder', 'message', 'note', 'social', 'web' ]
当选择“所有资产”时,选择“待读取”将针对所有类型的资产执行筛选,并且当选择特定类型的资产时,将发生附加筛选-如所解释的,这在更改架构之前起作用。

另外,忽略tags,因为这里没有使用它们。
感谢您的想法!

bnl4lu3b

bnl4lu3b1#

你没有提供你的输入样本(参数)或你使用的常量(例如MixinAssets().all,我怀疑这是有问题的)。
为了得到这个答案,我构建了自己的输入:

const args = {
    typeOfAsset: 'isToRead',
    typeOfAttribute: "isToRead",
    tagsToMatch: ["tag1", "tag2"],
    skip: 0,
    first: 1,
    orderBy: "_id",
    orderDirection: "desc"
}

这会产生下列管缐(使用您的程式码):

db.Assets.aggregate([
  {
    "$match": {
      "userId": "123",
      "typeOfAsset": {
        "$in": [
          "isToRead"
        ]
      },
      "tags": {
        "$in": [
          "tag1",
          "tag2"
        ]
      },
      "isActive": 3
    }
  },
  {
    "$project": {
      "_id": 0,
      "id": "$_id",
      "userId": 1,
      "folderId": 1,
      "title": 1,
      "note": 1,
      "typeOfAsset": 1,
      "isFavourite": 1,
      "createdAt": 1,
      "updatedAt": 1,
      "isActive": 1,
      "attributes": 1,
      "preferences": 1,
      "tags": 1,
      "traits": 1,
      "score": {
        "$meta": "searchScore"
      }
    }
  },
  {
    "$sort": {
      "_id": -1
    }
  },
  {
    "$skip": 0
  },
  {
    "$limit": 1
  }
])

正如您在Mongo Playground sample中所看到的,这是可行的。
那么你的问题是什么呢?正如我提到的,我怀疑一个问题是MixinAssets().all,如果args.typeOfAsset === 'all',那么你使用这个值,现在如果它是一个数组,匹配条件看起来像这样:

typeOfAsset: {
   $in: [['web', '...', '...']]
}

这不会匹配任何内容,因为它是数组的数组,如果它是常量值,那么它也不会匹配,因为数据库中的类型不同。
我再给予你一个提示,通常当你想构建一个分页系统,需要results和totalResultCount时,通常使用$facet,这样你就不必执行两次管道,你就可以提高性能,如下所示:

db.Assets.aggregate([
  {
    "$match": {
      "userId": "123",
      "typeOfAsset": {
        "$in": [
          "isToRead"
        ]
      },
      "tags": {
        "$in": [
          "tag1",
          "tag2"
        ]
      },
      "isActive": 3
    }
  },
  {
    $facet: {
      totalCount: [
        {
          $group: {
            _id: null,
            count: {
              $sum: 1
            }
          }
        }
      ],
      results: [
        {
          "$project": {
            "_id": 0,
            "id": "$_id",
            "userId": 1,
            "folderId": 1,
            "title": 1,
            "note": 1,
            "typeOfAsset": 1,
            "isFavourite": 1,
            "createdAt": 1,
            "updatedAt": 1,
            "isActive": 1,
            "attributes": 1,
            "preferences": 1,
            "tags": 1,
            "traits": 1,
            "score": {
              "$meta": "searchScore"
            }
          }
        },
        {
          "$sort": {
            "_id": -1
          }
        },
        {
          "$skip": 0
        },
        {
          "$limit": 1
        }
      ]
    }
  }
])

Mongo Playground

相关问题