使用mongo在单个记录中选择数组

时间:2017-07-31 13:06:41

标签: node.js mongodb aggregate

我有一个包含这样数据的文档:

{
    "data":"string"
    "arrayofdata" :[
        {},
        {},
        {}
    ]
}

我想要做的是只选择单个文档中的数据数组

我试图获得这种格式:

{ arrayofdata: [{},
                {},
                {}]}

我尝试的东西给了我这种不好的结果:

[ { arrayofdata: {} },
  { arrayofdata: {} },
  { arrayofdata: {} } ]

知道如何实现这个目标吗?

以下是我现在正在使用的内容:

async findSubElement(collection: string, subElementName: string, filter: {
    id?: number,
    offset?: number,
    limit?: number,
    order?: number,
    sort?: string,
    fields?: string[],
  }): Promise<any[]> {

    try {
      const aggregatePipeline: any = [];

      aggregatePipeline.push(
        {
          $unwind: '$' + subElementName,
        },
      );

      if (filter.sort) {
        filter.sort = subElementName + '.' + filter.sort;
      }

      // then we can apply filter on these documents
      aggregatePipeline.push(
        {
          $match: {
            _id: new ObjectID(filter.id),
          },
        },
        {
          $sort: {
            [filter.sort ? filter.sort : subElementName + '.created_at']: filter.order || 1,
          },
        },
        // Then, use the offset
        {
          $skip: filter.offset || 0,
        },
        // And the limit
        {
          $limit: filter.limit || 20,
        },
      );

      const fields: any = {};
      // If the user specified fields, build the 'fields' object in a Mongo way
      if (filter.fields && filter.fields.length > 0) {
        filter.fields.forEach(field => {
          fields[field] = '$' + subElementName + '.' + field;
        });
      } else {
        // If the user didn't specify any field, use a default value
        fields[subElementName] = 1;
      }
      fields['_id'] = false;

      // finally we "project" only the fields of our wanted subDocuments.
      aggregatePipeline.push({
        $project: fields,
      });

      const agg = await this.aggregate(
        collection,
        aggregatePipeline,
      );
      const result = await agg.toArray();

      return result.filter(a => a);
    } catch (error) {
      throw error;
    }
  }

1 个答案:

答案 0 :(得分:1)

您需要摆脱$unwind阶段,只需使用$project,就像这样:

aggregatePipeline.push(
  {
    {
      $project:
      {
        "_id": 0 /* get rid of _id explicitly */,
        "arrayofdata": "$arrayofdata" /* only include arrayofdata field */
      }
    }
  }
);

关于您的评论:是的,您也可以使用标准findOne()来执行此操作。实际上,您在代码中执行的所有操作都可以在不使用聚合框架的情况下完成。请注意,sort()skip()limit()仅在您的查询可以返回多个文档时才可用,因此对于findOne(),它们都不起作用。但是,它们都可以使用findMany()

工作

你的$ match变成了沼泽标准过滤器(findOne的第一个参数),如下所示:

collection.findOne({_id: new ObjectID(filter.id)})

您的$project将是findOne方法的投影参数:

// exclude "_id", include "arrayofdata"
collection.findOne({...}, {"_id": 0, "arrayofdata": 1})

$sort阶段变为.sort()命令:

// I'm not sure about the square brackets here but if your code works then this should work, too.
collection.findOne(...).sort([filter.sort ? filter.sort : subElementName + '.created_at']: filter.order || 1)

但要注意排序部分,并确保您理解这一点:How to project in MongoDB after sort?

$skip

相同
collection.findOne(...).sort(...).skip(filter.offset || 0)

还有$limit

collection.findOne(...).sort(...).skip(...).limit(filter.limit || 20)

总而言之,这是一个有效的查询,在您的数据上下文中可能有意义,也可能没有意义,但它在语法上是正确的:

collection.find({_id: ObjectId("597fa64617a1cdcf9ace4712")}, {"_id": 0, "arrayofdata": 1}).sort({"arraydata":1}).skip(10).limit(20)