How merge two collections in MongoDB by using Map/Reduce - mongodb

I'd like to merge these two collections, and it has "name" variable as the common field.
// product collections
db.products.insertOne( { name : "iPhone 5", Price : 600, company : "Apple"})
db.products.insertOne( { name : "Galaxy Note9", Price : 900, company : "samsung"})
db.products.insertOne( { name : "LG V40 ThinQ", Price : 800, company : "LG"})
db.products.insertOne( { name : "Watch", Price : 400, company : "Apple"})
db.products.insertOne( { name : "iPhone 7", Price : 900, company : "Apple"})
// product_review Collection
db.product_review.insertOne( { name : "Watch", comment: " Great Product" })
db.product_review.insertOne( { name : "Watch", comment: " AMAZING" })
db.product_review.insertOne( { name : "iPhone 7", comment: " Not bad" })
db.product_review.insertOne( { name : "iPhone 7", comment: " Expeinsive" })
db.product_review.insertOne( { name : "Galaxy Note9", comment: " Great Product" })
db.product_review.insertOne( { name : "Galaxy Note9", comment: " Great Product" })
// end of product_review collection.
The output that I am looking for is as the following. One Name and one or many comments without repeating the name with each comment. It should be like this.
{
name: "iPhone 7",
Price: 900,
company: "Samsung",
comments :[
{"comment": "Not bad"},
{"comment": " Expeinsive"}
]
}
My current Code does not give me the comment, It gives me the name and price, and empty comment.
I found this code and tried to adopt it, but it can be work as I want
// map for products collection
var mapproducts = function() {
var output = {
name: this.name,
Price :this.Price,
company:this.company,
comment:0
}
if (this.Price == 0) {
output.name = this.name;
}
emit(this.name, output);
};
// map for product_review collection
var map_product_review = function() {
emit(this.name, {
name: this.name,
comment:this.comment,
Price:0,
company: 0
});
};
// Reduce function.
// I believe that the "IF condition" has to be done, is specific ways
var r = function(key, values) {
var outs = { name: 1,comment: 1, Price: 0,company:0};
values.forEach(function(v){
outs.name = v.name;
if (v.comment == 0) {
outs.Price = v.Price;
outs.company = v.company;
}
});
return outs;
};
db.products.mapReduce(mapproducts, r, {out: {reduce: ‘Result’}})
db.product_review.mapReduce(map_product_review, r, {out: {reduce: 'Result'}})
db.Result.find().pretty()

Related

how to filter current array using another array

Let's say I have data as below.
[
{
hotelName : "Hotel 1",
hotelType : 1
prices :
[
{
roomType: "Single Room",
price : 1231
},
{
roomType: "Twin Room",
price : 1232
},
{
roomType: "Triple Room",
price : 1233
}
]
},
{
hotelName : "Hotel 2",
hotelType : 2
prices :
[
{
roomType: "Single Room",
price : 1241
},
{
roomType: "Twin Room",
price : 1242
},
{
roomType: "Triple Room",
price : 1243
}
]
}
]
I have another array for filter in below format.
[
{
"roomType": "Single Room"
},
{
"roomType": "Twin Room"
}
]
What I want to get is get room which have above types.
I am trying below way, but stuck at below point.
finalArray = finalArray.filter() {
hotelInfo in
hotelInfo.prices!.roomType!==(
// compare for roomType from another array
)
}
Could someone point me in right direction?
Struct I have is as below.
struct Hotels: Encodable, Decodable {
var hotelName: String?
var hotelType: Int?
var prices: [RoomPrices]?
}
struct RoomPrices: Encodable, Decodable {
var roomType: String?
var price: Double?
}
For filter, I have model as below
struct RoomFilter: Decodable {
var roomType: String?
}
Prices as 1 dictionary only
[
{
hotelName : "Hotel 1",
hotelType : 1
prices :
{
roomType: "Single Room",
price : 1231
}
},
{
hotelName : "Hotel 2",
hotelType : 2
prices :
{
roomType: "Twin Room",
price : 1242
}
}
]
Updated struct will be
struct Hotels: Encodable, Decodable {
var hotelName: String?
var hotelType: Int?
var prices: RoomPrices?
}
struct RoomPrices: Encodable, Decodable {
var roomType: String?
var price: Double?
}
You can do it like this:
let roomTypes = [RoomFilter(roomType: "Twin Room"), RoomFilter(roomType: "Single Room")]
let result = hotels.filter { hotel in
hotel.prices?.contains { price in
roomTypes.contains { rt in
rt.roomType == price.roomType
}
} ?? false
}
In case if a price is a dictionary:
let roomTypes = [RoomFilter(roomType: "Twin Room"), RoomFilter(roomType: "Single Room")]
let result = hotels.filter { hotel in
roomTypes.contains { filterRoomType in
filterRoomType.roomType == hotel.price?.roomType
}
}
You can filter an array of Hotels to only keep hotels that contain a RoomPrice whose roomType property is present in an array of RoomFilter using two nested contains(where:) calls inside your filter, one searching Hotel.prices and the other one searching roomFilters to see if there is at least a single common element between the two arrays.
let filteredHotels = hotels.filter({ hotel in
hotel.prices?.contains(where: { room in roomFilters.contains(where: {$0.roomType == room.roomType})}) ?? false
})
Some general advice: you should name your types using singular form, since a single Hotel instance represents 1 Hotel, not several ones, same for RoomPrices. It also doesn't make sense to mark all properties as optional and mutable. Declare everything as immutable and optional unless you have a good reason not to do so.

Group a list of providers by their type using pug

I'm trying to group a list of users based on their specialties. For example: I want to group all Family Medicine providers and display their names:
Family Medicine:
- List item
- List item
- List item
This is my js controller:
exports.provider_list = function(req, res, next) {
provider.find()
.sort([['SpecialtyName', 'ascending']])
.exec(function (err, list_providers) {
if (err) { return next(err); }
//Successful, so render
res.render('provider_list', { title: 'Provider List', list_providers: list_providers});
});
};
Pug list:
extends layout
block content
h1= title
ul.list-group
each val in list_providers
li
a(href=val.url) #{val.SpecialtyName}
| #{val.ProviderName}
else
li There are no provider.
As I understand , you want to list all provider names grouped by specialty names.
And I guess your data (list_providers) looks like that :
[{
"ProviderName": "P1",
"SpeacialyName": "S1",
"url" : "url_1"
}, {
"ProviderName": "P2",
"SpeacialyName": "S2"
}, {
"ProviderName": "P3",
"SpeacialyName": "S3"
}, {
"ProviderName": "P3",
"SpeacialyName": "S1"
}, {
"ProviderName": "P4",
"SpeacialyName": "S2"
}]
If your data is like above. You can modify your data convert it to like this :
[{
"SpeacialyName": "S1",
"url": "url_1",
"ProviderNames": ["P1", "P3"]
}, {
"SpeacialyName": "S2",
"ProviderNames": ["P2", "P4"]
}, {
"SpeacialyName": "S3",
"ProviderNames": ["P3"]
}
]
And here is convertion code for backend :
//Successful, so render
var providers = {}
for (var i = 0; i < list_providers.length; i++) {
var item = list_providers[i];
if (!providers[item.SpeacialyName]) {
providers[item.SpeacialyName] = item;
providers[item.SpeacialyName].ProviderNames = [item.ProviderName];
} else {
providers[item.SpeacialyName].ProviderNames.push(item.ProviderName)
}
delete providers[item.SpeacialyName].ProviderName;
}
//convert object to array
var providersArray = [];
for (const item in providers) {
providersArray.push(providers[item])
}
res.render('provider_list', { title: 'Provider List', list_providers: providersArray });
Finally, here is pug file to list
ul.list-group
each val in list_providers
li
a(href=val.url) #{val.SpeacialyName}
ul
each name in val.ProviderNames
li
a(href="")=name
else
li There are no speacialy.
else
li There are no provider.

Custom control Openui5

sap.ui.core.Element.extend("custom.barNlineChartControl", { metadata : {
properties : {
"Job" : {type : "string", group : "Misc", defaultValue : null},
"Threshold" : {type : "int", group : "Misc", defaultValue : null},
}
}});
sap.ui.core.Control.extend("control.barNlinechart", {
/* the control API */
metadata : {
aggregations : {
"items" : { type: "custom.barNlineChartControl", multiple : true, singularName : "item"}
},
events: {
"select" : {},
"selectEnd": {}
}
},
//D3 Code below:
onAfterRendering: function() {
var that = this;
/* get the Items aggregation of the control and put the data into an array */
var aItems = this.getItems();
var data = [];
for (var i=0;i<aItems.length;i++){
var oEntry = {};
for (var j in aItems[i].mProperties) {
oEntry[j]=aItems[i].mProperties[j];
}
data.push(oEntry);
}
alert(JSON.stringify(data));
Code of view & control
multiBarLineGraph = new control.barNlinechart({
layoutData: new sap.ui.layout.GridData({span: "L12 M12 S12"}),
items: {
path : "/genericData",
template : new custom.barNlineChartControl({Job:"{Job}",Threshold:"{Threshold}"}),
}
}),
var multiBarData = {
"genericData":[
{
"Job": "Doctor",
"Threshold": 45,
"Hospital1": 30,
"Hospital2": 100,
"Hospital3": 90,
},
{
"Job": "Teacher",
"Threshold": 65,
"School1": 60,
"School2": 75,
},
]};
When the alert in d3 code executes I get Job & Threshold but other data from JSON array are missing which is obvious as the properties set here only accept job and threshold. As the JSON is dynamic how to write custom control so that I can pass the complete data to control everytime no matter how dynamic the data be.
You could use type: "any" for your items and dont use the element custom.barNlineChartControl at all:
Edit: as an aggregation controls the lifetime of the aggregated objects you have to use a property in this case.
sap.ui.core.Control.extend("control.barNlinechart", {
/* the control API */
metadata : {
properties : {
"items" : { type: "any" }
},
events: {
"select" : {},
"selectEnd": {}
}
},
and then in your view:
multiBarLineGraph = new control.barNlinechart({
layoutData: new sap.ui.layout.GridData({span: "L12 M12 S12"}),
items: { path : "/genericData" }
}),
this.getItems() would return an array of whatever has been been set / bound.

Correct way to return from mongo to datatable

I'm using mongoose and returning documents from a collection to be displayed using datatables. I'm having some issues though. The client-side code is
var table = $('#dataTables-example').DataTable( {
"bProcessing" : true,
"bServerSide" : true,
"ajax" : {
"url" : "/mongo/get/datatable",
"dataSrc": ""
},
"columnDefs": [
{
"data": null,
"defaultContent": "<button id='removeProduct'>Remove</button>",
"targets": -1
}
],
"aoColumns" : [
{ "mData" : "name" },
{ "mData" : "price" },
{ "mData" : "category" },
{ "mData" : "description" },
{ "mData" : "image" },
{ "mData" : "promoted" },
{ "mData" : null}
]
});
Then this handled on the server-side using the following
db.once('open', function callback ()
{
debug('Connection has successfully opened');
productSchema = mongoose.Schema({
name: String,
price: String,
category: String,
description: String,
image: String,
promoted: Boolean
});
Product = mongoose.model('Product', productSchema, 'products');
});
exports.getDataForDataTable = function (request, response) {
Product.dataTable(request.query, function (err, data) {
debug(data);
response.send(data);
});
};
If I use the above code the datatable fails to display the documents, claiming no matching records found BUT it does correctly display the number of docs Showing 1 to 2 of 2 entries. If I change the server side code to response with data.data instead of data, the documents are correctly populated in the table BUT the number of records is no longer found, instead saying Showing 0 to 0 of 0 entries (filtered from NaN total entries)
exports.getDataForDataTable = function (request, response) {
Product.dataTable(request.query, function (err, data) {
debug(data);
response.send(data.data);
});
The actual data being returned when querying mongo is
{ draw: '1', recordsTotal: 2, recordsFiltered: 2, data: [ { _id: 5515274643e0bf403be58fd1, name: 'camera', price: '2500', category: 'electronics', description: 'lovely', image: 'some image', promoted: true }, { _id: 551541c2e710d65547c6db15, name: 'computer', price: '10000', category: 'electronics', description: 'nice', image: 'iamge', promoted: true } ] }
The third parameter in mongoose.model sets the collection name which is pluralized and lowercased automatically so it has no effect in this case.
Assuming your Product variable has been declared early on and global, try this:
products = mongoose.model('products', productSchema);
Product = require('mongoose').model('products');
Did you try to remove the dataSrc field in the DataTable configuration:
"ajax" : {
"url" : "/mongo/get/datatable",
},

mongodb update array any matching index

I have a data structure like this:
{
students: [
{ name: "john", school: 102, age: 4 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 7 }
]
}
{
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
students: [
{ name: "bob", school: 100, age: 7 },
{ name: "manuel", school: 100, age: 8 },
]
}
I want to update the documents such that any students who is 7 years old is changed to school 101. Is there any way to do this in 1 operation?
Currently mongo only supports updating a single subdocument in an array that match the specified c. This works really well with subdocuments that have a unique identifier (e.g. _id) that you are updating based on but does not work when there are multiple subdocuments that match the criteria (e.g. student ages).
To demonstrate this, I added a student "dupey" to the first set of students so the new records looks like this:
students: [
{ name: "john", school: 102, age: 4 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 7 },
{ name: "dupey", school: 102, age: 7 }
]
Here is the mongo update statement and the data result after the update completes:
> db.students.update({"students.age":7},{$set: {"students.$.school":101}},true,true);
> db.students.find({}, { _id: 0 });
{ "students" : [ { "name" : "john", "school" : 102, "age" : 4 }, { "name" : "jess", "school" : 102, "age" : 11 }, { "name" : "jeff", "school" : 101, "age" : 7 }, { "name" : "dupey", **"school" : 102**, "age" : 7 } ] }
{ "students" : [ { "name" : "ajax", "school" : 101, "age" : 7 }, { "name" : "achilles", "school" : 100, "age" : 8 } ] }
{ "students" : [ { "name" : "bob", "school" : 101, "age" : 7 }, { "name" : "manuel", "school" : 100, "age" : 8 } ] }
Notice dupey's school has not been changed. There is a mongo feature request to support this. Until then, this has to be broken up into multiple statements. Here is some code I wrote a few weeks back when I had a similar issue.
var mongoose = require('mongoose'),
async = require('async');
mongoose.connect('mongodb://127.0.0.1/products');
var PartsSchema = new mongoose.Schema({
type: String
, partNbr: Number
});
var ProductSchema = new mongoose.Schema({
sku: { type: String, unique: true }
, sku_type: String
, parts: [PartsSchema]
});
Product = mongoose.model('Product', ProductSchema);
Product.remove( function(err) {
if (err) { console.log("unable to remove: " + err); return; }
});
var cigars = new Product({
sku: 'cigar123',
sku_type: 'smoking',
parts: [{type: 'tobacco', partNbr: 4}, {type: 'rolling paper', partNbr: 8}, {type: 'tobacco', partNbr: 4}]
});
var cigarillo = new Product({
sku: 'cigarillo456',
sku_type: 'smoking',
parts: [{type: 'tobacco', partNbr: 4}, {type: 'crush paper', partNbr: 12}]
});
function updateProducts(sku_type, part_type, callback) {
function updateProduct(product_record, callback) {
function denormParts(part_records, sku_id) {
var returnArray = [];
part_records.forEach( function(item) {
var record = { sku_id: sku_id, part_type: item.type, part_id: item._id }
returnArray.push(record);
});
return returnArray;
function updateParts(part_record, callback) {
if (part_record.part_type != part_type) {
return callback(null);
}
console.log("updating based on: " + part_record.sku_id + " " + part_record.part_id);
Product.update( { _id: part_record.sku_id, 'parts._id': part_record.part_id },
{ $set: { 'parts.$.partNbr': 5 } },
function(err, numAffected) {
if (err) { console.log("err4: " + err); return callback(err); }
console.log("records updated: " + numAffected);
callback(null);
});
}
var denormedParts = denormParts(product_record.parts, product_record._id);
console.log("calling updateParts with: " + JSON.stringify(denormedParts));
async.map(denormedParts, updateParts, function(err) {
if (err) { console.log("errored out: " + err); callback(err); }
callback(null);
});
}
Product.find({ "parts.type": part_type, "sku_type": sku_type },
function(err, docs) {
if (err) { console.log("err3: " + err); return callback(err); }
console.log(docs);
async.map(docs, updateProduct, function(err) {
if (err) { console.log("err4: " + err); return callback(err); }
callback(null);
});
}); // end Product.find
}
cigars.save(function(err, product1) {
if(err) { console.log("err1: " + err); return err; }
console.log("saved: " + product1);
cigarillo.save(function(err, product2) {
if(err){ console.log("err2: " + err); return err; }
console.log("saved: " + product2);
updateProducts('smoking', 'tobacco', function(err) {
Product.find({}, function(err, docs) {
if (err) { console.log("err5: " + err); return err; }
console.log("updated data: " + docs);
Product.remove(function(err) {
if (err) { console.log("err6: " + err); return err; }
process.exit();
});
});
});
});
});
I hope this code helps you and others who run into this issue.
you can update an array by specifying its position, for example:
db.students.update({"students.age":7},{$set: {"students.$.school":101}},true,true)