I'm not sure of the right term to describe this problem so I'll go with a simple example.
I have blog posts index each with tags field (not_analyzed):
{
id: 1
tags: ['a']
},
{
id: 2
tags: ['a', 'b']
},
{
id: 3
tags: ['a', 'b', 'c']
}
and if I run search for tag:c I successfully get 3 post results and facets on tags:
{
term: 'a',
count: 3
},
{
term: 'b',
count: 2
}
In the result above, the facets are ordered by count. My question is, if it's possible to boost their values calculated from another index? If I have another index for tags with scores (arbitrary)
{
tag: 'a',
score: 0
},
{
tag: 'b',
score: 10
},
{
tag: 'c',
score: 0
},
Is it possible to achieve the following facets with scores calculated from another index?
{
tag: 'b'
score: 12 //10 + 2, where 2 is the count
},
{
tag: 'b'
score: 3 //0 + 3, where 3 is the count
}
*I'm aware that facets are being deprecated and I should update my code to use aggregations.
Related
I have a list of documents in a database collection. The documents ordered get changed by a front-end drag-drop functionality. order field of a document sort all documents in ascending order. For example,
[{
order: 1,
no: 1
......
},
{
order: 2,
no: 2
......
},
{
order: 3,
no: 3
......
}]
Now. when drag-drop is applied it may change to different formats. For example -
[{
order: 3,
no: 1
......
},
{
order: 1,
no: 2
......
},
{
order: 2,
no: 3
......
}]
Now it is not an issue to sort the list from the front end. Later by clicking a button, a new record is added to the top of the list. It's not a problem at all to sort again to the ascending order from the client side.
Actual Issue
I have pagination on the page so every time I don't have all items to sort from the client-side programming. There should have only/best way to sort them from the MongoDB query. For example, using the following query I can sort documents -
db.collection.aggregate([
{
"$sort": {
order: 1
}
}
])
But there is a problem if order field has duplicate numbers or doesn't have any number the above query can't sort it. Is there any query to sort the order field automatically in ascending order regardless have duplicate numbers or no value at all?
For example, if we have documents in the following format -
[
{
order: null,
no: 1,
...
},
{
order: 1,
no: 2,
...
},
{
order: 1,
no: 3,
...
},
{
order: 3,
no: 4,
...
},
]
it should be converted to -
[
{
order: 1,
no: 1,
...
},
{
order: 2,
no: 2,
...
},
{
order: 3,
no: 3,
...
},
{
order: 4,
no: 4,
...
},
]
We can use $addFields and $ifNull to set a default value of nullable field on query
db.collection.aggregate([
{
$addFields: {
order: { $ifNull: ["$order", "$order", -1] }, // order will be -1 if it is null
},
}
])
.sort({ order: 1 });
I have a Person object as follows
{
name: 'John Doe'
properties:
[
{
name: 'eyeColor',
value: 'brown'
},
{
name: 'age',
value: 25
},
{
name: 'interest',
value: 'reading'
},
{
name: 'interest',
value: 'diving'
}
]
}
Now I want to be able to filter my object based on multiple properties. In pseudocode:
Return all people for which
there exists any p.property such that
p.property.propertyname == 'interest'
AND p.property.propertyvalue == 'reading'
AND there exists any p.property such that
p.property.propertyname == 'age'
AND p.property.propertyvalue < 30
What is the most concise and extensible (I want to be able to apply N of these filters) of doing this without having too many intermediate results?
I'm trying to update a field (totalPrice) in my document(s) based on a value in a nested, nested array (addOns > get matching array from x number of arrays > get int in matching array[always at pos 2]).
Here's an example of a document with 2 arrays nested in addOns:
{
"_id": ObjectID('.....'),
"addOns": [
["appleId", "Apples", 2],
["bananaID", "Bananas", 1]
],
"totalPrice": 5.7
}
Let's say the price of bananas increased by 20cents, so I need to look for all documents that have bananas in its addOns, and then increase the totalPrice of these documents depending on the number of bananas bought. I plan on using a updateMany() query using an aggregation pipeline that should roughly look like the example below. The ??? should be the number of bananas bought, but I'm not sure how to go about retrieving that value. So far I've thought of using $unwind, $filter, and $arrayElemAt, but not sure how to use them together. Would appreciate any help!
db.collection('orders').updateMany(
{ $elemMatch: { $elemMatch: { $in: ['bananaID'] } } },
[
{ $set: {'totalPrice': { $add: ['$totalPrice', { $multiply: [{$toDecimal: '0.2'}, ???] } ] } } }
]
I'm not exactly sure whats my mongo version is, but I do know that I can use the aggregation pipeline because I have other updateMany() calls that also use the pipeline without any issues, so it should be (version >= 4.2).
**Edit: Thought of this for the ???, the filter step seems to work somewhat as the documents are getting updated, but the value is null instead of the updated price. Not sure why
Filter the '$addOns' array to return the matching nested array.
For the condition, use $eq to check if the element at [0] ('$$this.0') matches the string 'bananaID', and if it does return this nested array.
Get the array returned from step 1 using $arrayElemAt and position [0].
Use $arrayElemAt again on the array from step 3 with positon [2] as it is the index of the quantity element
{ $arrayElemAt: [{ $arrayElemAt: [ { $filter: { input: "$addOns", as:'this', cond: { $eq: ['$$this.0', 'bananaID'] } } } , 0 ] }, 2 ] }
Managed to solve it myself - though only use this if you don't mind the risk of updateMany() as stated by Joe in the question's comments. It's very similar to what I originally shared in **Edit, except you cant use $$this.0 to access elements in the array.
Insert this into where the ??? is and it'll work, below this code block is the explanation:
{ $arrayElemAt: [{ $arrayElemAt: [ { $filter: { input: "$addOns", as:'this', cond: { $eq: [{$arrayElemAt:['$$this', 0]}, 'bananaID'] } } } , 0 ] }, 2 ] }
Our array looks like this: [["appleId", "Apples", 2], ["bananaID", "Bananas", 1]]
Use $filter to return a subset of our array that only contains the elements that matches our condition - in this case we want the array for bananas. $$this represents each element in input, and we check whether the first element of $$this matches 'bananaID'.
{ $filter: { input: "$addOns", as:'this', cond: { $eq: [{$arrayElemAt:['$$this', 0]}, 'bananaID'] } } }
// how the result from $filter should look like
// [["bananaID", "Bananas", 1]]
Because the nested banana array will always be the first element, we use $arrayElemAt on the result from step 2 to retrieve the element at position 0.
// How it looks like after step 3: ["bananaID", "Bananas", 1]
The last step is to use $arrayElemAt again, except this time we want to retrieve the element at position 2 (quantity of bananas bought)
This is how the final updateMany() query looks like after steps 1-4 are evaluated.
db.collection('orders').updateMany(
{ $elemMatch: { $elemMatch: { $in: ['bananaID'] } } },
[
{ $set: {'totalPrice': { $add: ['$totalPrice', { $multiply: [{$toDecimal: '0.2'}, 1] } ] } } }
], *callback function code*)
// notice how the ??? is now replaced by the quantity of bananas which is 1
Having a list of elements:
List list = [
[category: 'A', name: 'a' value: 10],
[category: 'A', name: 'b' value: 20],
[category: 'B', name: 'a' value: 30],
[category: 'B', name: 'c' value: 40],
[category: 'B', name: 'd' value: 50],
]
I want to transform it into a nested map:
Map map = [
A: [a: 10, b: 20],
B: [a: 30, c: 40, d: 50],
]
The only solution I have come up with is to do something like this:
list.groupBy(
{ it.category }, { it.name }
).collectEntries { category, names ->
[(category): names.collectEntries { name, values ->
[(name): values.value[0]]
}]
}
However, I will have to deal with more than 2 levels of nesting in the future, and this approach will be unfeasible.
Is there any neat way to obtain the proper result in Groovy that will be more flexible?
EDIT:
By more than 2 levels of nesting I mean converting structure like:
List list = [
[category: 'A', subcategory: 'I', group: 'x', name: 'a', value: 10],
[category: 'A', subcategory: 'I', group: 'y', name: 'b', value: 20],
]
Into:
Map map = [
A: [I: [
x: [a: 10],
y: [b: 20],
]],
]
By adding nesting (depth) it would require more nested collectEntries calls, which will become unreadable.
I have found a neat solution to the problem by using Map's withDefault method and recursive Closure calls:
Map map = { [:].withDefault { owner.call() } }.call()
list.each {
map[it.category][it.name] = it.value
}
Or for the second case:
Map map = { [:].withDefault { owner.call() } }.call()
list.each {
map[it.category][it.subcategory][it.group][it.name] = it.value
}
Assume I have the following documents:
{
_id: 'id1',
tags: ['a', 'b']
}
{
_id: 'id2',
tags: ['b', 'c', 'd']
}
{
_id: 'id3',
tags: ['c', 'd', 'e']
}
Now I want to get all documents, where ALL tags are a subset of a given set. For example the view keys ['a','b','c','d'] should return doc 'id1' and 'id2' but not document with 'id3', because it contains the tag 'e' which is not in the requested keys.
How would you write such a view in CouchDB?