Take an unique array from list of arrays - mongodb

I have following array, i need to get unique arrays, or set union (same things only once) from the list of arrays and also exclude empty arrays. How can this be achieved on the server side ?
{
"slots": [
[],
["08:30AM", "08:40AM", "08:50AM", "09:00AM", "09:10AM", "09:20AM", "09:30AM", "09:40AM", "09:50AM", "10:00AM", "10:10AM", "10:20AM", "10:30AM", "10:40AM", "10:50AM", "11:00AM", "11:10AM", "11:20AM", "11:30AM"],
["08:30AM", "08:40AM", "08:50AM", "09:00AM", "09:10AM", "09:20AM", "09:30AM", "09:40AM", "09:50AM", "10:00AM", "10:10AM", "10:20AM", "10:30AM", "10:40AM", "10:50AM", "11:00AM", "11:10AM", "11:20AM", "11:30AM"],
["08:30AM", "08:40AM", "08:50AM", "09:00AM", "09:10AM", "09:20AM", "09:30AM", "09:40AM", "09:50AM", "10:00AM", "10:10AM", "10:20AM", "10:30AM", "10:40AM", "10:50AM", "11:00AM", "11:10AM", "11:20AM", "11:30AM"],
[],
[],
[]
],
}
I need the output as,
{
"slots": [
["08:30AM", "08:40AM", "08:50AM", "09:00AM", "09:10AM", "09:20AM", "09:30AM", "09:40AM", "09:50AM", "10:00AM", "10:10AM", "10:20AM", "10:30AM", "10:40AM", "10:50AM", "11:00AM", "11:10AM", "11:20AM", "11:30AM"]
}

The result can be acheived by the use of the .aggregate() method, which allows pipeline operations to act on the element to "re-form" the data. The most important operator here is $addToSet, which keeps only the "unique/same" elements:
db.slots.aggregate([
// Unwind the "slots" array
{ "$unwind": "$slots" },
// Unwind the "inner" arrays
{ "$unwind": "$slots" },
// Compose a "set" of the results
{ "$group": {
"_id": null,
"slots": { "$addToSet": "$slots" }
}},
// Just return the "set"
{ "$project": { "_id": 0, "slots": 1 }}
])
Note that a "set" is not considered to be ordered by the internals of the engine. If you need the results to be ordered then you can $unwind again, sort the results and re-group. As follows:
db.slots.aggregate([
// Unwind the "slots" array
{ "$unwind": "$slots" },
// Unwind the "inner" arrays
{ "$unwind": "$slots" },
// Compose a "set" of the results
{ "$group": {
"_id": null,
"slots": { "$addToSet": "$slots" }
}},
// Unwind the "set" result
{ "$unwind": "$slots" },
// Sort the results
{ "$sort": { "slots": 1 } },
// Group the array again
{ "$group": {
"_id": null,
"slots": { "$push": "$slots" }
}},
// Just return the "set"
{ "$project": { "_id": 0, "slots": 1 }}
])
You should be very careful with storing nested arrays like this. Unless you specifically need then for a special purpose, then they are notoriously difficult to query and update.

Related

MongoDB $push aggregaton won't keep the right order

I tried to make a $group aggregation with MongoDB, like the following example:
"$group": {
"_id": "$test_id",
"feeling": {
"$push": "$feeling"
},
"reference_id": {
"$push": "$_id"
},
"training_start": {
"$push": "$training_start"
},
"training_duration": {
"$push": "$duration_ms"
}
}
The aggregation works fine, but the created arrays are sorted different. That means, if I check the result of the aggregation by looking at reference_id[x] and training_start[x] then the value of training_start in the source collection is not equal to training_start[x].
Maybe an example shows my problem more precisely:
One document after the $group aggregation:
{
_id: "string_1",
reference_id: [1, 2, 3],
training_start: [01:00:00, 02:00:00, 03:00:00] (date times)
}
Documents from source collection:
{
_id:1,
training_start: 01:00:00,
test_id: "string_1"
},
{
_id:2,
training_start: 03:00:00,
test_id: "string_1"
},
{
_id:3,
training_start: 02:00:00,
test_id: "string_1"
}
The first elements in these arrays are always in the right order. So I checked if each grouped field has the same number of entries by using the code below. And the annoying result is, that the amount of entries in each array is equal. So there is no shift in the arrays caused by missing values.
"$group": {
"_id": "$test_id",
"sum": {
"$sum": {
"$cond": {
"if": {
"$lte": [
"$training_start", null
]
},
"then": 0,
"else": 1
}
}
}
Does anybody know, if there is an other way to create arrays (already tried $addToSet) which keep the order, the elements where pushed in? Or am I the problem?
Greetings Max

mongodb - $sort child documents only

When i do a find all in a mongodb collection, i want to get maintenanceList sorted by older maintenanceDate to newest.
The sort of maintenanceDate should not affect parents order in a find all query
{
"_id":"507f191e810c19729de860ea",
"color":"black",
"brand":"brandy",
"prixVenteUnitaire":200.5,
"maintenanceList":[
{
"cost":100.40,
"maintenanceDate":"2017-02-07T00:00:00.000+0000"
},
{
"cost":4000.40,
"maintenanceDate":"2019-08-07T00:00:00.000+0000"
},
{
"cost":300.80,
"maintenanceDate":"2018-08-07T00:00:00.000+0000"
}
]
}
Any guess how to do that ?
Thank you
Whatever order the fields are in with the previous pipeline stage, as operations like $project and $group effectively "copy" same position.So, it will not change the order of your fields in your aggregated result.
And the sort of maintenanceDate through aggregation will not affect parents order in a find all query.
So, simply doing this should work.
Assuming my collection name is example.
db.example.aggregate([
{
"$unwind": "$maintenanceList"
},
{
"$sort": {
"_id": 1,
"maintenanceList.maintenanceDate": 1
}
},
{
"$group": {
"_id": "$_id",
"color": {
$first: "$color"
},
"brand": {
$first: "$brand"
},
"prixVenteUnitaire": {
$first: "$prixVenteUnitaire"
},
"maintenanceList": {
"$push": "$maintenanceList"
}
}
}
])
Output:

mongodb aggregation - nested group

I'm trying to perform nested group, I have an array of documents that has two keys (invoiceIndex, proceduresIndex) I need the documents to be arranged like so
invoices (parent) -> procedures (children)
invoices: [ // Array of invoices
{
.....
"procedures": [{}, ...] // Array of procedures
}
]
Here is a sample document
{
"charges": 226.09000000000003,
"currentBalance": 226.09000000000003,
"insPortion": "",
"currentInsPortion": "",
"claim": "notSent",
"status": "unpaid",
"procedures": {
"providerId": "9vfpjSraHzQFNTtN7",
"procedure": "21111",
"description": "One surface",
"category": "basicRestoration",
"surface": [
"m"
],
"providerName": "B Dentist",
"proceduresIndex": "0"
},
"patientId": "mE5vKveFArqFHhKmE",
"patientName": "Silvia Waterman",
"invoiceIndex": "0",
"proceduresIndex": "0"
}
Here is what I have tried
https://mongoplayground.net/p/AEBGmA32n8P
Can you try the following;
db.collection.aggregate([
{
$group: {
_id: "$invoiceIndex",
procedures: {
$push: "$procedures"
},
invoice: {
$first: "$$ROOT"
}
}
},
{
$addFields: {
"invoice.procedures": "$procedures"
}
},
{
"$replaceRoot": {
"newRoot": "$invoice"
}
}
])
I retain the invoice fields with invoice: { $first: "$$ROOT" }, also keep procedures's $push logic as a separate field. Then with $addFields I move that array of procedures into the new invoice object. Then replace root to that.
You shouldn't use the procedureIndex as a part of _id in $group, for you won't be able to get a set of procedures, per invoiceIndex then. With my $group logic it works pretty well as you see.
Link to mongoplayground

Filter query on array of embedded documents(3 levels) in mongoDb

I have a collection of diagrams. Each diagram has several blocks. And each block is having several ports, which in itself has multiple objects as fields.
So far i am only able to apply $filter or $group up to level 1 on blocks. I am unable to filter up to ports based on some condition.
The document structure is: Diagram
[
{
"_id": "1",
"blocks": [
{
"port": [
{
"portType": {
"function": "input"
}
}
]
}
]
}
]
What i am trying to achieve is to get a list of all ports which have input as portType.function from the collection
db.collection.aggregate([{$project:{"blocks.ports":{$filter:{input:"$blocks.ports",as:"ports",cond:{$in:["input","$$ports.portType.function"]}}}}}]);
Expected output : list<Ports> which have only portType.function as input.
Actual output: Returns all the documents which have at least one port as "input" along with all the other ports as well.
If you want a list of Port objects you should use double $unwind stage, then $match for blocks.port.portType.function and finally extract only the ports using a $project stage
db.collection.aggregate([
{
"$unwind": "$blocks"
},
{
"$unwind": "$blocks.port"
},
{
"$match": {
"blocks.port.portType.function": "input"
}
},
{
$project: {
_id: 0,
portType: "$blocks.port.portType",
}
}
])
If your Port object contains other fields you want to extract, like for example Foo, you have just to add those fields in the $project stage like
$project: {
_id: 0,
portType: "$blocks.port.portType",
foo: "$blocks.port.foo"
...
}
you could simply use two $unwind stages to flatten the arrays, followed by a $match stage like this:
db.collection.aggregate([
{
"$unwind": "$blocks"
},
{
"$unwind": "$blocks.port"
},
{
"$match": {
"blocks.port.portType.function": "input"
}
}
])
result:
[
{
"_id": "1",
"blocks": {
"port": {
"portType": {
"function": "input"
}
}
}
}
]
try it online: mongoplayground.net/p/TOUCkCOSE7D

Embedded List ordering

I have the following object with an embedded list of items and I would like to write a query and return all or specific items ordered by date. Is it possible to do it or should I have a different collection for items and keep here their references?
I know that you can match specific element using $elemMatch.
{
"_id": "51cb12857124a215940cf2d4",
"level1":
[
{
"name":"item00",
"description":"item01",
"date": 1238492103
},
{
"name":"item10",
"description":"item11",
"date": 1238492104
}
]
}
If you want these items ordered by date on a more often than not then your best option is to keep the list ordered in the first place. The $push operator has an additional $sort paramter explicitly for this purpose.
db.collection.update(
{ "_id": "51cb12857124a215940cf2d4" },
{ "$push": {
"level1":{
"$each":[{
"name":"item11",
"description":"item11",
"date": 1238492104
}],
"$sort": { "date": 1 }
}
}
)
That actually even adapts so you could just sort your whole collection in one statement:
db.collection.update(
{},
{ "$push": {
"level1":{
"$each":[], "$sort": { "date": 1 }
}
},
{ "multi": true }
)
Without that your ony alternate is to order the results via the .aggregate() method. This really should not be your chosen operation as it requires processing $unwind on the array contents and then $sort operation on the elements within the document. Naturally this comes with some significant overhead on larger selections:
db.collection.aggregate([
{ "$unwind": "$level1" },
{ "$sort": { "_id": 1, "level1.date": 1 } },
{ "$group": {
"_id": "$_id",
"level1": { "$push": "$level1" }
}}
])