Aggregate a set of unique values from all documents in CouchDB - couchdb

I'm trying to create a view in CouchDB that returns groups of unique values. E.g., a list of all unique brands and categories.
map function
function (doc) {
emit("brands", [doc.brand]);
emit("categories", [doc.category]);
}
reduce function
function (keys, values, rereduce) {
return values.reduce(function(acc, value) {
if (acc.indexOf(value[0]) === -1) {
return acc.concat(value);
}
return acc;
});
}
then I call that view with group=true, group_level=2. The grouping is correct, but the values aren't unique. The value is an array containing duplicates.
What I'm trying to achieve is basically having the key be the group name, e.g., brands, and the value be the aggregated unique values, e.g., ["Brand A", "Brand B"].
Given the following documents
[
{
"_id": "1",
"brand": "Brand A",
"category": "Category A",
"colors": [
"Red",
"White"
]
},
{
"_id": "2",
"brand": "Brand B",
"category": "Category B",
"colors": [
"Blue",
"White"
]
},
{
"_id": "3",
"brand": "Brand A",
"category": "Category B",
"colors": [
"Green",
"Red"
]
}
]
When I query then view in CouchDB, I'd like to get the following result back
{
"brands": ["Brand A", "Brand B"],
"categories": ["Category A", "Category B"],
"colors": ["Red", "White", "Blue", "Green"]
}
Note: The result above is just a demonstration of what I expect the view to return. It does not have to be structured as such (not even sure it's possible).

I'm going to answer this myself.
First, we want to define a map function that emit the group name as the key and the value wrapped in an array (making rereduce easier).
map function
function (doc) {
emit("brands", [doc.brand]);
emit("categories", [doc.category]);
doc.colors.forEach(function(color) {
emit("colors", [color]);
})
}
The we define a custom reduce function
function (keys, values, rereduce) {
return values.reduce(function(acc, value) {
value.forEach(function(v) {
if (acc.indexOf(v) === -1) {
return acc.push(v);
}
});
return acc;
});
}
Now, calling the view with group=true and group_level=1 will yield the following result:
+------------+-----------------------------------+
| key | value |
+------------+-----------------------------------+
| brands | ["Brand A", "Brand B"] |
| categories | ["Category A", "Category B"] |
| colors | ["Red", "White", "Blue", "Green"] |
+------------+-----------------------------------+

Related

Distinct count of multiple fields values using mongodb aggregation

I'm trying to count distinct values of multiple fields By one MongoDB Aggregation query.
So here's my data:
{
"_id":ObjectID( "617b0dbacda6cbd1a0403f68")
"car_type": "suv",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a04078df")
"car_type": " suv ",
"color": "blue",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a040ld45")
"car_type": "wagon",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0403dcd")
"car_type": "suv",
"color": "blue",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0403879")
"car_type": " wagon ",
"color": "red",
"num_doors": 4
},
{
"_id":ObjectID( "617b0dbacda6cbd1a0405478")
"car_type": "wagon",
"color": "red",
"num_doors": 4
}
I want a distinct count of each color by car_type:
"car_type": "suv"
"red":2,
"blue":2
iwas able to distinct and cound all colors but i couldnt distinct them by car_type
Query
group specific first (cartype+color), to count the same colors
group less specific after (cartype), to get all colors/count for each car_type
project to fix structure and $arrayToObject to make the colors keys and the the count values
*query assumes that " wagon " was typing mistake(the extra spaces i mean), if your collection has those problems, use $trim to clear the database from those.
*query is updated to include the sum also, from the comment
Test code here
aggregate(
[{"$group":
{"_id": {"car_type": "$car_type", "color": "$color"},
"count": {"$sum": 1}}},
{"$group":
{"_id": "$_id.car_type",
"colors": {"$push": {"k": "$_id.color", "v": "$count"}}}},
{"$set": {"sum": {"$sum": "$colors.v"}}},
{"$project":
{"_id": 0,
"sum": 1,
"car_type": "$_id",
"colors": {"$arrayToObject": ["$colors"]}}},
{"$replaceRoot": {"newRoot": {"$mergeObjects": ["$colors", "$$ROOT"]}}},
{"$project": {"colors": 0}}])

Cosmos Db: How to query for the maximum value of a property in an array of arrays?

I'm not sure how to query when using CosmosDb as I'm used to SQL. My question is about how to get the maximum value of a property in an array of arrays. I've been trying subqueries so far but apparently I don't understand very well how they work.
In an structure such as the one below, how do I query the city with more population among all states using the Data Explorer in Azure:
{
"id": 1,
"states": [
{
"name": "New York",
"cities": [
{
"name": "New York",
"population": 8500000
},
{
"name": "Hempstead",
"population": 750000
},
{
"name": "Brookhaven",
"population": 500000
}
]
},
{
"name": "California",
"cities":[
{
"name": "Los Angeles",
"population": 4000000
},
{
"name": "San Diego",
"population": 1400000
},
{
"name": "San Jose",
"population": 1000000
}
]
}
]
}
This is currently not possible as far as I know.
It would look a bit like this:
SELECT TOP 1 state.name as stateName, city.name as cityName, city.population FROM c
join state in c.states
join city in state.cities
--order by city.population desc <-- this does not work in this case
You could write a user defined function that will allow you to write the query you probably expect, similar to this: CosmosDB sort results by a value into an array
The result could look like:
SELECT c.name, udf.OnlyMaxPop(c.states) FROM c
function OnlyMaxPop(states){
function compareStates(stateA,stateB){
stateB.cities[0].poplulation - stateA.cities[0].population;
}
onlywithOneCity = states.map(s => {
maxpop = Math.max.apply(Math, s.cities.map(o => o.population));
return {
name: s.name,
cities: s.cities.filter(x => x.population === maxpop)
}
});
return onlywithOneCity.sort(compareStates)[0];
}
You would probably need to adapt the function to your exact query needs, but I am not certain what your desired result would look like.

How to GROUP BY in a CouchDB reduce function

I am trying to get to grips with map/reduce queries when using PouchDB/CouchDB.
I have a lot of documents in my database but I need to create a design that queries the documents and gives me all of the unique team names as a key and then tells me
a) how many unique wards are within each team
b) the total number of jobs per team (across all wards)
The structure of my data is:
{
"_id": "0448071807c0f37f53e06aab54034a42",
"_rev": "6-13fd78ada9c8833ec36a01af0acd5957",
"team": "Team A",
"ward": "Ward A",
"date": "2017-03-30",
"person": "Alice",
"bed": "Bed 001",
"jobs": [1,2,3,4]
}
{
"_id": "0448071807c0f37f53e06aab54034a42",
"_rev": "6-13fd78ada9c8833ec36a01af0acd5957",
"team": "Team A",
"ward": "Ward B",
"date": "2017-03-30",
"person": "Bob",
"bed": "Bed 001",
"jobs": [1,2]
}
{
"_id": "0448071807c0f37f53e06aab54034a42",
"_rev": "6-13fd78ada9c8833ec36a01af0acd5957",
"team": "Team A",
"ward": "Ward C",
"date": "2017-03-30",
"person": "Charles",
"bed": "Bed 001",
"jobs": [9,5]
}
{
"_id": "0448071807c0f37f53e06aab54034a42",
"_rev": "6-13fd78ada9c8833ec36a01af0acd5957",
"team": "Team B",
"ward": "Ward 00",
"date": "2017-03-30",
"person": "David",
"bed": "Bed 001",
"jobs": [1]
}
The output I would expect would be like this:
Team A
- 3 unique wards
- 8 jobs
Team B
- 1 unique ward
- 1 job
e.g.
{
"key": "Team A",
"value": {
"wards": 3,
"jobs": 8
}
}
{
"key": "Team B",
"value": {
"wards": 1,
"jobs": 1
}
}
My map is currently:
{
"all": {
"map": "function(doc) { emit(doc.team, doc) }"
}
}
It is the reduce where my struggle comes in.
EDIT
I have taken the suggestions used on CouchDB View equivalent of SUM & GROUP BY but this only goes half way towards my challenge.
If I use:
{
"all": {
"map": "function(doc) { emit([doc.team, doc.ward], 1) }",
"reduce": "function(keys, values) { return sum(values); }"
}
}
And then go to http://my-ip:5984/wardround_jobs/_design/teams/_view/all?group_level=1 then I see the unique teams (good) and the number of occurrences (also great) but I am unsure how I extend the reduce function to include the total number of jobs.
First, you have to emit the jobs length (has the number of jobs) :
function (doc) {
emit([doc.team,doc.ward],doc.jobs.length);
}
Then, you need a reduce function like this :
function (keys, values, rereduce) {
var stats = {uniq:0,jobs:0};
if (rereduce) {
for(var i=0;i<values.length;i++){
stats.uniq += values[i].uniq;
stats.jobs += values[i].jobs;
}
return stats;
}
stats.uniq = values.length;
stats.jobs = sum(values);
return stats;
}
For the first iteration, we return an object (stats) with the number of wards perm team (uniq) and the number of jobs (we sum the jobs length of every team/ward.
Then, for the rereduce, we simply aggregate the object`s values.

MongoDB Get concatenated values in a find method [duplicate]

Let's say I have a collection called 'people' with the following documents:
{
"name": "doug",
"colors": ["blue", "red"]
}
{
"name": "jack",
"colors": ["blue", "purple"]
}
{
"name": "jenny",
"colors": ["pink"]
}
How would I get a concatenated array of all the colors subarrays, i.e.?
["blue", "red", "blue", "purple", "pink"]
Well, Try should work fine for you!!
db.people.distinct("colors")
Try to use aggregate:
db.people.aggregate([
{$unwind:"$colors"},
{$group:{_id:null, clrs: {$push : "$colors"} }},
{$project:{_id:0, colors: "$clrs"}}
])
Result:
{
"result" : [
{
"colors" : [
"blue",
"red",
"blue",
"purple",
"pink"
]
}
],
"ok" : 1
}
Updated
If you want to get unique values in result's array, you could use $addToSet operator instead of $push in the $group stage.

Aggregation in arangodb using AQL

I'm attempting a fairly basic task in arangodb, using the SUM() aggregate function.
Here is a working query which returns the right data (though not yet aggregated):
FOR m IN pkg_spp_RegMem
FILTER m.memberId == "40289"
COLLECT member = m.memberId INTO g
RETURN { "memberId" : member, "amount" : g[*].m[*].items }
This returns the following results:
[
{
"memberId": "40289",
"amount": [
[
{
"amount": 50,
"description": "some description"
}
],
[
{
"amount": 50,
"description": "some description"
},
{
"amount": 500,
"description": "some description"
},
{
"amount": 0,
"description": "some description"
}
],
[
{
"amount": 0,
"description": "some description"
},
]
]
}
]
I am using Collect to group the results because a given memberId may have multiple'RegMem' objects. As you can see from the query/results, each object has a list of smaller objects called 'items', with each item having an amount and a description.
I want to SUM() the amounts by member. However, adjusting the query like this does not work:
FOR m IN pkg_spp_RegMem
FILTER m.memberId == "40289"
COLLECT member = m.memberId INTO g
RETURN { "memberId" : member, "amount" : SUM(g[*].m[*].items[*].amount) }
It returns 0 because it apparently can't find a field in the expanded items list called amount.
Looking at the results I can sort of understand why: the results are being returned such that items is actually a list, of lists of objects with amount/description. But I don't understand how to reference or expand the un-named list correctly to return the amount field values for the SUM() function.
Ideally the query should return the memberId and total amount, one row per member such that I can remove the filter and execute for all members.
Many thanks in advance if you can help!
Martin
PS I've worked through the AQL tutorial on the arangodb website and checked out the manual but what would really help me is loads more example queries to look through. If anyone knows of a resource like that or wants to share some of their own, 'much obliged. Cheers!
Edited: Misread the question the first time. The first one can be seen in theedit history, as it also contains some hints:
I replicated your data by creating some documents in this format (and some with only one item):
{
"memberId": "40289",
"items": [
{
"amount": 50,
"description": "some description"
},
{
"amount": 500,
"description": "some description"
}
]
}
Based on some of those types of documents, your non-summarized query should indeed be looking like this:
FOR m IN pkg_spp_RegMem
FILTER m.memberId == "40289"
COLLECT member = m.memberId INTO g
RETURN { "memberId" : member, "amount" : g[*].m[*].items }
The data returned:
[
{
"memberId": "40289",
"amount": [
[
{
"amount": 50,
"description": "some description"
},
{
"amount": 0,
"description": "some description"
}
],
[
{
"amount": 50,
"description": "some description"
},
{
"amount": 0,
"description": "some description"
}
],
[
{
"amount": 50,
"description": "some description"
}
],
[
{
"amount": 50,
"description": "some description"
},
{
"amount": 500,
"description": "some description"
}
],
[
{
"amount": 0,
"description": "some description"
}
],
[
{
"amount": 50,
"description": "some description"
},
{
"amount": 500,
"description": "some description"
}
]
]
}
]
Based on the non summarized version, you need to loop through the items of the groups that have been generated by the collect function and do your SUM() there.
In order to be able to SUM the items you must FLATTEN() them into a single list, before summarizing them.
FOR m IN pkg_spp_RegMem
FILTER m.memberId == "40289"
COLLECT member = m.memberId INTO g
RETURN { "memberId" : member, "amount" : SUM(
FLATTEN(
(
FOR r in g[*].m[*].items
RETURN r[*].amount
)
)
)
}
This results in:
[
{
"memberId": "40289",
"amount": 1250
}
]

Resources