简体   繁体   中英

Query documents that have different sub-fields heterogeneous

I have a collection containing documents of two different (but similar) structures, some have field "someField" with a single string value, others have field "someField" with a bunch of sub-fields.

Can I query the following two types of documents, get someField as a string when it is a string, and as some of the sub fields if it has sub fields? (It's easy enough to get either the string or all of the sub fields)

Bonus points if you can format the sub-fields to look like the single string documents.

Collection containing two types of document (simplified example)

{
    "_id" : ObjectId("53345585cdf2fb6a03f4a7bc"),
    "document" : {
        "someField" : "Foo 1, Bar 2",
    }
}
{
    "_id" : ObjectId("533455sdfghjfb6a03f4a7bc"),
    "document" : {
        "someField" : {
            "foo": "1",
            "bar": "2",
            "wibble": "I don't want this field"
        }
    }
}

Extra Information:

The best I have so far is to use two queries, and join the results together afterwards in another script or code:

db.someCollection.find(
    {
        "document.someField.foo":{$exists:false},
    },
    {
        "document.someField": 1,
        _id: 0 
    }
).forEach(printjson)

db.someCollection.find(
    {
        "document.someField.foo":{$exists:true},
    },
    {
        "document.someField.foo": 1,
        "document.someField.bar": 1,
        _id: 0 
    }
).forEach(printjson)

Your question is a little abstract but you can work this sort of solution using .aggregate() and the $project operator:

db.collection.aggregate([
    { "$project": {
        "onefield": { "$cond": [
            "$document.someField.foo",
            {
                "foo": "$document.someField.foo",
                "bar": "$document.someField.bar"
            },
            "$document.someField"
        ]}
    }}
])

This also makes use of the ternary $cond operator in order to "evaluate" how the results are going to look.

So where your items do have the sub-document field then you can "re-shape" to the fields you want. When they do not then you can just dump the field as is shown or ( if your logic requires it ) nest further $cond operations to determine what to do with the field.

And of course if you really want everything as a string then use $concat to do this instead:

db.collection.aggregate([
    { "$project": {
        "onefield": { "$cond": [
            "$document.someField.foo",
            { "$concat": [
                "Foo ",
                "$document.someField.foo",
                " ,Bar ",
                "$document.someField.bar"
            ]},
            "$document.someField"
        ]}
    }}
])

I Think @Neil-Lunn's answer is probably better (I'm still working to convert it to my real world example), but I managed to do it myself using a JS function. *simplified from my real-world, so may contain typos.

db.someCollection.find()
    .forEach(function(o) {
        if(o.document.someField.foo) {
            print("Foo " 
                + o.document.someField.foo 
                + ", Bar "
                + o.document.someField.bar
            );
        } else {
            print(o.document.someField);
        }
    }
)

The condition checks if foo exists (undefined is false), and then does some manual string concatenation when necessary.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM