9

I'm trying to find (using a regexp) an array field and returns that element only

This is my data

  [
 {
"_id": "56d6e8bbf7404bd80a017edb",
"name": "document1",
"tags": [
  "A area1",
  "B area2",
  "C area3"
]
},
{
"_id": "56d6e8bbf7404bd82d017ede",
"name": "document2",
"tags": [
  "b_area3",
  "b_area4",
  "b_area5"
  ]
}
]

My query

var query=new RegExp('^'+string, "i");

Model.find({tags:query},{'tags.$': 1}, function(err,data){
        if(err) console.log(err);
        res.json(data);
    });

This query selects only the tags field (as I want), but selects the first element. I need the element(s) that matches the query.

EDIT: I tried the mongodb aggregate too, the $filter cond is wrong. I get the error "MongoError: invalid operator $regex"

caseNote.aggregate([
    { $match: {tags:query}},
    { $project: {
        tags: {$filter: {
            input: 'tags',
            as: 'item',
            cond: {$regex: ['$$item', query]}
        }}
    }}
], function (err, result) {
    if (err) {
        console.log(err);
    } else {
        res.json(result);
    }
});

EDIT2: on @zangw suggestion, this is the mongoose version, but it's incomplete: the tags fields is fine (needs test), but the query still returns the whole document.

 caseNote
     .aggregate({ $match: {tags: {$in:['area3']}}})
     .unwind('tags')
     .exec(function(err,d){
         res.json(d);
     });
alfredopacino
  • 2,979
  • 9
  • 42
  • 68

3 Answers3

9

According this issue Use $regex as the expression in a $cond, the $regex could NOT be used with cond for current mongo version.

Maybe you can try this one, filter the area3 through $match, then get all matched tags through $group, then remove the _id through $project.

caseNote.aggregate([{$unwind: '$tags'},
               {$match: {tags: /area3/}},
               {$group: {_id: null, tags: {$push: '$tags'}}},
               {$project: {tags: 1, _id: 0}}])
    .exec(function(err, tags) {
        if (err)
            console.log(err);
        else
            console.log(tags);
    });

Results:

{ "tags" : [ "C area3", "b_area3" ] }
zangw
  • 43,869
  • 19
  • 177
  • 214
  • @the.websurfer, I have updated my answer with test data. Your previous codes does not follow my answer... – zangw Mar 16 '16 at 02:22
  • I'll explain better: I need a plain array of tags that matches the query, something like `["C area3","b_area3"]`. At this time I guess there is no way to accomplish that straight with a query. – alfredopacino Mar 28 '16 at 23:43
  • 1
    @alfredopacino, you can do that through `$group`, please refer to my updated answer. – zangw Mar 29 '16 at 01:34
  • @alfredopacino, remove the `_id` field through `$project` at the end of aggregation. – zangw Mar 29 '16 at 01:42
  • I just tried, it works like a charm, but the aggregation doesn't remove duplicates (I need that). I guess I will remove by myself after the query, – alfredopacino Apr 11 '16 at 13:04
7

As per @zangw's answer,

this ISSUE-SERVER-8892, According this issue Use $regex as the expression in a $cond, the $regex could NOT be used with cond for current mongo version.

MongoDB v4.1.11 has launched new features in ISSUE-SERVER-11947, This features adds three new expressions $regexFind, $regexFindAll and $regexMatch to the aggregation language.

In your example, You can use $regexMatch expression,

Model.aggregate([
  {
    $project: {
      tags: {
        $filter: {
          input: "$tags",
          cond: {
            $regexMatch: {
              input: "$$this",
              regex: query
            }
          }
        }
      }
    }
  }
])

Playground

turivishal
  • 34,368
  • 7
  • 36
  • 59
2

This is how I solved it. If the query can be parsed to regex the projection is not added to aggregation, but happening after the db request. If the query string is normal string, the projection added.

const { query } = req;  // /rea/ or 'C area3'

const convertIfRegex = string => {
  const parts = string.split('/')
  let regex = string;
  let options = '';

  if (parts.length > 1) {
    regex = parts[1];
    options = parts[2];
  } else {
    return false
  }

  try {
    return new RegExp(regex, options);
  } catch (e) {
    return null
  }
};

const regex = convertIfRegex(queryString);
const aggregations = [{ $match: { tags:query } }]

if (!regex) {
  aggregations.push({
    $project: {
      tags: {$filter: {
        input: 'tags',
        as: 'item',
        cond: {$eq: ['$$item', query]}
      }}
    }
  })
}

let result = await caseNote.aggregate(aggregations);

if (regex) {
  result = result.reduce((acc, entry) => {
    const tags = entry.tags.filter(({ tag }) => (
      tag.match(regex)
    ))
    if (tags.length) return [...acc, { ...entry, tags }];
    return acc;
  })
}

res.json(result)

gazdagergo
  • 6,187
  • 1
  • 31
  • 45