Filter nested array using jmes query - ansible

I have to get the name of companies in which 'John' worked in the 'sales' department. My JSON
looks like this:
[
{
"name" : "John",
"company" : [{
"name" : "company1",
"department" : "sales"
},
{
"name" : "company2",
"department" : "backend"
},
{
"name" : "company3",
"department" : "sales"
}
],
"phone" : "1234"
}
]
And my jmesquery is like this:
jmesquery: "[? name=='John'].company[? department=='sales'].{Company: name}"
But with this query, I'm getting a null array.

This is because your first filter [?name=='John'] is creating a projection, and more specifically a filter projection, that you will have to reset in order to further filter it.
Resetting a projection can be achieved using pipes.
Projections are an important concept in JMESPath. However, there are times when projection semantics are not what you want. A common scenario is when you want to operate of the result of a projection rather than projecting an expression onto each element in the array.
For example, the expression people[*].first will give you an array containing the first names of everyone in the people array. What if you wanted the first element in that list? If you tried people[*].first[0] that you just evaluate first[0] for each element in the people array, and because indexing is not defined for strings, the final result would be an empty array, []. To accomplish the desired result, you can use a pipe expression, <expression> | <expression>, to indicate that a projection must stop.
Source: https://jmespath.org/tutorial.html#pipe-expressions
So, here would be a first step in your query:
[?name=='John'] | [].company[?department=='sales'].{Company: name}
This said, this still ends in an array of array:
[
[
{
"Company": "company1"
},
{
"Company": "company3"
}
]
]
Because you can end up with multiple people named John in a sales department.
So, one array for the users and another for the companies/departments.
In order to fix this, you can use the flatten operator: [].
So we end with:
[?name=='John'] | [].company[?department=='sales'].{Company: name} []
Which gives:
[
{
"Company": "company1"
},
{
"Company": "company3"
}
]

Related

Match keys with sibling object JSONATA

I have an JSON object with the structure below. When looping over key_two I want to create a new object that I will return. The returned object should contain a title with the value from key_one's name where the id of key_one matches the current looped over node from key_two.
Both objects contain other keys that also will be included but the first step I can't figure out is how to grab data from a sibling object while looping and match it to the current value.
{
"key_one": [
{
"name": "some_cool_title",
"id": "value_one",
...
}
],
"key_two": [
{
"node": "value_one",
...
}
],
}
This is a good example of a 'join' operation (in SQL terms). JSONata supports this in a path expression. See https://docs.jsonata.org/path-operators#-context-variable-binding
So in your example, you could write:
key_one#$k1.key_two[node = $k1.id].{
"title": $k1.name
}
You can then add extra fields into the resulting object by referencing items from either of the original objects. E.g.:
key_one#$k1.key_two[node = $k1.id].{
"title": $k1.name,
"other_one": $k1.other_data,
"other_two": other_data
}
See https://try.jsonata.org/--2aRZvSL
I seem to have found a solution for this.
[key_two].$filter($$.key_one, function($v, $k){
$v.id = node
}).{"title": name ? name : id}
Gives:
[
{
"title": "value_one"
},
{
"title": "value_two"
},
{
"title": "value_three"
}
]
Leaving this here if someone have a similar issue in the future.

MongoDb: how to search in multiple collections?

I got this structure in my mongodb (2 collections: restaurant and cocktail)
restaurant {id=1001, name="Res1", coor=[12.392, 19.123], cocktail=[13, 0, 92]}
cocktail {id=13, name="Capiroska"}, {id=167, name="Capirinha"}, {id=92, name="Negroni"}, {id=0, name="Martini"}
Multiple restaurants and multiple cocktails, N:N relationship.
My goal is to find which different cocktails I can drink within a specified area.
I've already written a query that finds all restaurants near my position like this:
mongoTemplate.find(new Query(Criteria.where("address.location").withinSphere(new Circle(latitude, longitude, radius/6371))
), Restaurant.class);
So that I obtain a list of restaurants.
Next steps are:
How to obtain distinct cocktail's id (no repetitions allowed)
How to look into cocktail collection in order to obtain all cocktail names
TY in advance!
This might not answer your question completely but can help
how to obtain distinct cocktails id (no repetitions allowed)
Your cocktail is in array so direct group or distinct might not work you can use $unwind.
What $unwind does is allow you to peel off a document for each element
and returns that resulting document
eg: for this object
{id=1001, name="Res1", coor=[12.392, 19.123], cocktail=[13, 0, 92]}
and this query
db.temp.aggregate( [ { $unwind: "$cocktail" } ] )
will result in
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 13 }
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 0 }
{ "_id" : 1001, "name" : "Res1", coor=[12.392, 19.123],, "cocktail" : 92 }
Now once you have all individual record you can group by cocktail
db.temp.aggregate( [ { $unwind: "$cocktail" },
{
"$group": {
_id: {
"_id": "$_id",
items: {$addToSet: '$cocktail'}}
}
}
}
] );
This should answer your 1st query
For getting cocktail names you need to use lookup, group and project something like this
db.temp.aggregate([
{
"$unwind": "$cocktail"
},
{
"$lookup": {
"from": "cocktail ",
"localField": "restaurant.cocktail._id",
"foreignField": "_id",
"as": "cocktails"
}
},
{ "$unwind": "$cocktails" },
{
"$group": {
"_id": "$_id",
"cocktail": { "$cocktail": "$cocktail" },
}
},
{
"$project": {
"name": 1,
"coor" : 1,
"cocktail.name" : 1,
}
}
]).pretty()
Note: This is just one approach, might not be the best way and also untested.
You can search data from two or more collection using join in MongoDB.
Depending on your scenario,the following links might help.
https://www.mongodb.com/blog/post/joins-and-other-aggregation-enhancements-coming-in-mongodb-3-2-part-1-of-3-introduction
what about using aggregation.
first perform match stage where you get all restaurant then perform unwind operation on cocktail after this you can perform group by on cocktail field. At this stage you have all unique cocktail id then perform lookup stage .
Order of stage
match
project if you want its optional
unwind
group
lookup
project //because you want only name of cocktail instead of complete
collection.
The code is in kotlin just convert it to java if you are using intellij as ide then it will convert it into java for you.
var match = Aggregation.match(Criteria.where("address.location").withinSphere(Circle(latitude, longitude, radius / 6371)))
var project = Aggregation.project("cocktail")
var unwind = Aggregation.unwind("cocktail")
var group = Aggregation.group("cocktail")
var lookup = Aggregation.lookup("your cocktail collection name","_id.cocktail","id","cocktailCollection")
var project1 = Aggregation.project("cocktailCollection.name").andExclude("_id")
var aggregation = Aggregation.newAggregation(match,project,unwind,group,lookup,project1)
println(aggregation) // if you want to see the query
var result = mongoTemplate.aggregate(aggregation,String::class.java)

Count Unique Objects

My index looks like this:
"_source": {
"ProductName": "Random Product Name",
"Views": {
"Washington": [
{ "4nce5bbszjfppltvc": "2018-04-07T18:25:16.160Z" },
{ "4nce5bba8jfpowm4i": "2018-04-07T18:05:39.714Z" },
{ "4nce5bbszjfppltvc": "2018-04-07T18:36:23.928Z" },
]
}
}
I am trying to count the number of unique objects in Views.Washington.
In this case, the result would be 2, since two objects have the same key names. ( first and third object in the array ).
Obviously, my first thought was to use aggregations, but I am not sure how to use them with nested objects, like these.
Can this be done with normal aggregations?
Will I need to use a script?
Yes this can be done with Aggregations: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-nested-aggregation.html

How do I use Spring Mongo to group two fields and get a array of one fields based on the other?

Let me give a example here:
Two entries in the collection Author:
{
"name" : "Joe"
"Book" : "A"
},
{
"name" : "Joe"
"Book" : "B"
}
Now, if I use the aggregation function in Mongo via spring mongo, basically just to grab the books with name Joe, it could be coded like:
Aggregation agg = newAggregation(Map.class, group("name", "Book"));
AggregationResults<Map> results = mongoTemplate.aggregate(agg, "Author",
Map.class);
Obviously I could get two Maps this way, one has entry {"name":"Joe", "Book": A}, the other has {"name" : "Joe", "Book" : "B"}
But what if I want get ONLY one result back, with one entry :
{"name" : Joe, "Books" : ["A", "B"]}?
I'm not sure if it is reachable just using one query. It certainly could be achieved by multiple steps, which I'd hate to do..
You need to use the $addToSet operator in your $group pipeline. This will return an array of all unique values ["A", "B"] that results from applying the $group expression to each document in a group of documents that share the same group by key "name". So in mongo shell you have
db.author.aggregate([
{ $group: {
_id: '$name',
Books: { $addToSet: '$Book' }
} }
]);
which brings back the desired result
{
"result" : [
{
"_id" : "Joe",
"Books" : [ "B", "A" ]
}
],
"ok" : 1
}
The equivalent Spring aggregation:
Aggregation agg = newAggregation(Map.class, group("name").addToSet("Book").as("Books"));
AggregationResults<Map> results = mongoTemplate.aggregate(agg, "Author", Map.class);

Facet to get all keys from an object in elasticsearch

Let's say I have the following docs:
{
"title": "Some Title",
options: {
"key5": 1,
"key3": 0,
"key1": 1,
}
},
{
"title": "Some Title",
options: {
"key2": 0,
"key3": 0,
"key5": 1,
}
}
I want to get all the keys from options object using facet.
If options was a simple array of keys as strings, I would simple use a facet like this:
"facets" : {
"options" : {
"terms" : {
"field" : "options"
}
}
}
But it doesn't work in my case.
So if a query returns those two docs, I should get these keys: ["key5","key3","key1","key2"]
What kind of facet do I actually need?
You can't do that using a facet.
You have 2 options -
Keep your current document structure and get the list of keys from the type mapping (see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-get-mapping.html). This brings the scheme of your type which holds all the fields encountered.
Change your structure. Keep the key also as a field, so your option array becomes an array of documents like:
"options" :
[
{ "key" : "key1", "value" : 1},
{ "key" : "key2", "value" : 0}
]
You probably will want to keep the context of the key-value pairs when searching or faceting so configure it as a nested type (http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-nested-type.html).
Then you can facet on the "options.key" field to get a list of top keys.
if i understand you correctly, you would want to make a terms_facet for each and every field in your nested options object. kind of a "wildcard facet"?
i think that there is no functionality in the facet api that allows for this kind of operation. if i am not mistaken, fields used for faceting have to be mapped, so it might be possible to extract the fields in a separate query by inspecting the index mappings.

Resources