Related
I am using mongoDB as backend server, I have nested array(one level array) like this
{
"_id" : ObjectId("60b1fc6d3c43f74e0c1dba92"),
"seriesId" : "60acebf73acb5b3a98d14331",
"name" : "Season 1",
"logoURL" : "uploads/season/1622277216401.png",
"yearOfPublish" : "2021-05-29",
"description" : "Season 1",
"createdBy" : ObjectId("609cbf49ba46cc3924859ab5"),
"createdOn" : "2021-05-29T08:33:49.480Z",
"episode" : [
{
"seasonId" : "60b1fc6d3c43f74e0c1dba92",
"name" : "Episode 1",
"id" : 0,
"logoURL" : "uploads/episode/1622278616899.png",
"dateOfTelecast" : null,
"description" : "sadfgh",
"duration" : "30",
"videoType" : "customURL",
"embedCode" : "",
"url" : "https://youtu.be/kbpsXMUr7ss",
"liveboxChannel" : "",
"createdOn" : "2021-05-29T08:56:59.230Z",
"createdBy" : ObjectId("609cbf49ba46cc3924859ab5"),
"_id" : "ZaVrpOLO5"
},
{
"seasonId" : "60b1fc6d3c43f74e0c1dba92",
"name" : "Episode 2",
"id" : 0,
"logoURL" : "uploads/episode/1622279206607.png",
"dateOfTelecast" : null,
"description" : "adfd",
"duration" : "30",
"videoType" : "customURL",
"embedCode" : "",
"url" : "https://youtu.be/kbpsXMUr7ss",
"liveboxChannel" : "",
"createdOn" : "2021-05-29T09:06:48.637Z",
"createdBy" : ObjectId("609cbf49ba46cc3924859ab5"),
"_id" : "9GKqXhxcH"
},}
I have more no of seasons. from the season collection,i have episode array under the name of Episode.
Now My frontend page required that episode array alone.
response = {episode: all the episode data} and this episode data is based on skip and limit value
I have tried something in mongodb,
db.getCollection('season_copy').aggregate([
{$project: {
episodes: {
$cond:{ if: { $isArray: "$episode" }, then: { input:"$episode" }, else: 0 }
},
},
},
])
Can anyone suggest me some idea?
Check this out:
Without aggregate:
db.getCollection('season_copy')
.find({ _id: ObjectID(id)})
.project({ episode: 1 }).toArray();
With aggregate:
MongoDB playground
db.getCollection('season_copy')
.aggregate([
{
$match: {
_id: ObjectId("60b1fc6d3c43f74e0c1dba92")
}
},
{
$project: {
episode: 1
}
}
])
I'm using nodejs with official mongodb's package. I got many documents in mongodb containing "type" and "timestamp" field. I want to sort it by prioritizing "type" (only specific content) and then "timestamp".
As example I have following documents:
{ type: "book", timestamp: 1580825471 }
{ type: "house", timestamp: 1580825502 }
{ type: "water", timestamp: 1580825515 }
{ type: "book", timestamp: 1580825478 }
{ type: "smartphone", timestamp: 1580825522 }
{ type: "book", timestamp: 1580825424 }
My goal is to have sorted by that way to priority the type "book" first (and then sort it by timestamp)
{ type: "book", timestamp: 1580825478 }
{ type: "book", timestamp: 1580825471 }
{ type: "book", timestamp: 1580825424 }
{ type: "smartphone", timestamp: 1580825522 }
{ type: "water", timestamp: 1580825515 }
{ type: "house", timestamp: 1580825502 }
I was trying to use the db.collection.aggregate with following $sort value:
$sort: {
type: "book",
timestamp: -1
}
But that didn't worked out because the $sort field's value can only have the value of "1", "-1" or "{ $meta: "textScore" }".
Does anybody have an idea how to solve that issue?
Thanks in advance
EDIT:
This solution by using
$sort: {
type: 1,
timestamp: -1
}
is not a solution since then all types are also sorted which I don't want it. I just want to have "book" as first result then after that, types can be randomized (but timestamp is still being sorted.). Reason for that is that I want to list history entries (that's why I'm using timestamp to sort it), but I want to show type "book" at first. Even if the document are older than other documents.
So yeah, for other types expect "book", I want it to be sorted by timestamp.
You can add an extra field in a project stage that creates a sort priority, then use that to sort on.
For example:
db.data.aggregate([
{ $addFields : { sortPriority: { $eq: [ "$type", "book" ] } } },
{ $sort: { sortPriority: -1, timestamp: -1} }
])
This will output the following:
{ "_id" : ObjectId("5e39892e0f18de54afe4d874"), "type" : "book", "timestamp" : 1580825478, "sortPriority" : true }
{ "_id" : ObjectId("5e39892e0f18de54afe4d871"), "type" : "book", "timestamp" : 1580825471, "sortPriority" : true }
{ "_id" : ObjectId("5e39892e0f18de54afe4d876"), "type" : "book", "timestamp" : 1580825424, "sortPriority" : true }
{ "_id" : ObjectId("5e39892e0f18de54afe4d875"), "type" : "smartphone", "timestamp" : 1580825522, "sortPriority" : false }
{ "_id" : ObjectId("5e39892e0f18de54afe4d873"), "type" : "water", "timestamp" : 1580825515, "sortPriority" : false }
{ "_id" : ObjectId("5e39892e0f18de54afe4d872"), "type" : "house", "timestamp" : 1580825502, "sortPriority" : false }
If you want to ommit the extra field add $unset stage:
db.data.aggregate([
{ $addFields : { sortPriority: { $eq: [ "$type", "book" ] } } },
{ $sort: { sortPriority: -1, timestamp: -1} },
{ $unset: "sortPriority" }
])
This will then output:
{ "_id" : ObjectId("5e39892e0f18de54afe4d874"), "type" : "book", "timestamp" : 1580825478 }
{ "_id" : ObjectId("5e39892e0f18de54afe4d871"), "type" : "book", "timestamp" : 1580825471 }
{ "_id" : ObjectId("5e39892e0f18de54afe4d876"), "type" : "book", "timestamp" : 1580825424 }
{ "_id" : ObjectId("5e39892e0f18de54afe4d875"), "type" : "smartphone", "timestamp" : 1580825522 }
{ "_id" : ObjectId("5e39892e0f18de54afe4d873"), "type" : "water", "timestamp" : 1580825515 }
{ "_id" : ObjectId("5e39892e0f18de54afe4d872"), "type" : "house", "timestamp" : 1580825502 }
You can create a sorting key by your own:
db.col.aggregate([
{
$addFields: {
sortBy: {
$cond: {
if: { $eq: ["$type", "book"] }, then: 0, else: 1
}
}
}
},
{ $sort: { sortBy: 1, timestamp: 1 } },
{ $unset: "sortBy" }
])
Output:
{ "_id" : ObjectId("5e398952227b6d209de231bb"), "type" : "book", "timestamp" : 1580825424, "sortPriority" : true }
{ "_id" : ObjectId("5e398952227b6d209de231b6"), "type" : "book", "timestamp" : 1580825471, "sortPriority" : true }
{ "_id" : ObjectId("5e398952227b6d209de231b9"), "type" : "book", "timestamp" : 1580825478, "sortPriority" : true }
{ "_id" : ObjectId("5e398952227b6d209de231b7"), "type" : "house", "timestamp" : 1580825502, "sortPriority" : false }
{ "_id" : ObjectId("5e398952227b6d209de231b8"), "type" : "water", "timestamp" : 1580825515, "sortPriority" : false }
{ "_id" : ObjectId("5e398952227b6d209de231ba"), "type" : "smartphone", "timestamp" : 1580825522, "sortPriority" : false }
I have a table on my website where users can type in a word in a search field, and I want mongodb to search for those words in specific objects and return only matching objects. The search text is sent in the request query.
I use the following code for pagination, but the search feature is missing.
const result = await orders.aggregate([
{ $facet: {
rows: [
{ $match: { status: {
$in: ['received', 'packing', 'packed', 'fulfilled', 'rejected', 'withdrawn'],
},
} },
{ $skip: offsetPage * parseInt(req.query.rowsPerPage) },
{ $limit: parseInt(req.query.rowsPerPage) },
{ $project: {
record: 1,
status: 1,
onPalette: 1,
note: 1,
grossTotal: 1,
receivedLog: 1,
arrivalDate: 1,
partner: 1,
location: 1,
} },
],
count: [
{ $count: 'count' },
],
} },
]).toArray();
I would like to search in the "record" field which is a number, and in a partner's name "partner.name" which is a string.
If no search query sent, or the search query (req.query.filter) is an empty string just return everything
{ "_id" : 1, "location" : "24th Street",
"in_stock" : [ "apples", "oranges", "bananas" ] }
{ "_id" : 2, "location" : "36th Street",
"in_stock" : [ "bananas", "pears", "grapes" ] }
{ "_id" : 3, "location" : "82nd Street",
"in_stock" : [ "cantaloupes", "watermelons", "apples" ] }
db.fruit.aggregate([
{
$project: {
"store location" : "$location",
"has bananas" : {
$in: [ "bananas", "$in_stock" ]
}
}
}
])
OUTPUT --
{ "_id" : 1, "store location" : "24th Street", "has bananas" : true }
{ "_id" : 2, "store location" : "36th Street", "has bananas" : true }
{ "_id" : 3, "store location" : "82nd Street", "has bananas" : false }
I'm trying to match some document in mongoDB:
My Document model :
profile: {
languages: [
{"name": "French", "level": 1},
{"name": "English", "level": 2},
{"name": "Spanish", "level": 4}
]
}
What I (can) have to search my result set:
lang = ["French", "English"];
objLang = [
{name: "French"},
{name: "English"}
];
What I need is to db.find() all documents that match at least one of the languages, for example :
profile.languages.name = "French"
or
profile.languages.name = "English"
WHat I mean is that if I have French or English in my option set, I need to get all the users who have a element of their languages array where name match one of my options, no matter the level of the languages.
So, unless I'm wrong, I can't do
db.find({"profile.languages": {$in: [{name: "French"}, {name: "English"}]});
How would you proceed ?
Thanks a lot.
David
Actually, you were almost right:
db.collection.find({"profile.languages.name":{$in: ["French","English"]} })
Since profile.languages is an array of subdocuments, you can call in for one of the subdocument's keys and the subsequent query gets mapped to all subdocuments containing that key.
However, without proper indexing, an added .explain() shows something pretty ugly:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.languages",
"indexFilterSet" : false,
"parsedQuery" : {
"profile.languages.name" : {
"$in" : [
"English",
"French"
]
}
},
"winningPlan" : {
"stage" : "COLLSCAN",
"filter" : {
"profile.languages.name" : {
"$in" : [
"English",
"French"
]
}
},
"direction" : "forward"
},
"rejectedPlans" : [ ]
}
}
(serverInfowas ommited).
So in order to make this query efficient, you need to create an index over the field you want to query:
db.languages.ensureIndex({"profile.languages.name":1})
An added explain now tells us that the matching documents are identified via the index:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "test.languages",
"indexFilterSet" : false,
"parsedQuery" : {
"profile.languages.name" : {
"$in" : [
"English",
"French"
]
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"profile.languages.name" : 1
},
"indexName" : "profile.languages.name_1",
"isMultiKey" : true,
"direction" : "forward",
"indexBounds" : {
"profile.languages.name" : [
"[\"English\", \"English\"]",
"[\"French\", \"French\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"ok" : 1
}
You could try using the dot notation to access both the elements of an array and to access the fields of an embedded document:
db.find({
"profile.languages.name": {
"$in": ["French", "English"]
}
});
Please try the below query :
Solution 1 :
db.collection.aggregate([
{
$unwind:"$profile.languages"
},
{
$match:{"profile.languages.name":{"$in":["French", "English"]}}
},
{
$group:{_id: "$_id", profile:{"$push":"$profile.languages"}}
}
])
Solution 2 :
db.collection.find(
{
"profile.languages.name":
{
"$in": [ "English", "French"]
}
}
);
I've got two collections in my Mongo database, and the Foos contain references to one or more Bars:
Foo: {
prop1: true,
prop2: true,
bars: [
{
"$ref": "Bar",
"$id": ObjectId("blahblahblah")
}
]
}
Bar: {
testprop: true
}
What I want is to find all of the Foos that have at least one Bar that has its testprop set to true. I've tried this command, but it doesn't return any results:
db.Foo.find({ "bars.testprop" : { "$in": [ true ] } })
Any ideas?
You can now do it in Mongo 3.2 using $lookup
$lookup takes four arguments
from: Specifies the collection in the same database to perform the join with. The from collection cannot be sharded.
localField: Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection.
foreignField: Specifies the field from the documents in the from collection.
as: Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection.
db.Foo.aggregate(
{$unwind: "$bars"},
{$lookup: {
from:"bar",
localField: "bars",
foreignField: "_id",
as: "bar"
}},
{$match: {
"bar.testprop": true
}}
)
You can't. See http://www.mongodb.org/display/DOCS/Database+References
You have to do it in the client.
We have had a similar issue as we use MongoDB (3.4.4, actually 3.5.5 for testing) in combination with Morphia where we use #Referenece on a couple of entities. We are though not that happy with this solution and are considering removing these declarations and instead do the reference lookups manually.
I.e. we have a company collection and a user collection. The user entity in Morphia contains a #Refrence declaration on a company entity. The respective company collections contains entries like:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5dee"),
"name" : "Test",
"gln" : "1234567890123",
"uuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e",
"creationDate" : ISODate("2017-09-01T09:14:41.551Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.551Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5def"),
"name" : "Sample",
"gln" : "3210987654321",
"uuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451",
"creationDate" : ISODate("2017-09-01T09:14:41.562Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.562Z"),
"version" : NumberLong(1),
"disabled" : false
}
while the user collections contains the following entries:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"company" : {
"$ref" : "company",
"$id" : ObjectId("59a92501df01110fbb6a5dee")
},
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"creationDate" : ISODate("2017-09-01T09:14:41.673Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.765Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"company" : {
"$ref" : "company",
"$id" : ObjectId("59a92501df01110fbb6a5def")
},
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"creationDate" : ISODate("2017-09-01T09:14:41.873Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.878Z"),
"version" : NumberLong(1),
"disabled" : false
}
/* 3 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df2"),
"userId" : "user",
"userKeyEncrypted" : {
"salt" : "ab9df671340a7d8b",
"encryptedAttribute" : "7d8ad4ca6ad88686d810c70498407032f1df830596f72d931880483874d9cce3"
},
"passwordHash" : "$2a$10$0FLFw3ixW79JIBrD82Ly6ebOwnEDliS.e7GmrNkFp2nkWDA9OE/RC",
"uuid" : "d02aef94-fc3c-4539-a22e-e43b8cd78aaf",
"creationDate" : ISODate("2017-09-01T09:14:41.991Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.995Z"),
"version" : NumberLong(1),
"disabled" : false
}
In order to create a special company user view we also wanted to dereference the company in the user and only include selected fields. Based on a comment within a bug report we learned that MongoDB provides a $objectToArray: "$$ROOT.element" operation which basically splits fields of the given elements into key and value pairs. Note that $objectToArray operation was added in MongoDB version 3.4.4!
An aggregation on the company element contained in the user collection using the $objectToArray operation may look like below:
dp.user.aggregate([{
$project: {
"userId": 1,
"userKeyEncrypted": 1,
"uuid":1,
"roles": 1,
"passwordHash": 1,
"disabled": 1,
company: { $objectToArray: "$$ROOT.company" }
}
}])
The result of above aggregation looks like this:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"company" : [
{
"k" : "$ref",
"v" : "company"
},
{
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5dee")
}
]
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"company" : [
{
"k" : "$ref",
"v" : "company"
},
{
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5def")
}
]
}
/* 3 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df2"),
"userId" : "user",
"userKeyEncrypted" : {
"salt" : "ab9df671340a7d8b",
"encryptedAttribute" : "7d8ad4ca6ad88686d810c70498407032f1df830596f72d931880483874d9cce3"
},
"passwordHash" : "$2a$10$0FLFw3ixW79JIBrD82Ly6ebOwnEDliS.e7GmrNkFp2nkWDA9OE/RC",
"uuid" : "d02aef94-fc3c-4539-a22e-e43b8cd78aaf",
"disabled" : false,
"company" : null
}
Now it's simply a matter of filtering unwanted stuff (i.e. users that have no company assigned and selecting the right array entries) in order to feed the $lookup operation #sidgate has already explained and copy the value of the dereferenced company into the user response.
I.e. an aggregation like the one below will perform an join and add the data of the company to users that have a company assigned as the as value defined in the lookup:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, company: { $objectToArray: "$$ROOT.company" }} },
{ $unwind: "$company" },
{ $match: { "company.k": "$id"} },
{ $lookup: { from: "company", localField: "company.v", foreignField: "_id", as: "company_data" } }
])
The result to the above aggregation can be seen below:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"company" : {
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5dee")
},
"company_data" : [
{
"_id" : ObjectId("59a92501df01110fbb6a5dee"),
"name" : "Test",
"gln" : "1234567890123",
"uuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e",
"creationDate" : ISODate("2017-09-01T09:14:41.551Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.551Z"),
"version" : NumberLong(1),
"disabled" : false
}
]
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"company" : {
"k" : "$id",
"v" : ObjectId("59a92501df01110fbb6a5def")
},
"company_data" : [
{
"_id" : ObjectId("59a92501df01110fbb6a5def"),
"name" : "Sample",
"gln" : "3210987654321",
"uuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451",
"creationDate" : ISODate("2017-09-01T09:14:41.562Z"),
"lastChange" : ISODate("2017-09-01T09:14:41.562Z"),
"version" : NumberLong(1),
"disabled" : false
}
]
}
As can hopefully be seen we only have the two users that contained a company reference and the two users now have also the complete company data in the response. Now additional filtering can be applied to get rid of the key/value helper and also to hide unwanted data.
The final query we came up with looks like this:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, company: { $objectToArray: "$$ROOT.company" }} },
{ $unwind: "$company" },
{ $match: { "company.k": "$id"} },
{ $lookup: { from: "company", localField: "company.v", foreignField: "_id", as: "company_data" } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyUuid": { $arrayElemAt: [ "$company_data.uuid", 0 ] } } }
])
Which finally returns our desired representation:
/* 1 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df0"),
"userId" : "admin",
"userKeyEncrypted" : {
"salt" : "78e0528db239fd86",
"encryptedAttribute" : "e4543ddac7cca9757721379e4e70567bb13956694f473b73f7723ac2e2fc5245"
},
"passwordHash" : "$2a$10$STRNORu9rcbq4qYUMld4G.HJk8QQQQBmAswSNC/4PBn2bih0BvjM6",
"roles" : [
"ADMIN"
],
"uuid" : "b8aafdcf-d5c4-4040-a96d-8ab1a8608af8",
"disabled" : false,
"companyUuid" : "f1f86961-e8d5-40bb-9d3f-fdbcf549066e"
}
/* 2 */
{
"_id" : ObjectId("59a92501df01110fbb6a5df1"),
"userId" : "sample",
"userKeyEncrypted" : {
"salt" : "e3ac48695dea5f51",
"encryptedAttribute" : "e804758b0fd13c219c3fc383eaa9267b70f7b8a1ed74f05575add713ce11804a"
},
"passwordHash" : "$2a$10$Gt2dq1vy4J9MeqDnXjokAOtvFcvbhe/g9wAENXFPaPxLAw1L4EULG",
"roles" : [
"USER"
],
"uuid" : "55b62d4c-e5ee-408d-80c0-b79e02085b02",
"disabled" : false,
"companyUuid" : "fee69ee4-b29c-483b-b40d-e702b50b0451"
}
Some final note to this approach: This aggregation isn't very fast, sadly, but at least it gets the job done. I haven't tested it with an array of references as originally asked though this may require some additional unwindings probably.
Update: A further way of aggregating the data, which is more in line with the comments in the above mentioned bug report, can be seen below:
db.user.aggregate([
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, companyRefs: { $let: { vars: { refParts: { $objectToArray: "$$ROOT.company" }}, in: "$$refParts.v" } } } },
{ $match: { "companyRefs": { $exists: true } } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyRef": { $arrayElemAt: [ "$companyRefs", 1 ] } } },
{ $lookup: { from: "company", localField: "companyRef", foreignField: "_id", as: "company_data" } },
{ $project: { "userId": 1, "userKeyEncrypted": 1, "uuid":1, "roles": 1, "passwordHash": 1, "disabled": 1, "companyUuid": { $arrayElemAt: [ "$company_data.uuid", 0 ] } } }
])
Here the $let: { vars: ..., in: ... } operation copies the key and value of the reference into an own object and thus allows later on to lookup the reference via the corresponding operation.
Which of these aggregations performs better has yet to be profiled.
Well.. you could query the Bar Model for the _id of all documents with testprop: true, then do a find $in and populate bars on the Foo Model with an array of those _id's you got from the first query.. :P
Maybe that counts as "In the Client" :P just a thought.
It wasn't possible before, but improvements from Mongo v3.4 we can get very close to it.
You can do it with mongo-join-query. Your code would look like this:
const mongoose = require("mongoose");
const joinQuery = require("mongo-join-query");
joinQuery(
mongoose.models.Foo,
{
find: { "bars.testprop": { $in: [true] } },
populate: ["bars"]
},
(err, res) => (err ? console.log("Error:", err) : console.log("Success:", res.results))
);
How does it work?
Behind the scenes mongo-join-query will use your Mongoose schema to determine which models to join and will create an aggregation pipeline that will perform the join and the query.
Disclosure: I wrote this library to tackle precisely this use case.