Mongo search Array with ONLY one match in $or - mongodb

I have an array of objects to search. I want to ensure that ONLY ONE MATCH in a list of elements is returned. The following works to find if ANY of the elements in the array matches:
db.users.find({
$or:[
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f00e' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f02d' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a7d6f1aaf645c94f2cf' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a7b6f1aaf645c94f132' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f02f' )} } }
]
})
However, records can have more than one of these ids, and I want to isolate to ensure that only one of these matches. Please note that these particular 5 matches that we care about are a subset of a list that is over 2,000 strong.
In other words, I need to match ONLY ONE of these - and don't care if they match the other 2,000. That's why I can't use a "subscriberOf.1":{$exists:false}, to weed these out.
I can run multiple queries manually, if I can figure out how - maybe do a specific match on one id, then a $nor on the rest?
Any help would be appreciated.

Please try this :
var inputArr = [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a796f1aaf645c94f00e")
]
db.yourCollectionName.aggregate([{ $addFields: { matchingCount: { $size: { $setIntersection: ["$subscriberOf.town", inputArr] } } } },
{ $match: { matchingCount: 1 } }, {$project : {matchingCount:0}}])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e0be0f9400289966e8c9bb8"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
/* 2 */
{
"_id" : ObjectId("5e0be106400289966e8c9da2"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02d")
]
}
}
/* 3 */
{
"_id" : ObjectId("5e0be2ab400289966e8ce29b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7d6f1aaf645c94f2cf")
]
}
}
/* 4 */
{
"_id" : ObjectId("5e0be2d7400289966e8cea84"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 5 */
{
"_id" : ObjectId("5e0be2eb400289966e8cee3b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
/* 6 */
{
"_id" : ObjectId("5e0be303400289966e8cf210"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 7 */
{
"_id" : ObjectId("5e0be327400289966e8cf92a"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
Result :
/* 1 */
{
"_id" : ObjectId("5e0be0f9400289966e8c9bb8"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
/* 2 */
{
"_id" : ObjectId("5e0be106400289966e8c9da2"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02d")
]
}
}
/* 3 */
{
"_id" : ObjectId("5e0be2ab400289966e8ce29b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7d6f1aaf645c94f2cf")
]
}
}
/* 4 */
{
"_id" : ObjectId("5e0be2d7400289966e8cea84"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 5 */
{
"_id" : ObjectId("5e0be2eb400289966e8cee3b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
Note : Returning only docs where exactly one matches, 6 & 7 are not retained.
Ref : Aggregation-pipeline-operators

MongoDB aggregation offers better solution to achieve that.
First, we extend array into single document with $unwind. Then we apply $matchstage to filter desired id's. In the last stage, we $group with accumulator operator $addToSet to create array with unique values.
db.collection.aggregate([
{
$unwind: "$subscriberOf"
},
{
$match: {
"subscriberOf.town": {
$in: [
ObjectId("58252a796f1aaf645c94f00e"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
},
{
$group: {
_id: "$_id",
subscriberOf: {
$addToSet: "$subscriberOf"
}
}
}
])
MongoPlayground
EDIT: If you want to export the result, follow these steps:
Iterate with .forEach method aggregation cursor to retrieve all values.
Create .js file (for instance export.js) with your aggregation, wrap it inside a function like this:
function exporter(){
db.collection.aggregate(...).forEach(function(doc){
printjson(doc);
}
})
exporter();
Now, call mongo with js file and redirect output into file from cmd / command shell:
path\to\mongo\mongo[.exe] --quiet [-u username -p password] [host:port/]database export.js > export.csv 2>&1

Related

Query to count the number of documents for each user

I have a collection named 'captures' and the documents within it have the field 'username'
a document looks something like this
/* 1 */
{
"_id" : ObjectId("622b951a026ca3a73f5a2a1c"),
"username" : "andre",
"data" : {
"metadata" : {
"start" : "2022-02-24T09:32:22.390Z",
...
},
...
}
}
/* 2 */
{
"_id" : ObjectId("9255941b026ca3a73f5a2a1c"),
"username" : "andre",
"data" : {
"metadata" : {
"start" : "2022-05-10T03:12:23.440Z",
...
},
...
}
}
/* 3 */
{
"_id" : ObjectId("7775941b026ca3a73f5a2a1c"),
"username" : "bob",
"data" : {
"metadata" : {
"start" : "2022-05-16T12:24:12.002Z",
...
},
...
}
}
/* 4 */
{
"_id" : ObjectId("3215331b026ca3a73f5a2a1c"),
"username" : "bob",
"data" : {
"metadata" : {
"start" : "2022-05-18T12:24:12.002Z",
...
},
...
}
}
I'd like to return a count of documents for each distinct username, where 'start' is after 2022-02-24T09:32:22.390Z
the above example would return something like:
{ "user" : "andre", "count" : 1 }
{ "user" : "bob", "count" : 2 }
I've tried using count, distinct, aggregate without success...
This is pretty simple to do with the aggregation framework:
[
{
$project: {
_id: 0,
user: '$username',
start: {
$toDate: '$data.metadata.start'
}
}
},
{
$match: {
start: {
$gt: Date('2022-02-24T09:32:22.390Z')
}
}
},
{
$group: {
_id: '$user',
user: {
$first: '$user'
},
count: {
$sum: 1
}
}
}
]
By the way you should store dates as Date objects, not strings, it will make your life easier.

MongoDB get user which are new today

I am trying to find a user list which is new for day-1. I have written the query to find the users who arrived till the day before yesterday and the list of users arrived yesterday. Now I want minus those data how can I do that in a single aggregate function.
Function to get the list before yesterday
db.chat_question_logs.aggregate([
{
$match : {"createdDate":{$lte: ISODate("2020-04-29T00:00:00Z")}}
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
similarly for the day-1 is as below
db.chat_question_logs.aggregate([
{
$match : {"createdDate":{$gte: ISODate("2020-04-30T00:00:00Z"),$lte: ISODate("2020-05-01T00:00:00Z")}}
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
Result JSON are as below
/* 1 */
{
"_id" : {
"userId" : "2350202241750776"
},
"count" : 1
},
/* 2 */
{
"_id" : {
"userId" : "26291570771793121"
},
"count" : 1
},
/* 3 */
{
"_id" : {
"userId" : "2742872209107866"
},
"count" : 5
},
/* 4 */
{
"_id" : {
"userId" : "23502022417507761212"
},
"count" : 1
},
/* 5 */
{
"_id" : {
"userId" : "2629157077179312"
},
"count" : 43
}
How can I find the difference.
It sounds like what you want is to get all users created yesterday (which is the 28th in this example).
db.chat_question_logs.aggregate([
{
$match : { $and: [
{ "createdDate":{$lt: ISODate("2020-04-29T00:00:00Z")} },
{ "createdDate": {$gte: ISODate("2020-04-28T00:00:00Z") }}
] }
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
Is this what you want?
Hi found the solution which is below
I used the group and first appearance of the Id and then filter record on date which I wanted.The query is as below
db.chat_question_logs.aggregate([
{
$group:
{
_id: "$userInfo.userId",
firstApprance: { $first: "$createdDate" }
}
},
{
$match : { "firstApprance": { $gte: new ISODate("2020-05-03"), $lt: new ISODate("2020-05-05") } }
}
])

how to count/sum array on mongodb?

I have a collection where every documents has an array named "contacts".
Sample Docs :
{
"_id" : ObjectId("5660c2dfdfdfcba2d47baa2d9"),
"name" : john,
"contacts" : [
{
"name" : "ari",
"phone" : "12341234"
},
{
"name" : "dea",
"phone" : "34234234"
}
]
},
{
"_id" : ObjectId("5660c2dfdfdfcba2d47baa2d9"),
"name" : joni,
"contacts" : [
{
"name" : "budi",
"phone" : "13341234"
},
{
"name" : "ade",
"phone" : "3242343"
},
{
"name" : "are",
"phone" : "64545345"
}
]
}
I want to know get total count of contacts for all docs.
From sample docs output should be 5 contacts
Thank you for helping me.
You can try below query :
1) If contacts array exists in every doc :
db.collection.aggregate([
/** project only needed field contacts with size of array in each doc */
{
$project: {
_id: 0,
contacts: {
$size: "$contacts"
}
}
},
/** group on empty(without any filter) & sum contacts field */
{
$group: {
_id: "",
TotalContacts: {
$sum: "$contacts"
}
}
},
/** Optional projection */
{
$project: {
_id: 0
}
}
])
2) If contacts array field might not exist in every doc, if it exists & not an array then an additional type check has to be done in below $cond :
db.collection.aggregate([
/** group on empty(without any filter) & sum contacts field's size if contacts exists else sum 0, You've can have optional projection as first stage like above query */
{
$group: {
_id: "",
TotalContacts: {
$sum: {
$cond: [
{
"$ifNull": [
"$contacts",
false
]
},
{
$size: "$contacts"
},
0
]
}
}
}
},
/** Optional projection */
{
$project: {
_id: 0
}
}
])
Test : MongoDB-Playground

How can I correctly output an array of objects in the reverse order from mongodb?

Comments are saved in an array of objects. How can I correctly output them in reverse order (comments from newest to oldest)?
My db:
{"_id":{"$oid":"5e3032f14b82d14604e7cfb7"},
"videoId":"zX6bZbsZ5sU",
"message":[
{"_id":{"$oid":"5e3032f14b82d14604e7cfb8"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"1"
},
{"_id":{"$oid":"5e3032f14b82d14604e7cfb9"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"2"
},
....
]
My sheme Mongoose:
const schema = new Schema({
videoId: { type: String, isRequired: true },
message: [
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: { type: String }
},
]
});
My code:
const userComments = await Comment.find(
{ videoId: req.query.videoId },
{ message: { $slice: [skip * SIZE_COMMENT, SIZE_COMMENT] } }
)
.sort({ message: -1 })
.populate('message.user', ['avatar', 'firstName']);
but sort not working;
thanks in advance!
You can simply use $reverseArray to reverse content of an array.
db.collection.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
}
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
}
]
}
Your Query : You can use native MongoDB's $lookup instead of .populate() , So try below :
Comments.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}, {
$lookup: {
from: "User",
let: { ids: "$message.user" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$ids"] } }
},
{ $project: { avatar: 1, firstName: 1, _id: 0 } }
],
as: "userData"
}
}
])
You're going to want to use one of two things:
The MongoDB aggregation framework
Sorting within the application
If you choose to use the MongoDB aggregation framework, you'll likely want to use the $unwind operation to expand the array into separate documents, then sorting those documents with the $sort operation.
You can see more on how to do that in this ticket:
how to sort array inside collection record in mongoDB
You could also do this within your application by first executing a query, and then sorting each of the arrays in the result set.
Best,

How do get the last substring using the MongoDB aggregation framework?

Let's say I have these documents
> db.coll.find().toArray()
[
{
"_id" : ObjectId("5a36c3e218948d0722457078"),
"locality" : "Nasimi, Baku, Azerbaijan"
},
{
"_id" : ObjectId("5a36c3e218948d0722457079"),
"locality" : "Garland, TX, USA"
},
{
"_id" : ObjectId("5a36c3e218948d072245707a"),
"locality" : "Halytskyi District, Lviv, Lviv Oblast, Ukraine"
},
{
"_id" : ObjectId("5a36c3e218948d072245707b"),
"locality" : "Tozeur, Tunisia"
}
]
I would like to get only the country, i.e. whatever is after the last comma (', ').
e.g.
[
"Azerbaijan",
"USA",
"Ukraine",
"Tunisia"
]
I've managed to get the index of the first comma and what is after that but I can't figure out how to to get the last one.
db.coll
.aggregate([
{
$project: {
lastIndexOf: { $indexOfBytes: [ '$locality', ', ' ] },
locality: '$locality',
}
},
{
$project: {
lastIndexOfPlusTwo: { $add: [ '$lastIndexOf', 2 ] },
locality: '$locality',
}
},
{
$project: { country: { $substr: [ '$locality', '$lastIndexOfPlusTwo', -1 ] } }
}
]).pretty()
{
"_id" : ObjectId("5a36c3e218948d0722457078"),
"country" : "Baku, Azerbaijan"
}
{
"_id" : ObjectId("5a36c3e218948d0722457079"),
"country" : "TX, USA"
}
{
"_id" : ObjectId("5a36c3e218948d072245707a"),
"country" : "Lviv, Lviv Oblast, Ukraine"
}
{
"_id" : ObjectId("5a36c3e218948d072245707b"),
"country" : "Tunisia"
}
This works but it loads all the results in memory in the JavaScript Mongo Shell but would be ideal if it can be done with a single MongoDB aggregation command.
var res2 = db.coll.aggregate();
res2 = res2.toArray().map(function(doc) {
var lastIndex = doc.locality.lastIndexOf(',');
return doc.locality.slice(lastIndex + 2);
});
[ "Azerbaijan", "USA", "Ukraine", "Tunisia" ]
Maybe with $let, $split?
You can try below aggregation query.
{"$arrayElemAt":["$$locality",-1]} to access the last element from the array.
db.coll.aggregate([{"$project":{
"_id":0,
"last":{
"$let":{
"vars":{"locality":{"$split":["$locality",","]}},
"in":{"$arrayElemAt":["$$locality",-1]}
}
}
}}])