INNER JOIN with GROUP BY in MongoDB - mongodb

I am trying to do an INNER JOIN with a GROUP BY in MongoDB. I am not getting any errors but no results either.
TableOne:
|Site|Starts|date |
| Google| 900 |2019-10-13|
| FB | 300 |2019-10-10|
| Yahoo | 100 |2019-10-11|
TableTWO:
|Finishes |date |
| 5 |2019-10-13|
| 4 |2019-10-10|
| 3 |2019-10-11|
Desired Output:
|Site|Starts|date | Finishes |
| Google| 900 |2019-10-13| 5 |
| FB | 300 |2019-10-10| 4 |
| Yahoo | 100 |2019-10-11| 3 |
SQL:
SELECT a.site, r.date, SUM(a.starts), SUM(r.finishes) FROM tableONE a INNER JOIN tableTWO r
ON a.site = r.site
GROUP BY a.site, r.date
ORDER BY 1
My MongoDB query:
db.tableONEaggregate([
{
'$match': { 'date': {
'$gte': '2019-09-19',
'$lte': '2019-10-19'
} }
},
{ '$lookup': {
'localField': 'site',
'from': 'tableTWO',
'foreignField': 'site',
'as': 'site_yo'
} },
{ '$unwind': '$site_yo' },
{
'$group': {
'_id': {
'site': '$site',
'date': '$site_yo.date',
},
'starts': {
'$sum': '$starts'
},
'finishes': {
'$sum': '$finishes'
}
}
}, {
'$project': {
'_id': 0,
'site': '$_id.site',
'date': '$_id.date',
'starts': '$starts',
'finishes': '$starts'
}
}, {
'$sort': {
'starts': -1
}
}, {
'$limit': 100
}
])
I am not getting any results. What us wrong with my Mongo Query?

I guess you're using a false foreign key within your Join. If you wanna join two tables you have to use ON primarykeyOfTable1 = ForeignkeyOfTable2
In your case this would be
SELECT a.site, r.date, SUM(a.starts), SUM(r.finishes) FROM tableONE a INNER JOIN tableTWO r
ON a.date = r.date
GROUP BY a.site, r.date
ORDER BY 1

Related

MongoError: PlanExecutor error during aggregation

I have tree records in mongodb but there could be many more, I'm getting shops by an ID coming from frontend
I need to get 20 records and group them by itemId and colorId, and get counts for every shop. the count of shops can be 1,2,3,....10etc..
this is output I need:
+--------+----------+-------+-------+-------+
| itemId | colorId | shop1 | shop2 | shop3 |
+========+==========+=======+=======+=======+
| 1 | colorId1 | 5 | 0 | 3 |
+--------+----------+-------+-------+-------+
| 2 | colorId2 | 3 | 0 | 0 |
+--------+----------+-------+-------+-------+
| 3 | colorId2 | 0 | 3 | 0 |
+--------+----------+-------+-------+-------+
| 2 | colorId1 | 0 | 5 | 0 |
+--------+----------+-------+-------+-------+
| 3 | colorId1 | 0 | 0 | 5 |
+--------+----------+-------+-------+-------+
here is my data and query - here shopId is string and it's work good.
but when I use this query on my local mashine, I'm getting this error:
MongoError: PlanExecutor error during aggregation :: caused by :: $arrayToObject requires an object with keys 'k' and 'v', where the value of 'k' must be of type string. Found type: objectId
but when I change shopId to the ObjectId I'm getting error.
ObjectId versoin
Per your request in the comments (if I got it right):
db.collection.aggregate([
{
"$match": {}// <-- Highly recommend you to use match due to the complexity of this query
},
{
$group: {
_id: 0,
data: {
$push: {
shopId: "$shopId",
shopItems: "$shopItems"
}
},
shopIds: {
"$push": {
shopId: "$shopId",
"count": 0
}
}
}
},
{
$unwind: "$data"
},
{
$unwind: "$data.shopItems"
},
{
$group: {
_id: {
itemId: "$data.shopItems.itemId",
colorId: "$data.shopItems.colorId"
},
data: {
$push: {
shopId: "$data.shopId",
count: "$data.shopItems.itemCount"
}
},
existing: {
$push: {
shopId: "$data.shopId",
"count": 0
}
},
shopIds: {
$first: "$shopIds"
}
}
},
{
"$addFields": {
"missing": {
"$setDifference": [
"$shopIds",
"$existing"
]
}
}
},
{
$project: {
data: {
$concatArrays: [
"$data",
"$missing"
]
}
}
},
{
$unwind: "$data"
},
{
$sort: {
"data.shopId": 1
}
},
{
$group: {
_id: "$_id",
counts: { // here you can change this key
$push: "$data"
},
totalCount: {
$sum: "$data.count" // if you want it
}
}
}
])
After the first $match, we $group in order to get all shopIds in each document.
Next we $unwind and $group by the group you wanted: by colorId and itemId. Then we are adding all the shops with count 0 and removing the ones that do have actual count. Last three steps are just for sorting, summing and formating.
You can play with it here.

How to search in MongoDB an element depending on the previous one?

I'm having to deal with a query that is kind of strange. I'm creating an app for boat tracking: I have a collections of documents with the timestamp and the Port ID where it was the board at that moment.
After sorting all the documents of this collection by the timestamp descending, I need to grab the elements that have the same Port ID in that range of time.
For example:
timestamp | port_id
2021-11-10T23:00:00.000Z | 1
2021-11-10T22:00:00.000Z | 1
2021-11-10T21:00:00.000Z | 1
2021-11-10T20:00:00.000Z | 2
2021-11-10T19:00:00.000Z | 2
2021-11-10T18:00:00.000Z | 2
2021-11-10T17:00:00.000Z | 1
2021-11-10T16:00:00.000Z | 1
2021-11-10T15:00:00.000Z | 1
Having this data (sorted by timestamp), I would have to grab the first 3 documents. The way I'm doing this now, is grabbing 2000 documents and implementing a filter function in the application level.
Another approch would be grabbing the first element, and then filtering by that port id, but that returns me 6 elements, not the first 3.
Do you know any way to perform a query like this in Mongo? Thanks!
Use $setWindowFields
db.collection.aggregate([
{
$setWindowFields: {
partitionBy: "",
sortBy: { timestamp: -1 },
output: {
c: {
$shift: {
output: "$port_id",
by: -1,
default: "Not available"
}
}
}
}
},
{
$set: {
c: {
$cond: {
if: { $eq: [ "$port_id", "$c" ] },
then: 0,
else: 1
}
}
}
},
{
$setWindowFields: {
partitionBy: "",
sortBy: { timestamp: -1 },
output: {
c: {
$sum: "$c",
window: { documents: [ "unbounded", "current" ] }
}
}
}
},
{
$match: { c: 1 }
},
{
$unset: "c"
}
])
mongoplayground

Mongodb match on a lookup table not working

How can I match a condition on a column that belongs to a joined table?
For eg:
Consider two collections A and B:
Collection A:
id|name|status
++++++++++++++
1 |Rock|1
2 |Sam |1
3 |Jack|1
Collection B:
id|userId| loc | status
+++++++++++++++++++++++++++++++++++
1| 1 |[11.111,22.321] | -1
2| 1 |[16.22,33.213] | 1
3| 2 |[334.11,242.321]| 1
4| 3 |[1.111,224.321] | 1
I want to get all users with their current location based on status field in collection B, that is user's current location will have status 1 in B and previous location have status -1.
So, the result I want is like:
+++++++++++++++++
id:1, name: Rock, status: 1, userLocTable: [{id:2, userId:1, loc: [16.22,33.213], status: 1}]
What I am doing is:
db.collectionA.aggregate([
{
$lookup: {
from: collectionB,
localField: id,
foreignField: userId,
as: userLocTable
}
},
{
$match:{
userLocTable.status: 1
}
}
])
But I am getting all rows(both status 1 and -1) from the lookup table (B) instead of only records with status 1. What could be the problem. Any help wuld be appreciated. Thanks!
Try this one,
db.collectionA.aggregate([
{
"$lookup": {
"from": collectionB,
"localField": id,
"foreignField": userId,
"as": userLocTable
}
},
{
"$addFields": {
"userLocTable": {
"$filter": {
"input": "$userLocTable",
"as": "userLoc",
"cond": {
"$eq": [ "$$userLoc.status", "1" ]
}
}
}
}
}
])

MongoDB - How to extract field with max value

I have a MongoDb collection genre_count as
user | genre | count
-----+---------------+-------
1 | Western | 2
1 | Adventure | 1
1 | Comedy | 5
2 | Western | 3
2 | Thriller | 1
2 | Romance | 2
I need to extract the genre for each user with maximum count i.e. for user 1 , the genre with maximum count is Comedy with Count 5. I tried using couple of ways as:
db.genre_count.aggregate([
{
$group:{
_id:{
user:"$user",
genre:"$genre"
},
max_val:{
$max: "$count"
}
}
}
])
I thought this would work but it returned the count of the user for each genre so basically it returned me all the records.
Then I tried another solution which worked partially in :
db.genre_count.aggregate([
{
$group:{
_id:{
user:"$user"
},
max_val:{
$max: "$count"
}
}
}
])
But this only returned the maximum value as it has no corresponding genre information for that maximum value. Is there any way I can get the desired result?
To return the maximum count and genre list, you need to use the $max in your group stage to return the maximum "Count" for each group then use $push accumulator operator to return a list of "Genre Name" and "Count" for each group.
From there you need to use the $map operator in your $project stage to return a list of genre_names alongside the maximum count. The $cond here is used to compare each genre count to the maximum value.
db.genre_count.aggregate([
{ '$group': {
'_id': '$user',
'maxCount': { '$max': '$count' },
'genres': {
'$push': {
'name': '$genre',
'count': '$count'
}
}
}},
{ '$project': {
'maxCount': 1,
'genres': {
'$setDifference': [
{ '$map': {
'input': '$genres',
'as': 'genre',
'in': {
'$cond': [
{ '$eq': [ '$$genre.count', '$maxCount' ] },
'$$genre.name',
false
]
}
}},
[false]
]
}
}}
])
I think you can use this aggregate:
db.genre_count.aggregate([
{
$sort: {user:1, count:1}
},
{
$group:
{
_id: "$user",
maxCount: {$max: "$count"},
genre: {$last: "$genre"}
}
}])

Group count with MongoDB using aggregation framework

Let's say my MongoDB schema looks like this:
{car_id: "...", owner_id: "..."}
This is a many-to-many relationship. For example, the data might look like this:
+-----+----------+--------+
| _id | owner_id | car_id |
+-----+----------+--------+
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 4 | 2 | 1 |
| 5 | 2 | 2 |
| 6 | 3 | 4 |
| 7 | 3 | 5 |
| 8 | 3 | 6 |
| 9 | 3 | 7 |
| 10 | 1 | 1 | <-- not unique
+-----+----------+--------+
I want to get the number of cars owned by each owner. In SQL, this might look like:
SELECT owner_id, COUNT(*) AS cars_owned
FROM (SELECT owner_id FROM car_owners GROUP BY owner_id, car_id) AS t
GROUP BY owner_id;
In this case, the result would look like this:
+----------+------------+
| owner_id | cars_owned |
+----------+------------+
| 1 | 3 |
| 2 | 2 |
| 3 | 4 |
+----------+------------+
How can I accomplish this same thing using MongoDB using the aggregation framework?
To accommodate the potential duplicates, you need to use two $group operations:
db.test.aggregate([
{ $group: {
_id: { owner_id: '$owner_id', car_id: '$car_id' }
}},
{ $group: {
_id: '$_id.owner_id',
cars_owned: { $sum: 1 }
}},
{ $project: {
_id: 0,
owner_id: '$_id',
cars_owned: 1
}}]
, function(err, result){
console.log(result);
}
);
Gives a result with a format of:
[ { cars_owned: 2, owner_id: 10 },
{ cars_owned: 1, owner_id: 11 } ]
$group is similar to SQL Group by command. In the below example, we're going to aggregate companies on the basis of the year in which they were founded. And calculate the average number of employees for each company.
db.companies.aggregate([{
$group: {
_id: {
founded_year: "$founded_year"
},
average_number_of_employees: {
$avg: "$number_of_employees"
}
}
}, {
$sort: {
average_number_of_employees: -1
}
}
])
This aggregation pipeline has 2 stages
$group
$sort
Now, fundamental to the $group stage is the _id field that we specify as the part of the document. That is the value of the $group operator itself using a very strict interpretation of the arrogation framework syntax. _id is how we define, how we control, how we tune what the group stage uses to organize the documents that it sees.
The below query find the relationships of the people with companies using $sum operator:
db.companies.aggregate([{
$match: {
"relationships.person": {
$ne: null
}
}
}, {
$project: {
relationships: 1,
_id: 0
}
}, {
$unwind: "$relationships"
}, {
$group: {
_id: "$relationships.person",
count: {
$sum: 1
}
}
}, {
$sort: {
count: -1
}
}])