Project matching field value from array - mongodb

I want to change the document structure and only display the definition in the definitionarray that matches the idLanguage in the document. How do I do that?
Example of a document with an definition array of 3 elements (three different id of idLanguage):
{
"_id" : ObjectId("59bc29897d7934a6a7577ee0"),
"reference" : "FIIG=A23900 INC=62356",
"idTerm" : "0161-1#TM-218801#1",
"idLanguage" : "0161-1#LG-000002#1",
"statusTerm" : 0,
"idOrganisation" : "0161-1#OG-000194#1",
"idConcept" : "0161-1#01-000001#1",
"term" : "ZRYCHLOVAC ZçVERU, KE KULOMETU ",
"definition" : [
{
"_id" : ObjectId("59bc0bd77d7934a6a7243f05"),
"reference" : "FIIG=A23900 INC=62356",
"idDefinition" : "0161-1#DF-000001#1",
"idLanguage" : "0161-1#LG-000001#1",
"statusDefinition" : 0,
"idOrganisation" : "0161-1#OG-002462#1",
"definition" : "A metallic claw shaped pivoting item, designed to accelerate the weapon's recovery from recoil by assisting in realigning the breech with the barrel.",
"idConcept" : "0161-1#01-000001#1"
},
{
"_id" : ObjectId("59bc29047d7934a6a7370782"),
"reference" : "FIIG=A23900 INC=62356",
"idDefinition" : "0161-1#DF-283090#1",
"idLanguage" : "0161-1#LG-000002#1",
"statusDefinition" : 0,
"idOrganisation" : "0161-1#OG-000194#1",
"definition" : "Kovov‡ otocn‡ p‡kov‡ polo_ka pro zrychlov‡n’ obnoven’ stavu zbrane pred zpetn_m r‡zem /v_strelem t’m, _e napom‡h‡ osov_mu ztoto_nen’ z‡vorn’ku /z‡veru s hlavn’.",
"idConcept" : "0161-1#01-000001#1"
},
{
"_id" : ObjectId("59bc290b7d7934a6a73ce124"),
"reference" : "FIIG=A23900 INC=62356",
"idDefinition" : "0161-1#DF-668740#1",
"idLanguage" : "0161-1#LG-000005#1",
"statusDefinition" : 0,
"idOrganisation" : "0161-1#OG-000200#1",
"definition" : "Metalowy element wahliwy w ksztalcie szpona, przeznaczony do przyspieszenia powrotu broni po odrzucie poprzez wspomaganie ponownego ustawienia w linii zamka i lufy.",
"idConcept" : "0161-1#01-000001#1"
}
]
}

You can use $indexOfArray and $arrayElemAt to match the values. Earlier questions suggest you are using MongoDB 3.4 at least, so that should not be a problem:
db.collection.aggregate([
{ "$addFields": {
"definition": {
"$arrayElemAt": [
"$definition.definition",
{ "$indexOfArray": [
"$definition.idLanguage",
"$idLanguage"
}}
]
}
}}
])
That extracts from the array by "definition" ( the field ) at the matched position of idLanguage. So you would be replacing the "array" with the singular value that matches between those properties.

You may want this if understood your problem correctly
db.collection.aggregate([
{
$project: {
reference: 1,
defination: {
$filter: {
input: "$definition",
as: "elem",
cond: {$eq: ["$$elem.idLanguage", "0161-1#LG-000001#1"]}
// instead of "0161-1#LG-000001#1" you can use your variable
}
}
}
}
])
OR to return only definition.definition
db.collection.aggregate([
{"$unwind": "$definition"},
{"$match": {"definition.idLanguage": "0161-1#LG-000001#1"}},
{
$group: {
_id: "$_id",
defination: {$push: "$definition.definition"}
}
}
])

Related

MongoDB Lookup MainField to FieldA or FieldB

I need to join two MongoDB Colletions with lookup, the MainField to join from de first collection has to join with the other collection through Field A or Field B.
MainField is an array, with this structure [Doc1.FieldA, Doc2.FieldA, Doc3.FieldB,...].
FieldA is Unique-Index.
FieldB is Non-Unique-Index, it is for group FieldB with a unique value.
The problem is that I need to keep the order of the MainField Array.
I like to do something like this:
db.getCollection("collection1").aggregate([
$lookup: {
from: "collection2",
localField: "mainField",
foreignField: $or:["fieldA","FieldB"]
as: "mainFieldInfo"
}]
Is it possible to do this lookup or I need a different approach?
Collections examples, the documents are simplified there are more fields
in each document.
Collection Machines (1 example) :
{
"_id" : ObjectId("5c793a188021710636865c33"),
"MachineName" : "CER3A",
"NextJobs" : [ //--> MainField
"ST105862", // match with FIELD B - Flags.STS
"OFT083520", // match with FIELD A - Lote
"OFT083365",
"ST105946"
]
}
Collection Works (2 example, 1 to match with FieldA, 1 to match Field B):
Field A example:
FieldB*(Flags.STS)* is empty
{
"_id" : ObjectId("5c1b89d0b6e97d001816595e"),
"Lote" : "OFT083520", //--> FIELD A
"Flags" : {
"ShipsFinished" : true,
"PlanFinished" : true,
"Finished" : true,
"IdDefecto" : false,
"EstadoOF" : 4,
"GCT" : "GCT018929",
"PedidoVenta" : "",
"STS" : "", //--> FIELD B
}
}
Field B Example (2 docs):
FieldA*(Lote)* is diferent in each document, FieldB*(Flags.STS)* is equal
{
"_id" : ObjectId("5dcd78e2a2061070185400e2"),
"Lote" : "OFT083671", //--> FIELD A
"Flags" : {
"B2" : 1,
"EstadoOF" : 4,
"Finished" : false,
"GCT" : "GCT024270",
"LaSI" : 0,
"PedidoVenta" : "P056048",
"SPO" : "PO23579",
"STS" : "ST105862", //--> FIELD B
"Inks" : "true",
}
}
{
"_id" : ObjectId("5dcd78e2a2061070185401f0"),
"Lote" : "OFT083672", //--> FIELD A
"Flags" : {
"B2" : 1,
"EstadoOF" : 4,
"Finished" : false,
"STS" : "ST105862", //--> FIELD B
"ShipsFinished" : false,
"TipoOF" : 1,
"EstatIQC" : 1,
}
}
You have to use the other form of $lookup stage, which allow to perform multiple conditions for the lookup stage.
Here's the query you have to run :
db.machines.aggregate([
{
$lookup: {
from: "works",
let: {
"nj": "$NextJobs"
},
pipeline: [
{
$match: {
$expr: {
$or: [
{
$in: [
"$Lote",
"$$nj"
]
},
{
$in: [
"$Flags.STS",
"$$nj"
]
}
]
}
}
}
],
as: "linkedWorks"
}
}
])
You can test it here

MongoDB: How to get the object names in collection?

and think you in advance for the help. I have recently started using mongoDB for some personal project and I'm interested in finding a better way to query my data.
My question is: I have the following collection:
{
"_id" : ObjectId("5dbd77f7a204d21119cfc758"),
"Toyota" : {
"Founder" : "Kiichiro Toyoda",
"Founded" : "28 August 1937",
"Subsidiaries" : [
"Lexus",
"Daihatsu",
"Subaru",
"Hino"
]
}
}
{
"_id" : ObjectId("5dbd78d3a204d21119cfc759"),
"Volkswagen" : {
"Founder" : "German Labour Front",
"Founded" : "28 May 1937",
"Subsidiaries" : [
"Audi",
"Volkswagen",
"Skoda",
"SEAT"
]
}
}
I want to get the object name for example here I want to return
[Toyota, Volkswagen]
I have use this method
var names = {}
db.cars.find().forEach(function(doc){Object.keys(doc).forEach(function(key){names[key]=1})});
names;
which gave me the following result:
{ "_id" : 1, "Toyota" : 1, "Volkswagen" : 1 }
however, is there a better way to get the same result and also to just return the names of the objects. Thank you.
I would suggest you to change the schema design to be something like:
{
_id: ...,
company: {
name: 'Volkswagen',
founder: ...,
subsidiaries: ...,
...<other fields>...
}
You can then use the aggregation framework to achieve a similar result:
> db.test.find()
{ "_id" : 0, "company" : { "name" : "Volkswagen", "founder" : "German Labour Front" } }
{ "_id" : 1, "company" : { "name" : "Toyota", "founder" : "Kiichiro Toyoda" } }
> db.test.aggregate([ {$group: {_id: null, companies: {$push: '$company.name'}}} ])
{ "_id" : null, "companies" : [ "Volkswagen", "Toyota" ] }
For more details, see:
Aggregation framework
$group
Accumulator operators
As a bonus, you can create an index on the company.name field, whereas you cannot create an index on varying field names like in your example.

Individual search result in multiple values in arrays

I have following model:
{
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"serials" : [
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"320ffd42-f101-4b1d-8ff4-80bc693a29e6",
"fef5e68b-aed0-4a96-9488-7941c41d1c1f",
"2c0752ba-bf7a-4a3b-bd9f-14db4b2f8bae",
"6c5ff44d-5979-4bff-af12-9e6d282c3789",
"9c91bf91-72d7-4b71-827b-924947d6e93d",
"fb34b28e-afb1-4b6a-a3c1-5a1fe44246ee",
"91ab22ef-702f-4cbd-8919-a67a2b9a684c",
"ee1a7cb2-e088-47e6-a824-c8697df7d94c",
"0dc4c687-4db2-481e-a1a6-491320dede11",
"34612148-3e01-44ee-b262-de2035e63691",
"5ba85baf-e48a-40af-8578-55ff1a873c76",
"19fe3672-b6cb-4bb6-8d21-93412b938584",
"1d0d6f6d-1b49-461b-8661-ecbf43a6595e",
"d9a5455c-65ee-45e1-ae49-33cc15dec841",
"4a690a00-a76c-4d3e-aee3-78b2bb731b0c",
"ae331830-40b4-457c-8cc4-5d548f769c3e",
"fe3e460b-c89d-4ace-8a36-5ba2b53bf4d0",
"2cc6a2a0-e029-475f-a7fc-a46a79afb605",
"a7d07767-eada-4ce3-b083-9b048e9ae9f4"
],
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
I would like to retrive documents (multiple) from collection based on this serial numbers ("serials" field). For example i am finding:
[
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"key that doesn't exist",
]
We have to assume that one of the serial number doesn't exist, so would like to get information for individual serial, expected output:
[
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"e127av48-0697-4977-b096-5ce79c89a414",
"doc":{
....whole document where above serial is in array field "serials"
}
},
{
"serial":"key that doesn't exist",
"doc": null
}
]
I was trying the simplest solution - mongodb find by multiple array items, but unfortunately it'doesn't return info for individual serial number. I'am not sure it's possible to prepare this kind of query. I think some complex aggregation could perform it, but i don't even know this kind of pipelines.
Of course, i can get simple solution by using multiple aggregate or even find, but it could impact on performance, when application will be looking for 10000 records per request.
The following query can do the trick:
db.collection.aggregate([
{
$limit:1
},
{
$project:{
"_id":0,
"serialsToSearch":[
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"key that doesn't exist",
]
}
},
{
$unwind:"$serialsToSearch"
},
{
$lookup:{
"from":"collection",
"let":{
"serial":"$serialsToSearch"
},
"pipeline":[
{
$match:{
$expr:{
$in:["$$serial","$serials"]
}
}
},
{
$project:{
"serials":0
}
}
],
"as":"searialsLookup"
}
},
{
$unwind:{
"path":"$searialsLookup",
"preserveNullAndEmptyArrays":true
}
},
{
$project:{
"serial":"$serialsToSearch",
"doc":{
$ifNull:["$searialsLookup",null]
}
}
}
]).pretty()
Data Set:
{
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"serials" : [
"e127av48-0697-4977-b096-5ce79c89a414",
"d163f80a-55ff-40fe-90b4-331ece5bebd5",
"4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"320ffd42-f101-4b1d-8ff4-80bc693a29e6",
"fef5e68b-aed0-4a96-9488-7941c41d1c1f",
"2c0752ba-bf7a-4a3b-bd9f-14db4b2f8bae",
"6c5ff44d-5979-4bff-af12-9e6d282c3789",
"9c91bf91-72d7-4b71-827b-924947d6e93d",
"fb34b28e-afb1-4b6a-a3c1-5a1fe44246ee",
"91ab22ef-702f-4cbd-8919-a67a2b9a684c",
"ee1a7cb2-e088-47e6-a824-c8697df7d94c",
"0dc4c687-4db2-481e-a1a6-491320dede11",
"34612148-3e01-44ee-b262-de2035e63691",
"5ba85baf-e48a-40af-8578-55ff1a873c76",
"19fe3672-b6cb-4bb6-8d21-93412b938584",
"1d0d6f6d-1b49-461b-8661-ecbf43a6595e",
"d9a5455c-65ee-45e1-ae49-33cc15dec841",
"4a690a00-a76c-4d3e-aee3-78b2bb731b0c",
"ae331830-40b4-457c-8cc4-5d548f769c3e",
"fe3e460b-c89d-4ace-8a36-5ba2b53bf4d0",
"2cc6a2a0-e029-475f-a7fc-a46a79afb605",
"a7d07767-eada-4ce3-b083-9b048e9ae9f4"
],
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
Output:
{
"serial" : "e127av48-0697-4977-b096-5ce79c89a414",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{
"serial" : "d163f80a-55ff-40fe-90b4-331ece5bebd5",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{
"serial" : "4740021f-e9b5-4ca5-bf0e-8554c123bb94",
"doc" : {
"_id" : ObjectId("5d61aaf8108e185191552bbb"),
"name" : "ApiCard",
"producer" : "Farmina",
"form" : "syrop",
"__v" : 0
}
}
{ "serial" : "key that doesn't exist", "doc" : null }
Note: The query won't give expected output if the collection would be empty.
Aggregation stages details:
STAGE I: Limiting the records to 1, as initially, our motive is to inject the input array in aggregation. The injection would be done in no time.
STAGE II: Projecting the input array as serialsToSearch
STAGE III: Now we have the input array as a field, we can unwind it
STAGE IV: Lookup in the same collection with each field of the input array and check if the searched serial is present in serials array
STAGE V: unwinding the lookup output
STAGE VI: Projecting fields as per the response required.

MongoDB update latest subdocument

here is my mongo document..
{
"_id" : ObjectId("5a69d0acb76d1c2e08e4ccd8"),
"subscriptions" : [
{
"sub_id" : "5a56fd399dd78e33948c9b8e",
"invoice_id" : "5a56fd399dd78e33948c9b8d"
},
{
"sub_id" : "5a56fd399dd78e33948c9b8e"
}
]
}
i want to update and upsert invoice_id into last element of sub-array..
i have tried..
sort: {$natural: -1},
subscription.$.invoice
what i want it to be is....
{
"_id" : ObjectId("5a69d0acb76d1c2e08e4ccd8"),
"subscriptions" : [
{
"sub_id" : "5a56fd399dd78e33948c9b8e",
"invoice_id" : "5a56fd399dd78e33948c9b8d"
},
{
"sub_id" : "5a56fd399dd78e33948c9b8e",
"invoice_id" : "5a56fd399dd78e33948c9b8f"
}
]
}
While there are ways to get the last array element, like Saravana shows in her answer, I don't recommend doing it that way because it introduces race conditions. For example, if two subs are added simultaneously, you can't depend on which one is 'last' in the array.
If an invoice_id has to be tied to a specific sub_id, then it's far better to query and find that specific element in the array, then add the invoice_id to it.
In the comments, the OP indicated that the current order of operations is 1) add sub_id, 2) insert the invoice record into the INVOICE collection and get the invoice_id, 3) add the invoice_id into the new subscription.
However, if you already have the sub_id, then it's better to re-order your operations this way: 1) insert the invoice record and get the invoice_id 2) add both sub_id and invoice_id with a single operation.
Doing this improves performance (eliminates the second update operation), but more importantly, eliminates race conditions because you're adding both sub_id and invoice_id at the same time.
we can get the document and update last element by index
> var doc = db.sub.findOne({"_id" : ObjectId("5a69d0acb76d1c2e08e4ccd8")})
> if ( doc.subscriptions.length - 1 >= 0 )
doc.subscriptions[doc.subscriptions.length-1].invoice_id="5a56fd399dd78e33948c9b8f"
> db.sub.update({_id:doc._id},doc)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
or write an aggregation pipeline to form the document and use it for update
db.sub.aggregate(
[
{$match : { "_id" : ObjectId("5a69d0acb76d1c2e08e4ccd8") }},
{$addFields : { last : { $subtract : [{$size : "$subscriptions"},1]}}},
{$unwind : { path :"$subscriptions" , includeArrayIndex : "idx"}},
{$project : { "subscriptions.sub_id" : 1,
"subscriptions.invoice_id" : {
$cond : {
if: { $eq: [ "$idx", "$last" ] },
then: "5a56fd399dd78e33948c9b8f",
else: "$$REMOVE"
}
}
}
},
{$group : {_id : "$_id", subscriptions : {$push : "$subscriptions"}}}
]
).pretty()
result doc
{
"_id" : ObjectId("5a69d0acb76d1c2e08e4ccd8"),
"subscriptions" : [
{
"sub_id" : "5a56fd399dd78e33948c9b8e"
},
{
"sub_id" : "5a56fd399dd78e33948c9b8e",
"invoice_id" : "5a56fd399dd78e33948c9b8f"
}
]
}

how to update property in nested mongo document

I want to update a particular property in a nested mongo document
{
"_id" : ObjectId("55af76e60b0e4b318ba822ec"),
"make" : "MERCEDES-BENZ",
"model" : "E-CLASS",
"variant" : "E 250 CDI CLASSIC",
"fuel" : "Diesel",
"cc" : 2143,
"seatingCapacity" : 5,
"variant_+_fuel" : "E 250 CDI CLASSIC (Diesel)",
"make_+_model_+_variant_+_fuel" : "MERCEDES-BENZ E-CLASS E 250 CDI CLASSIC (Diesel)",
"dropdown_display" : "E-CLASS E 250 CDI CLASSIC (Diesel)",
"vehicleSegment" : "HIGH END CARS",
"abc" : {
"variantId" : 1000815,
"makeId" : 1000016,
"modelId" : 1000556,
"fuelId" : 2,
"segmentId" : 1000002,
"price" : 4020000
},
"def" : {
"bodyType" : 1,
"makeId" : 87,
"modelId" : 21584,
"fuel" : "DIESEL",
"vehicleSegmentType" : "E2"
},
"isActive" : false
}
This is my document. If I want to add or update a value for key "nonPreferred" inside "abc", how do I go about it?
I tried it with this query:
db.FourWheelerMaster.update(
{ "abc.modelId": 1000556 },
{
$Set: {
"abc": {
"nonPreferred": ["Mumbai", "Pune"]
}
}
},
{multi:true}
)
but it updates the whole "abc" structure, removed all key:values inside it and kept only newly inserted key values like below
"abc" : {
"nonPreferred" : [
"Mumbai",
"Pune"
]
},
Can anyone tell me how to update only particular property inside it and not all the complete key?
Instead of using the $set operator, you need to push that array using the $push operator together with the $each modifier to append each element of the value separately as follows:
db.FourWheelerMaster.update(
{ "abc.modelId": 1000556 },
{
"$push": {
"abc.nonPreferred": {
"$each": ["Mumbai", "Pune"]
}
}
},
{ "multi": true }
)