Project specific dictionary from array in elasticsearch - ubuntu-14.04

I have array of dictionaries in json as follows
{
students : [
{
"name" : "abc",
"class" : "A1"
},
{
"name" : "xyz",
"class" : "B1"
},
{
"name" : "lmn",
"class" : "B1"
}
]
}
I want all student names which are into class "B1" in elasticsearch and I have tried using following elasticsearch query
{
"query" : { "term" : { "students.class.raw": "B1" } }
}
but it has given output
[
{
"name" : "abc",
"class" : "A1"
},
{
"name" : "xyz",
"class" : "B1"
},
{
"name" : "lmn",
"class" : "B1"
}
]
Projected all fields into array.
Get query for all student names which are into class "B1" in elasticsearch and output should be
[
{
"name" : "xyz",
"class" : "B1"
},
{
"name" : "lmn",
"class" : "B1"
}
]
I need help to resolve this problem.
Thanks in advance.

Related

Mongo: filter documents from multiple collections and merge

I am new to mongo and NodeJS and have a use case where I want to get filtered results from multiple collection.
Advance apologies for the long post.
for ex:
collectionA
{
"_id" : "foo#gmail.com",
"name" : "Foo",
"location" : {
"coordinates" : [
-122.420170,
37.780080
],
"type" : "Point"
}
},
{
"_id" : "bar#gmail.com,
"name" : "Bar",
"location" : {
"coordinates" : [
-122.420060,
37.780180
],
"type" : "Point"
}
}
collectionB: Some attributes are not present for all the documents and hence optional
{
"_id" : "foo#gmail.com"
"AttributeA" : [
{
"name" : "AttA_Name",
"val" : "Coll_B_AttA_Val_Foo"
},
{
"name" : "AttA_Name1",
"val" : "Coll_B_AttA_Val_Foo"
}]
},
{
"_id" : "bar#gmail.com"
"AttributeA" : [
{
"name" : "AttA_Name",
"val" : "Coll_B_AttA_Val_Bar"
},
{
"name" : "AttA_Name2",
"val" : "Coll_B_AttA_Val_Bar"
}
],
"AttributeB" : [
{
"name" : "AttB_Name",
"val" : "Coll_B_AttB_Val_Bar"
}
]
}
CollectionC: Some attributes are not present for all the documents and hence optional
{
"_id" : "foo#gmail.com"
"AttributeA" : [
{
"name" : "Coll_C_AttA_Name",
"val" : "Coll_C_AttA_Val_Foo"
}]
},
{
"_id" : "bar#gmail.com"
"AttributeA" : [
{
"name" : "Coll_C_AttA_Name",
"val" : "Coll_C_AttA_Val_Bar"
}
],
"AttributeB" : [
{
"name" : "Coll_C_AttB_Name",
"val" : "Coll_C_AttB_Val_Bar"
}
]
}
I know Collection B and C schema looks the same but the purpose is different and they have to be different. DB design is not the question so I would appreciate if do not put all the focus on it.
Query:
Assume there is another user (Alan) with same attributes present as Bar that exist in the collection but is not living nearby the location of Bar.
The query I am trying to build on top of these is,
Find people living nearby from CollectionA
And Collection B, if AttributeA exist and have an element with name: AttA_Name
And in Collection C, if AttributeA exist and have an name: Coll_C_AttA_Name
In the above case I am expecting a result as
{
"_id" : "foo#gmail.com",
"name" : "Foo",
"location" : {
"coordinates" : [
-122.420170,
37.780080
],
"type" : "Point"
},
"collectionB_AttributeA" : [
{
"name" : "AttA_Name",
"val" : "Coll_B_AttA_Val_Foo"
},
{
"name" : "AttA_Name1",
"val" : "Coll_B_AttA_Val_Foo"
}]
,
"collectionC_AttributeA" : [
{
"name" : "Coll_C_AttA_Name",
"val" : "Coll_C_AttA_Val_Foo"
}]
},
{
"_id" : "bar#gmail.com,
"name" : "Bar",
"location" : {
"coordinates" : [
-122.420060,
37.780180
],
"type" : "Point"
},
"collectionB_AttributeA":[
{
"name" : "AttA_Name",
"val" : "Coll_B_AttA_Val_Bar"
},
{
"name" : "AttA_Name2",
"val" : "Coll_B_AttA_Val_Bar"
}
],
"collectionC_AttributeA":[
{
"name" : "Coll_C_AttA_Name",
"val" : "Coll_C_AttA_Val_Bar"
}
]
}
There is one way of doing is in parts:
query Collection A and get the nearby people
Loop through the result of 1 and find in CollectionB if they have AttributeA and an element with name AttA_Name and eliminate if they don't match.
Loop through the filtered results from 2 and find in CollectionC if they have AttributeA and and element with name Coll_C_AttA_Name and if they don't eliminate such documents.
Is there a way I can use aggregate to build this query as one? I tried reading and trying the aggregate but seems like my understanding is incomplete.
let result = await CollectionASchema.aggregate([
{
$geoNear: {
near: { type: "Point", coordinates: [ Number(long) , Number(lat) ] },
distanceField: "dist.calculated",
minDistance: 0,
maxDistance: radiusinmetres,
spherical: true
}
},
{
$lookup:
{
from: 'collectionB',
pipeline: [
{ $match : { $and: [{ AttributeA :{$exists: true}}, { [category]: { $elemMatch: { name: “AttA_Name” } } }] }},
{ $project: { AttributeA: 0 } }
],
as: "collectionB_AttributeA"
}
}
])
If you can explain if this is possible or let me know off this is the right approach that would be helpful.

Update array with multiple conditions in mongodb

I have this document:
{
"_id" : ObjectId("5b673f525ef92ec6ef16504e"),
"events" : [
{
"name" : "Winner",
"map" : 0,
"something" : []
},
{
"name" : "Winner",
"map" : 2,
"something" : []
},
{
"name" : "DifferentName",
"map" : 2,
"something" : []
}
]
}
If I run the following update:
db.getCollection('test').updateOne({
"_id": ObjectId("5b673f525ef92ec6ef16504e"),
"events.name": "Winner",
"events.map": 2
},
{$push: {
"events.$.something": {
something: "test",
}
}
})
I get the bad result:
{
"_id" : ObjectId("5b673f525ef92ec6ef16504e"),
"events" : [
{
"name" : "Winner",
"map" : 0,
"something" : [
{
"something" : "test"
}
]
},
{
"name" : "Winner",
"map" : 2,
"something" : []
},
{
"name" : "DifferentName",
"map" : 2,
"something" : []
}
]
}
This is wrong, because "something" : "test" should be in the second element, where the map is equal to 2.
If I change the field "name" to "a" and run the same update, then I get the right result:
{
"_id" : ObjectId("5b673f525ef92ec6ef16504e"),
"events" : [
{
"a" : "Winner",
"map" : 0,
"something" : []
},
{
"a" : "Winner",
"map" : 2,
"something" : [
{
"something" : "test"
}
]
},
{
"a" : "DifferentName",
"map" : 2,
"something" : []
}
]
}
Now you can see, that "something" : "test" is in the right place (second event). Is this because I have used "name" and "name" is some kind of reserved keyword in Mongo?
When there are multiple conditions to match inside an array then the .Dot notation doesn't work with update query.
You need to use $elemMatch to match exact two fields inside an array
db.getCollection('test').updateOne(
{
"_id": ObjectId("5b673f525ef92ec6ef16504e"),
"events": { "$elemMatch": { "name": "Winner", "map": 2 }}
},
{
"$push": { "events.$.something": { "something": "test" }}
}
)

How to push new field in array mongodb

Imagine I have database like
{
"_id" : ObjectId("594994d1ab6c161168aa5969"),
"user_id" : ObjectId("594994d1ab6c161168aa5968"),
"active" : [
{
"type" : "active"
},
{
"type" : "inactive"
}
]
}
I want add to first object in active array . Here is result I expected
{
"_id" : ObjectId("594994d1ab6c161168aa5969"),
"user_id" : ObjectId("594994d1ab6c161168aa5968"),
"active" : [
{
"type" : "active",
"note": [{
content: 'here is content',
title : ' here is title'
}]
},
{
"type" : "inactive"
}
]
}
Here is code I tried
db.collection('..').update({'user_id' : ObjectId(userId)} ,
{$push:{ "active.0": "note": [{
content: 'here is content',
title : ' here is title'
}] } )
But I get The field 'active.0' must be an array but is of type object in document . Where is my wrong ? Please help
Starting with your document like this:
{
"_id" : ObjectId("594994d1ab6c161168aa5969"),
"user_id" : ObjectId("594994d1ab6c161168aa5968"),
"active" : [
{
"type" : "active"
},
{
"type" : "inactive"
}
]
}
You run the $push like this:
db.collection.update(
{ "_id" : ObjectId("594994d1ab6c161168aa5969") },
{ "$push":{ "active.0.note": { content: 'here is content', title : ' here is title' } } }
)
Which creates the new array within the first element like so:
{
"_id" : ObjectId("594994d1ab6c161168aa5969"),
"user_id" : ObjectId("594994d1ab6c161168aa5968"),
"active" : [
{
"type" : "active",
"note" : [
{
"content" : "here is content",
"title" : " here is title"
}
]
},
{
"type" : "inactive"
}
]
}
Everything here is cut and paste from my shell.

MongoDB: Using map-reduce to change string into integer

I am new here and asking a question for a mongodb related problem I have been facing.
Here is an example structure of how one record would look like.
{
"_id" : {
"id" : "lk23j",
"language" : "English"
},
"class" : "test",
"title" : {
"duration" : "34"
"year" : "1991"
}
}
There are several records like this and I would like to take the sum of duration, in all records. To my understanding aggregation would not work as title.duration field needs to be converted to integer, so taking the sum returns NaN(not a number). Hence I'll need to convert then take the sum using map-reduce which will allow parseInt(in javascript).
From http://blog.physalix.com/datas-manipulation-in-mongodb-rename-field-change-type-add-sub-document/, I was unable to change the title.duration to integer:
db.members.find().forEach( function (x) {x.title.duration= parseInt(x.title.duration);});
The query above threw a
"cannot read property 'duration' of undefined".
I frequently get the error
Invalid left-hand side in assinment" for "title.duration"= parseInt("title.duration");
I've tried a similar approach using map-reduce but had no success. I am having troubles referencing the data title.duration.
If someone can help out I'd greatly appreciate it!
You could try filtering your collection for documents where the title.duration field is of string type and exists, then iterate over the find() cursor with the forEach method, do the conversion and save the updated document.
Let's take the minimum test case and insert the following test documents to a test collection:
db.test.insert([
{
"_id" : {
"id" : "lk23j",
"language" : "English"
},
"class" : "test",
"title" : {
"duration" : "34",
"year" : "1991"
}
},
{
"_id" : {
"id" : "abc",
"language" : "French"
},
"class" : "foo"
},
{
"_id" : {
"id" : "def",
"language" : "German"
},
"class" : "bar",
"title" : {
"year" : "1991"
}
},
{
"_id" : {
"id" : "erb42",
"language" : "Shona"
},
"class" : "xyz",
"title" : {
"duration" : null,
"year" : "1993"
}
},
{
"_id" : {
"id" : "urn321",
"language" : "Latin"
},
"class" : "bar",
"title" : {
"duration" : "",
"year" : "1999"
}
}
])
The conversion operation as described above can then be carried out as follows:
db.test.find({ "title.duration": { "$type" : 2 } }).forEach(function (doc){
doc.title.duration = parseInt(doc.title.duration) || 0;
db.test.save(doc);
});
After the conversion you can then use the aggregation framework to calculate the sum on the title.duration field for all documents as follows:
db.test.aggregate([
{
"$group": {
"_id": null,
"total_duration": {
"$sum": "$title.duration"
}
}
}
]);
And the result:
/* 0 */
{
"result" : [
{
"_id" : null,
"total_duration" : 34
}
],
"ok" : 1
}

Update array object based on id?

I'm having a bit of a mongo issue. I was wondering if there was a way to do the following in a mongo console command rather then multiple find and update calls.
{
"_id" : ObjectId("50b429ba0e27b508d854483e"),
"array" : [
{
"id" : "1",
"letter" : "a"
},
{
"id" : "2",
"letter" : "b"
}
],
"tester" : "tom"
}
I want to update the object with this new array item
{
"id": "2",
"letter": "c"
}
I used this, addToSet is limited, it won't insert an item into the array if it is already there but it will not update an item based on an identifier. In this case I would really like to update this entry based on the id.
db.soup.update({
"tester": "tom"
}, {
$addToSet: {
"array": {
"id": "2",
"letter": "c"
}
}
});
This gives me:
{
"_id" : ObjectId("50b429ba0e27b508d854483e"),
"array" : [
{
"id" : "1",
"letter" : "a"
},
{
"id" : "2",
"letter" : "b"
},
{
"id" : "2",
"letter" : "c"
}
],
"tester" : "tom"
}
When what I really wanted was:
{
"_id" : ObjectId("50b429ba0e27b508d854483e"),
"array" : [
{
"id" : "1",
"letter" : "a"
},
{
"id" : "2",
"letter" : "c"
}
],
"tester" : "tom"
}
You can use the $ positional operator to do this:
db.soup.update(
{_id: ObjectId("50b429ba0e27b508d854483e"), 'array.id': '2'},
{$set: {'array.$.letter': 'c'}})
The $ in the update object acts as a placeholder for the first element of array to match the query selector.
Here you go:
> db.collection.insert( { array : [ { id : 1, letter : 'a' }, { id : 2, letter : 'b' } ], tester : 'tom' } );
> db.collection.findOne();
{
"_id" : ObjectId("50b431a69a0358d590a2f5f0"),
"array" : [
{
"id" : 1,
"letter" : "a"
},
{
"id" : 2,
"letter" : "b"
}
],
"tester" : "tom"
}
> db.collection.update( { tester : 'tom' }, { $set : { 'array.1' : { id : 2, letter : 'c' } } }, false, true );
> db.collection.findOne();
{
"_id" : ObjectId("50b431a69a0358d590a2f5f0"),
"array" : [
{
"id" : 1,
"letter" : "a"
},
{
"id" : 2,
"letter" : "c"
}
],
"tester" : "tom"
}
The trick lies in the false, true, false.
That is: true for upsert, false for update multiple.
For more details check out:
http://www.mongodb.org/display/DOCS/Updating#Updating-update%28%29

Resources