如何从条件输出中删除不需要的字段

我有一个投影阶段如下,

{ 'name': {$ifNull: [ '$invName', {} ]},, 'info.type': {$ifNull: [ '$invType', {} ]}, 'info.qty': {$ifNull: [ '$invQty', {} ]}, 'info.detailed.desc': {$ifNull: [ '$invDesc', {} ]} } 

如果字段不存在,则投影为空对象( {} ),因为如果在字段中执行sorting并且该字段不存在,则该文档按sorting顺序排列( sorting文档不存在字段到结束结果 )。 下一个阶段是sorting,并希望不存在的字段sorting顺序最后。 这是按预期工作。

现在,我想删除那些有空对象作为值的字段(如果info.detailed.desc是空的info.detailed不应该在输出中)。 我可以使用像这样的lodash在节点级别lodash此操作( https://stackoverflow.com/a/38278831/6048928 )。 但我试图在MongoDB级别做到这一点。 可能吗? 我试过$redact ,但它是过滤掉整个文档。 是基于值的PRUNEDESCEND字段的文件是可能的?

完全从文档中删除属性并不是一件微不足道的事情。 基本$replaceRoot是服务器本身在MongoDB 3.4之前还没有做过这样的事情,引入了$replaceRoot ,它实际上允许将expression式作为文档上下文返回。

即使有了这个添加,如果没有MongoDB 3.4.4中引入的$arrayToObject$arrayToObject进一步特性,也是不切实际的。 但要贯穿案件。

使用快速示例

 { "_id" : ObjectId("59adff0aad465e105d91374c"), "a" : 1 } { "_id" : ObjectId("59adff0aad465e105d91374d"), "a" : {} } 

有条件地返回根对象

 db.junk.aggregate([ { "$replaceRoot": { "newRoot": { "$cond": { "if": { "$ne": [ "$a", {} ] }, "then": "$$ROOT", "else": { "_id": "$_id" } } } }} ]) 

这是一个非常简单的原则,事实上可以应用到任何嵌套的属性来删除它的子键,但会需要不同层次的嵌套$cond甚至$switch来应用可能的条件。 $replaceRoot当然是“顶级”移除所需要的,因为它是有条件地表示顶级键返回的唯一方法。

因此,尽pipe理论上可以使用$cond$switch来决定返回的内容,但通常会比较麻烦,而且您希望更灵活一些。

过滤空对象

 db.junk.aggregate([ { "$replaceRoot": { "newRoot": { "$arrayToObject": { "$filter": { "input": { "$objectToArray": "$$ROOT" }, "cond": { "$ne": [ "$$this.v", {} ] } } } } }} ]) 

这是$arrayToObject$arrayToObject使用。 而不是写出每个可能的关键条件,我们只是把对象内容转换成一个“数组”,并对数组条目应用$filter来决定保留什么。

$objectToArray将任何对象转换为表示每个属性的文档数组,其中"k"表示键的名称, "v"表示该属性的值。 由于这些值现在可以作为“值”访问,因此可以使用$filter这样的方法来检查每个数组项,并丢弃不需要的项。

最后, $arrayToObject接受“过滤的”内容,并将这些"k""v"值转换回属性名称和值作为结果对象。 这样,“filter”条件将从结果对象中删除不符合条件的任何属性。

A返回$ cond

 db.junk.aggregate([ { "$project": { "a": { "$cond": [{ "$eq": [ "$a", {} ] }, "$$REMOVE", "$a" ] } }} ]) 

MongoDB 3.6引入了一个$$REMOVE常量的新玩家。 这是一个新的特性,可以使用$cond来决定是否显示属性。 所以当然这个版本是可用的。

在上述所有情况下,当值是我们想要移除的空对象时,不会返回"a"属性。

 { "_id" : ObjectId("59adff0aad465e105d91374c"), "a" : 1 } { "_id" : ObjectId("59adff0aad465e105d91374d") } 

更复杂的结构

这里的具体问题是包含嵌套属性的数据。 所以继续从概述的方法,我们可以展示如何做。

首先一些示例数据:

 { "_id" : ObjectId("59ae03bdad465e105d913750"), "a" : 1, "info" : { "type" : 1, "qty" : 2, "detailed" : { "desc" : "this thing" } } } { "_id" : ObjectId("59ae03bdad465e105d913751"), "a" : 2, "info" : { "type" : 2, "qty" : 3, "detailed" : { "desc" : { } } } } { "_id" : ObjectId("59ae03bdad465e105d913752"), "a" : 3, "info" : { "type" : 3, "qty" : { }, "detailed" : { "desc" : { } } } } { "_id" : ObjectId("59ae03bdad465e105d913753"), "a" : 4, "info" : { "type" : { }, "qty" : { }, "detailed" : { "desc" : { } } } } 

应用过滤方法

 db.junk.aggregate([ { "$replaceRoot": { "newRoot": { "$arrayToObject": { "$filter": { "input": { "$concatArrays": [ { "$filter": { "input": { "$objectToArray": "$$ROOT" }, "cond": { "$ne": [ "$$this.k", "info" ] } }}, [ { "k": "info", "v": { "$arrayToObject": { "$filter": { "input": { "$objectToArray": "$info" }, "cond": { "$not": { "$or": [ { "$eq": [ "$$this.v", {} ] }, { "$eq": [ "$$this.v.desc", {} ] } ] } } } } } } ] ] }, "cond": { "$ne": [ "$$this.v", {} ] } } } } }} ]) 

由于嵌套级别,这需要更复杂的处理。 在主要情况下,您需要独立查看"info"键,并删除任何不符合条件的子属性。 既然你需要返回“东西”,那么当它的所有内部属性都被移除时,我们基本上就需要删除"info"键本身。 这是对每组结果进行嵌套过滤操作的原因。

将$ cond与$$ REMOVE一起应用

在可能的情况下,这似乎是一个更合乎逻辑的select,所以首先从最简单的forms来看这个问题是有帮助的:

 db.junk.aggregate([ { "$addFields": { "info.type": { "$cond": [ { "$eq": [ "$info.type", {} ] }, "$$REMOVE", "$info.type" ] }, "info.qty": { "$cond": [ { "$eq": [ "$info.qty", {} ] }, "$$REMOVE", "$info.qty" ] }, "info.detailed.desc": { "$cond": [ { "$eq": [ "$info.detailed.desc", {} ] }, "$$REMOVE", "$info.detailed.desc" ] } }} ]) 

但是,你需要看看这个实际产生的输出:

 /* 1 */ { "_id" : ObjectId("59ae03bdad465e105d913750"), "a" : 1.0, "info" : { "type" : 1.0, "qty" : 2.0, "detailed" : { "desc" : "this thing" } } } /* 2 */ { "_id" : ObjectId("59ae03bdad465e105d913751"), "a" : 2.0, "info" : { "type" : 2.0, "qty" : 3.0, "detailed" : {} } } /* 3 */ { "_id" : ObjectId("59ae03bdad465e105d913752"), "a" : 3.0, "info" : { "type" : 3.0, "detailed" : {} } } /* 4 */ { "_id" : ObjectId("59ae03bdad465e105d913753"), "a" : 4.0, "info" : { "detailed" : {} } } 

当其他键被删除时, "info.detailed"仍然存在,因为在这个级别没有任何实际的testing。 实际上,你无法用简单的术语来expression,所以解决这个问题的唯一方法就是将对象作为expression式求值,然后在每个输出级别上应用额外的过滤条件来查看空对象仍然驻留的位置,并删除他们:

 db.junk.aggregate([ { "$addFields": { "info": { "$let": { "vars": { "info": { "$arrayToObject": { "$filter": { "input": { "$objectToArray": { "type": { "$cond": [ { "$eq": [ "$info.type", {} ] },"$$REMOVE", "$info.type" ] }, "qty": { "$cond": [ { "$eq": [ "$info.qty", {} ] },"$$REMOVE", "$info.qty" ] }, "detailed": { "desc": { "$cond": [ { "$eq": [ "$info.detailed.desc", {} ] },"$$REMOVE", "$info.detailed.desc" ] } } } }, "cond": { "$ne": [ "$$this.v", {} ] } } } } }, "in": { "$cond": [ { "$eq": [ "$$info", {} ] }, "$$REMOVE", "$$info" ] } } } }} ]) 

与纯$filter方法相同的方法实际上从结果中删除了“all”空对象:

 /* 1 */ { "_id" : ObjectId("59ae03bdad465e105d913750"), "a" : 1.0, "info" : { "type" : 1.0, "qty" : 2.0, "detailed" : { "desc" : "this thing" } } } /* 2 */ { "_id" : ObjectId("59ae03bdad465e105d913751"), "a" : 2.0, "info" : { "type" : 2.0, "qty" : 3.0 } } /* 3 */ { "_id" : ObjectId("59ae03bdad465e105d913752"), "a" : 3.0, "info" : { "type" : 3.0 } } /* 4 */ { "_id" : ObjectId("59ae03bdad465e105d913753"), "a" : 4.0 } 

在Code中做这一切

所以这里的一切都取决于最新的function,或者真正的“即将到来的function”在你使用的MongoDB版本中可用。 在这些不可用的情况下,替代方法是简单地从游标返回的结果中删除空对象。

这通常是最正常的事情,除非聚合pipe道需要继续移除字段,否则就是您所需要的。 即使这样,你可能应该在逻辑上解决这个问题,并把最终的结果留给游标处理。

作为用于shell的JavaScript,您可以使用以下方法,而且无论实际的语言实现如何,原则基本保持不变:

 db.junk.find().map( d => { let info = Object.keys(d.info) .map( k => ({ k, v: d.info[k] })) .filter(e => !( typeof ev === 'object' && ( Object.keys(ev).length === 0 || Object.keys(evdesc).length === 0 ) )) .reduce((acc,curr) => Object.assign(acc,{ [curr.k]: curr.v }),{}); delete d.info; return Object.assign(d,(Object.keys(info).length !== 0) ? { info } : {}) }) 

这与上面的例子相同的原生语言方式是,其中一个预期的属性包含一个空的对象,完全从输出中删除该属性。