Projection option to return length/size of field

2020-04-15 11:35发布

问题:

I'm simply trying to write a mongo query like in SQL:

SELECT LENGTH(binaryBody) AS bodyLength FROM documents;

To do so I thought I have to use the projection of the find method.

db.documents.find(
  {<query>},
  { bodyLength: {$size: 'body'}}
);

But how to do that?

Error: error: { "waitedMS" : NumberLong(0), "ok" : 0, "errmsg" :

"Unsupported projection option: bodyLength: { $size: \"body\" }", "code" : 2 }

回答1:

.find() does not "alter" documents returned in any way. You can only "include" or "exclude" in projection.

The only things that "alter" are .aggregate() or .mapReduce().

For .aggregate(), requires MongoDB 3.4 for $strLenCP or $strLenBytes, but usually you mean the former:

db.documents.aggregate([
  { "$project": {
    "bodyLength": { "$strLenCP": "$body" }
  }}
])

For .mapReduce()

db.documents.mapReduce(
  function() {
    emit(this._id, this.body.length)
  },
  function() { },
  { "out": { "inline": 1 } }
);

And realistically in the latter case, you may as well be iterating the cursor, and may need to unless the collection is small enough or you can actually output to another collection instead.

The $size operator you are attempting to use only applies to "arrays" to return the number of entries present. And again, it's only valid for usage with the .aggregate() method.

If you mean to omit characters such as a space within a string then a $split and $reduce with $concat can be applied:

db.documents.aggregate([
  { "$addFields": {
    "bodyLength": {
      "$strLenCP": {
        "$reduce": {
          "input": { "$split": [ "$name", " "] },
          "initialValue": "",
          "in": { "$concat": [ "$$value", "$$this" ] }
        }
      }
    }
  }}
])

Or again with mapReduce():

db.documents.mapReduce(
  function() {
    emit(this._id, "".concat.apply(this.body.split(" ")).length)
    // Or even
    // emit(this._id, this.body.split(" ").reduce((o,e) => o.concat(e),"").length)
  },
  function() { },
  { "out": { "inline": 1 } }
);