take union of results in mongoid - mongoid

I have two mongoid documents Class1 and Class2,both have subject field
Now I can get result from individual classes group by subject like this
#class1_subject = Class1.all.group_by {|s| s.subject }
#class2_subject = Class2.all.group_by {|s| s.subject }
now #class1_subject contains =>
{
"algebra" => [
#<Class1 _id: 51af35f4066104e046000003, name: "root", subject: "algebra">
],
"algebra-II" => [
#<Class1 _id: 51af3da0066104f448000003, name: "roo2", subject: "algebra-II">
]
}
and #class2_subject contains =>
{
"algebra" => [
#<Class2 _id: 51af07d606610423e7000002,.........,subject: "algebra">
]
}
After union i expecting this result
{
"algebra" => [
#<Class1 _id: 51af35f4066104e046000003,name: "root", subject: "algebra">,
#<Class2 _id: 51af07d606610423e7000002,.........,subject: "algebra">
],
"algebra-II" => [
#<Class1 _id: 51af3da0066104f448000003, name: "roo2", subject: "algebra-II">
]
}
How can I do this??

This can be an approximation:
#class1_subject.keys.each do |key|
#class2_subject[key] ||= []
#class2_subject[key] += #class1_subject[key]
end
#class2_subject now has the result

Related

MongoDB: nested array count + original document

I have the following document structure which contains an array of votes:
{ _id: ObjectId("6350e2c1a15e0e656f4a7472"),
category: 'business',
votes:
[ { voteType: 'like',
userId: ObjectId("62314007da34df3f32f7cfc0") },
{ voteType: 'like',
userId: ObjectId("6356b5cbe2272ebf628451b") } ] }
What I would like to achieve is to add for each document the sum of votes for which voteType = like, while keeping the original document, such as:
[ [{ _id: ObjectId("6350e2c1a15e0e656f4a7472"),
category: 'business',
votes:
[ { voteType: 'like',
userId: ObjectId("62314007da34df3f32f7cfc0") },
{ voteType: 'like',
userId: ObjectId("6356b5cbe2272ebf628451b") } ] }, {sum: 2, voteType: "like"} ], ...]
At the moment, the only workaround that I found is through an aggregation although I cannot manage to keep the original documents in the results:
db.getCollection('MyDocument') .aggregate([ {
$unwind: "$votes" }, {
$match: {
"votes.voteType": "like",
} }, {
$group: {
_id: {
name: "$_id",
type: "$votes.voteType"
},
count: {
$sum: 1
}
} },
{ $sort : { "count" : -1 } }, {$limit : 5}
])
which gives me:
{ _id: { name: ObjectId("635004f1b96e494947caaa5e"), type: 'like' },
count: 3 }
{ _id: { name: ObjectId("63500456b96e494947cbd448"), type: 'like' },
count: 3 }
{ _id: { name: ObjectId("63500353b6c7eb0a01df268e"), type: 'like' },
count: 2 }
{ _id: { name: ObjectId("634e315bb7d17339f8077c39"), type: 'like' },
count: 1 }
You can do it like this:
$cond with $isArray - to check if the votes property is of the type array.
$filter - to filter votes based on voteType property.
$size - to get the sized of the filtered array.
db.collection.aggregate([
{
"$set": {
"count": {
"$cond": {
"if": {
"$isArray": "$votes"
},
"then": {
"$size": {
"$filter": {
"input": "$votes",
"cond": {
"$eq": [
"$$this.voteType",
"like"
]
}
}
}
},
"else": 0
}
}
}
}
])
Working example

Mongo aggregation framework match a given _id

My model :
const scheduleTaskSchema = new Schema({
activity: { type: Object, required: true },
date: { type: Date, required: true },
crew: Object,
vehicle: Object,
pickups: Array,
details: String,
});
const ScheduleTaskModel = mongoose.model("schedule_task", scheduleTaskSchema),
and this aggregation pipeline :
let aggregation = [
{
$sort: {
"pickups.0.time": 1,
},
},
{
$group: {
_id: "$date",
tasks: { $push: "$$ROOT" },
},
},
{ $sort: { _id: -1 } },
];
if (hasDateQuery) {
aggregation.unshift({
$match: {
date: { $gte: new Date(start_date), $lte: new Date(end_date) },
},
});
} else {
aggregation.push({ $limit: 2 });
}
const scheduledTasksGroups = await ScheduleTaskModel.aggregate(aggregation);
the crew object can have arbitrary number of keys with this structure :
crew : {
drivers: [
{
_id: "656b1e9cf5b894a4f2v643bc",
name: "john"
},
{
_id: "567b1e9cf5b954a4f2c643bhh",
name: "bill"
}
],
officers: [
{
_id: "655b1e9cf5b6632a4f2c643jk",
name: "mark"
},
{
_id: "876b1e9af5b664a4f2c234bb",
name: "jane"
}
],
//...any number of keys that contain an array of objects that all have an _id
}
I'm looking for a way to return all documents (before sorting/grouping) that contain a given _id anywhere within the crew object without knowing which key to search,it can be many different keys that all contain an array of objects that all have an _id
Any ideas ?
You can use $objectToArray for this:
db.collection.aggregate([
{$addFields: {crewFilter: {$objectToArray: "$crew"}}},
{$set: {
crewFilter: {$size: {
$reduce: {
input: "$crewFilter",
initialValue: [],
in: {$concatArrays: [
"$$value",
{$filter: {
input: "$$this.v",
as: "member",
cond: {$eq: ["$$member._id", _id]}
}
}
]
}
}
}}
}},
{$match: {crewFilter: {$gt: 0}}}
])
See how it works on the playground example

How can I combine the results of 3 queries in MongoDB?

I made the following filter in hopes that I would be combining the results from all 3 $and arrays but it is only matching one of those blocks.
How can I combine the results of what would be returned from each $and array if conditions are met. Hopefully that's clear. I don't know what to call the $and array.
const filter = {
$or: [
{
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
name: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
description: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
tags: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
},
],
};
await Content.paginate(filter, options, (err, result) => {
if (err) {
res.status(500).send(err);
} else {
res.json(result);
}
});
EDIT: Below is an example of two entries that would be found in the database. The way it should work is it should use category, subCategory, and contentType to filter out the entries in the database so that what I have now are only the entries which have the same category, subCategory, and contentType as specified in req.query, I'll call this the firstFilterResult. From there, I am trying to search within firstFilterResult to see if I have entries that have name, tag, or description matches. So basically catgeory, subCategory and contentType are just used to narrow down the results so that I can find matches for name, tag, and description. My code above doesn't do exactly this but this is the idea behind it and I thought that what I have would do similar, but I guess I'm wrong.
contents: [
{
tags: [
'food',
'drinks',
'card',
'account'
],
_id: '1d13ff7m6db4d5417cd608f4',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'In here I will begin to talk about...',
content_id: '5dbcb998ad4144390c244093',
contentType: 'quiz',
date: '2019-06-03T04:00:00.000Z',
category: 'food',
image: 'https://IMAGE.PNG',
__v: 0
},
{
tags: [
'computer',
'laptop'
],
_id: '7d1b940b1c9d44000025db8c',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'This is another description',
content_id: '5f1b963d1c9d44000055db8d',
contentType: 'tool',
date: '2019-06-03T04:00:00.000Z',
category: 'money',
image: 'https://IMAGE.PNG',
__v: 0
}
]
I finally got it to work with this
const catFilter =
req.query.category !== "" ? { category: req.query.category } : {};
const subCatFilter =
req.query.subCategory !== "" ? { tags: req.query.subCategory } : {};
const typeFilter =
req.query.contentType !== ""
? { contentType: req.query.contentType }
: {};
const filter = {
$and: [
{
$or: [
{
name: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
description: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
tags: {
$regex: req.query.searchTerm,
$options: "i",
},
},
],
},
catFilter,
subCatFilter,
typeFilter,
],
};
Since each element of the $or contains the same 3 checks with a single one that varies, these can be separated out, and the $or is then only needed if a search term is specified.
Passing options:"i" makes the entire regex match case insensitive, so it is not necessary to surround the search string with (?i) and (?-i)
The following should build the filter that you are attempting, without using empty objects:
// base query that checks the common fields
var filter = {
category: req.query.category,
tags: req.query.subCategory,
contentType: req.query.contentType
};
// if a search term is provided, add in the additional critera
if (req.query.searchTerm !== "") {
var regex = {
$regex: req.query.searchTerm,
options:"i"
};
filter['$or'] = [
{ name: regex },
{ description: regex },
{ tags: regex }
]
}
If this doesn't obtain the results you're after, please edit the question and add in some sample documents so we can see the problem.

Formulating MongoDB Queries

Hello members of stackoverflow, I'm totally new to mongoDB hence I'm having trouble with formulating some queries in it. I've been trying to do it for quite some time but have failed to do so. Please refer to the following code:
#create database
use db_m_1
#create collection (all tables in database)
db.createCollection('articles')
#this is your article in noSQL
#change this according to your info
article = {
_id: '1',
title: 'article1',
date_published: ISODate("2014-09-17T23:25:56.314Z"),
written_by: [{
_id: '1',
name: 'Osama'
}],
comments: [{
_id: '1',
content: 'This is very good article',
date: ISODate("2014-09-17T23:25:56.314Z"),
comment_by: {
_id: '1',
name: 'Osama'
password: 'pass'
}
}]
}
#this is used to insert
db.articles.insert(article)
#queries
#1-- know which comments were made on article 'a beginning' and by whom
#create query
let q1 = {
title: 'a beginning'
}
#create projection
db.articles.find(q1, {"Comments.username": 1, "Comments.comment_content": 1})
#2-- which staff members were working on article 'sample'
let q2 = {
title: 'sample'
}
let p2 = {
written_by: 1
}
db.articles.find(q2, p2);
#5-- which articles were written in 2014
let q5 = {
date_published: new Date("2014-10-01T00:00:00.000Z")}
}
q5 = {date_published: new Date("2014-05-13T00:00:00.000+00:00")}
let p5 = {
title: 1,
written_by: 1
}
I need help with query 3 and 4.
3-- how many articles has each staff member worked on?
4-- which staff members have worked on more than one article with maximum number of article writing staff on top
Thank you. :)
3-- how many articles has each staff member worked on?
4-- which staff members have worked on more than one article with
maximum number of article writing staff on top
These two aggregations solve the queries 3 and 4 respectively.
# 3
db.articles.aggregate( [
{
$unwind: "$written_by"
},
{
$group: {
_id: "$written_by.name",
count: { $sum: 1 }
}
},
{
$project: {
name: "$_id",
_id: 0,
count: 1
}
}
] )
# 4
db.articles.aggregate( [
{
$unwind: "$written_by"
},
{
$group: {
_id: "$written_by.name",
count: { $sum: 1 }
}
},
{
$match: {
count: { $gt: 1 }
}
},
{
$project: {
name: "$_id",
_id: 0,
count: 1
}
},
{
$sort: { count: -1 }
}
] )
Some test data:
article1 = {
_id: '1',
title: 'article1',
date_published: ISODate("2014-09-17T23:25:56.314Z"),
written_by: [ {
_id: '1',
name: 'Osama'
} ],
comments: [ {
_id: '1',
content: 'This is very good article',
date: ISODate("2014-09-17T23:25:56.314Z"),
comment_by: {
_id: '1',
name: 'Osama',
password: 'pass'
}
} ]
}
article2 = {
_id: '2',
title: 'article2',
written_by: [ {
_id: '9',
name: 'Krish'
},
{
_id: '1',
name: 'Osama'
} ],
comments: "..."
}
the data structure you use isn't proper for this kind of queries. I recommend you to have one collection per your entities, for this example we need 3 collections with these schema:
// article doc schema
{
_id: int,
title: String,
date_published: ISODate,
};
// writer doc schema
{
_id: int,
name: String,
articles_ids: [int]
};
// comment doc schema
{
_id: int,
content: String,
date: ISODate,
by_id: int,
article_id: int
};
by this way you can make complex queries, and you have to use aggregate query for your need. here is some examples:
// Query 03
// how many articles has each staff member worked on?
let piplines = [
{
// separate writers by their names
$group: {
_id: "$name",
totalArticles: { $size: "$articles_ids" }
}
}
];
let arrayOfStaffStates = writerCollection.aggregate(piplines);
// Query 04
// which staff members have worked on more than one article
let piplines = [
{
// separate writers by their names
$group: {
_id: "$name",
totalArticles: { $size: "$articles_ids" }
},
// match they who have more than 1 articles
$match: {
totalArticles: { $gte: 1 }
}
}
];
let stuffs = writerCollection.aggregate(piplines);

MongoDB perform $match with two input array values?

In MongoDB, I am trying to write a query where I have two input array Bills, Names where the first one contains billids and the second one contains names of the person. Also in reality Bills at index i and Names at index i is the actual document which we want to search in MongoDB.
I want to write a query such that Bills[i] = db_billid && Names[i] = db_name which means I want to return the result where at a particular index both billid and name matches.
I thought of using $in but the thing is I can apply $in in Bills but I don't know at which index that billid is found.
{ $and: [{ billid: { $in: Bills } }, {name: Names[**index at which this bill is found]}] }
Can anyone please help me how can I solve this ??
MongoDB Schema
var transactionsschema = new Schema({
transactionid: {type: String},
billid: {type: String},
name: {type: String}
});
Sample documents in MongoDB
{ _id: XXXXXXXXXXX, transactionid: 1, billid : bnb1234, name: "sudhanshu"}, { _id: XXXXXXXXXXX, transactionid: 2, billid : bnb1235, name: "michael"}, { _id: XXXXXXXXXXX, transactionid: 3, billid : bnb1236, name: "Morgot"}
Sample arrays
Bills = ["bill1", "bill2", "bill3"], Names = ["name1", "name2", "name"]
Edit - If $in can work in array of objects then I can have array of object with keys as billid and name
var arr = [{ billid: "bill1", "name": "name1"}, {billid: "bill2", "name": "name2"}, {billid: "bill3", "name": "name3"}]
But the thing is then how can put below query
{ $and: [{ billid: { $in: arr.Bills } }, {name: arr.Names}] }
Knowing that bills are unique but names may have duplicates you can use $indexOfArray to get index of matching bill and then use that index to compare names array at evaluated index (using $arrayElemAt to retrieve value). Also you have to check if value returned by $indexOfArray is not equal to -1 (no match)
var bills = ["bnb1234", "bnb1235"];
var names = ["sudhanshu", "michael"];
db.col.aggregate([
{
$match: {
$expr: {
$and: [
{ $ne: [{ $indexOfArray: [ bills, "$billid" ] }, -1] },
{ $eq: ["$name", { $arrayElemAt: [ names, { $indexOfArray: [ bills, "$billid" ] } ] }] },
]
}
}
}
])
alternatively $let can be used to avoid duplication:
db.col.aggregate([
{
$match: {
$expr: {
$let: {
vars: { index: { $indexOfArray: [ bills, "$billid" ] } },
in: { $and: [ { $ne: [ "$$index", -1 ] }, { $eq: [ "$name", { $arrayElemAt: [ names, "$$index" ] }] }]}
}
}
}
}
])

Resources