Cube Js | How to join two tables created from MongoDB arrays? - mongodb

I am new to cube.js and I am facing a problem, maybe someone will be able to help me. I did not find anything very helpful on the internet...
Here is an example of o document in my collection:
{
"_id" : ObjectId("5a835e0000f73b69c100f15c"),
"studyDescription" : "xxxxxxxx",
"observations" : [
{
"_id" : "1JELIZY6QSAGW",
"state" : "validated",
"stateBy" : "user#xxx.com",
"stateAt" : ISODate("2019-10-22T15:06:48.133+0000"),
"created" : ISODate("2019-10-22T15:06:48.133+0000"),
"createdBy" : "user#xxx.com",
"history" : [
{
"author" : "user#xxx.com",
"role" : "ADMIN",
"state" : "validated",
"tsp" : ISODate("2019-10-22T15:06:48.133+0000")
}
]
}
]
}
My collection contains studies and each study contains several observations. Each observation can be reviewed by one or several reviewers and this information is contained in "history" array. I need to do some reporting so I was advised to use cube.js. Problem is I need to filter some of my charts with data contained in arrays and to do so, i need to do some joins. My problem is that the "observations" array does not contain the study id and the "history" array does not contain either study id nor observations id whereas I would need both to join the tables and filter according to the author for example. I can't join them except if I modify the collection in the database to add the information, which is unfortunately not an option in my case...
Would you have an idea to make this join possible ?
Thank you very much for your help

Embedded documents represented as related tables by Mongo BI connector. In your case there will be following tables:
studies
studies_observations
studies_observations_history
In this case Cube.js schema will look as follows:
cube(`Studies`, {
sql: `select * from studies`,
joins: {
Observations: {
sql: `${Studies}._id = ${Observations}._id`,
relationship: `hasMany`
}
},
measures: {
count: {
type: `count`
}
},
dimensions: {
id: {
sql: `_id`,
type: `string`,
primaryKey: true
}
}
});
cube(`Observations`, {
sql: `select * from studies_observations`,
joins: {
History: {
sql: `${Observations}._id = ${History}._id AND ${Observations}.observations_idx = ${History}.observations_idx`,
relationship: `hasMany`
}
},
dimensions: {
id: {
sql: `CONCAT(${CUBE}._id, ${CUBE}.observations_idx)`,
type: `string`,
primaryKey: true
}
}
});
cube(`History`, {
sql: `select * from studies_observations_history`,
dimensions: {
id: {
sql: `CONCAT(${CUBE}._id, ${CUBE}.observations_idx, ${CUBE}.\`observations.history_idx\`)`,
type: `string`,
primaryKey: true
},
author: {
sql: `${CUBE}.\`observations.history.author\``
}
}
})
Learn more about Mongo BI arrays schema and Cube.js joins.

Related

MongoDB Multiple documents update with Positional Array index

Having a collections as below:
[{
"_id" : ObjectId("5ee111e541050ba2728adb3f"),
"User" : "user1",
"applications" : ["test1", "test2","test3"]
},
{
"_id" : ObjectId("5ee111e541050ba2728adb40"),
"User" : "user2",
"applications" : ["test1", "test3","test2"]
}]
Expected Output:
[{
"_id" : ObjectId("5ee111e541050ba2728adb3f"),
"User" : "user1",
"applications" : ["test1", "test2Updated","test3"]
},
{
"_id" : ObjectId("5ee111e541050ba2728adb40"),
"User" : "user2",
"applications" : ["test1", "test3","test2Updated"]
}]
Used Below Query:
db.getCollection('testUser').update({"applications":"test2"},
{$set:{"applications.$[elem]":"Test2Updated"}},
{ "arrayFilters": [{ "elem": "test2" }], "multi": true })
Not working in MongoDB 3.4
Any Solutions to make it work on MongoDB 3.4 could be helpful
As arrayFilters was only introduced after 3.6 you need to do it in two update operations, We can use .bulkWrite() to achieve this in one DB call.
Query :
db.collection.bulkWrite([
/** First push `test2Updated` element to applications array where `test2` exists */
{
updateMany: {
filter: { applications: "test2" },
update: { $push: { applications: "test2Updated" } }
}
},
/** Remove `test2` from applications */
{
updateMany: {
filter: { applications: "test2" },
update: { $pull: { applications: "test2" } }
}
}
]);
Note :
Since we can't use $set + $unset both on array field at same time we've split this into two ops.
Also if you've multiple test2 elements in same applications array use $pullAll to pull all of them out but you can't insert equivalent no.of test2Updated elements as you don't know how many test2's exist for a document in that case you need to individually read docs to code and update applications array(I guess this is not the case).
One other thing is test2Updated will not be pushed to same index at which test2 is but I guess this is not that important.

Cannot query nested document's _id (other fields work)

I want to find all documents where vendor._id has a certain value. Below is the code, I tried, but it returns nothing.
let name = sampleData.name, _id = sampleData._id
Product.find({"vendor._id":ObjectID(_id)}).then((products) => {
//returns empty array
})
With the same method I tried to query a different field and it works. But I want to query with _id because other fields could vary with time.
Product.find({"vendor.name":name}).then((products) => {
//returns all documents that satisfy the condition.
})
Below is a sample document which I want to find
{
"status" : "active",
"connectedFarms" : [
{
"_id" : "5c412c62bf8a6602f04ae0bf",
"status" : "inActive",
"margin" : 10,
"price" : 55
},
{
"_id" : "5c4567bcb3845b0536a4d92e",
"status" : "inActive",
"margin" : 20,
"price" : 60
},
{
"_id" : "5c4567c4b3845b0536a4d931",
"status" : "active",
"margin" : 7,
"price" : 53.5
}
],
"vendor" : {
"_id" : ObjectId("5c3fcc0c7657ee02ac24bc21"),
"name" : "manna"
}
}
And here is the schema for this document.
let ProductSchema = new mongoose.Schema({
vendor:{_id:String, name:String},
connectedFarms:[{_id:String, name:String, status:String, price:Number, margin:Number}],
status:{
type:String,
trim: true,
minlength:1
}
});
Let's take a different approach on this, and make vendor its own schema. Mongoose does not allow you to nest schemas, so you cannot make the vendor._id a true ObjectID.
Vendor Schema
const VendorSchema = new mongoose.Schema({
name: string
});
module.exports = mongoose.model('Vendor', VendorSchema);
Product Schema
const ProductSchema = new mongoose.Schema({
vendor: {
type: mongoose.Types.ObjectID,
ref: 'Vendor'
},
connectedFarms: [{
_id: String,
name: String,
status: String,
price: Number,
margin: Number
}],
status: {
type: String,
trim: true,
minlength: 1
}
});
module.exports = mongoose.model('Product', ProductSchema);
Now when you want to query a product based on the vendors _id, it's very simple! All you need to do is supply the _id of the vendor in the query. NOTE: There is no reason to convert the _id to an ObjectID in the query, as mongoose accepts strings and converts them later on.
Query
const vendorID = myVendor._id;
Product.find({ vendor: vendorID })
.then((products) => {
// Do something with the found products
});
That's it! Much simpler to do, and much cleaner in the database. The vendor field is now easier to reference. You also have the ability to get the full vendor object in a query if desired by populating in the query. The difference is, the population will return the vendor name and _id, rather than just the _id. To do this, run the following:
Product.find({ vendor: vendorID })
.populate('vendor')
.then((products) => {
// Do something with the populated found products
});

MongoDB - Aggregate $match on ObjectId

I have a schema that looks like this:
var mongoose = require('mongoose');
module.exports = mongoose.model('Owner',{
username: String,
blocks: {type:mongoose.Schema.Types.ObjectId, ref: 'Block'},
});
I'm trying to run a query to see if Owner has a reference to Block's id. Owner has an array of ObjectIds. When I run db.owners.aggregate({$match: {username: 'example'}},{$unwind: "$blocks"},{$project: { _id : 1,blocks: 1}}) it returns:
{ "_id" : ObjectId("550d9dc64d9dc3d026fadfc7"), "blocks" : ObjectId("550dc117dc9605ab27070af7") }
{ "_id" : ObjectId("550d9dc64d9dc3d026fadfc7"), "blocks" : ObjectId("550dc123dc9605ab27070af8") }
{ "_id" : ObjectId("550d9dc64d9dc3d026fadfc7"), "blocks" : ObjectId("550dc12edc9605ab27070af9") }
{ "_id" : ObjectId("550d9dc64d9dc3d026fadfc7"), "blocks" : ObjectId("550dc157dc9605ab27070afa") }
How can I match the block id? I've tried db.publishers.aggregate({$match: {username: 'example'}},{$unwind: "$blocks"},{$project: { _id : 1,blocks: 1}},{$match : {"blocks._id" : '550dc157dc9605ab27070afa'}}) but that doesn't work.
I think you don't need aggreation for that, you can use a simple find() or findOne() query:
var Mongoose = require('mongoose');
var ObjectId = Mongoose.Types.ObjectId;
Owner.findOne({ username: 'example', blocks: new ObjectId('550dc157dc9605ab27070afa') }, function (err, owner) {
...
});
that does not work right now with versions > 3.8.31
i've wasted countless hours on that one, should have tested the earlier mayor sooner...

how to design mongodb based product schema, some data fields could link to different lanuage

I am design a product schema in mongodb - mongoose, the schema is simply like
var schema = new mongoose.Schema({
name:String,
description:String,
img:String,
});
However, in this schema, I need to name the product in Both french and english, how could I make the this flexible to different language
I've seen a similar example that where the names are stored like this:
> db.dogs.insert( { name: { de: 'Hund',
en: 'dog',
es: 'perro',
fr: 'chien' }
} )
WriteResult({ "nInserted" : 1 })
> db.dogs.find( {}, { "name.en": 1 })
{ "_id" : ObjectId("54472399e3ce503b652f4790"), "name" : { "en" : "dog" } }

search in combination two field in Mongodb

I use from Mongodb and my database schema like this:
firstName: 'vahid',
lastName: 'kh',
age: 12
I want find all records that firstname + lastname likes 'vahid kh'. In SQL, this would be:
Select * from table where firstName + lastName like '%vahid kh%'
Seems this is available only in 2.4, which was released today.
> db.temp.save({ "firstName" : "Bob", "lastName" : "Smith", Age : 12 });
> db.temp.find();
{ "_id" : ObjectId("5148a2a00477cddcdece1b34"), "firstName" : "Bob", "lastName" : "Smith", "Age" : 12 }
> db.temp.aggregate({ $project : { "name" : { $concat : [ "$firstName", " ", "$lastName" ] } } });
{
"result" : [
{
"_id" : ObjectId("5148a2a00477cddcdece1b34"),
"name" : "Bob Smith"
}
],
"ok" : 1
}
You can use $regex, this way you can use partial matches.
Something like that:
db.collection.find( { field: /acme.*corp/i } )
Here is somewhat similar question with answer in php: MongoRegex and search multiple rows in collection
Docs about mongoregex are here: http://docs.mongodb.org/manual/reference/operator/regex/
Edit:
I just read your comment with query in sql. Simple solution could be to make field fullname and search it with $regex, it is kind of db denormalization, where you store somewhat redundant data.
Or even easier, this should do the job too:
db.collection.find( { firstName: /*vahid/i, lastName: /kh*/i } )
To search against a combination of two or more fields, you need to use the aggregation framework. It lets you sort by the combinations of fields, and you don't need to store (denormalize) any extra fields:
db.collection.aggregate([
{
$match: {
// Optional criteria to select only some documents to process, such as...
deleted: null
}
},
{
$addFields: {
// Need to prefix fields with '$'
fullName: { $concat: [ "$firstName", "$lastName" ] },
}
},
{
$search: { fullName: /.*vakid kh.*/ },
}
]);
Explanation:
the $addFields aggregation pipeline stage creates dynamic, on-the-fly fields
$concat creates the fullName field by concatenating the first and last name
$search does a regular expression search, which is the MongoDB equivalent to the SQL LIKE operator.
I have code in expressjs code aggregate is bit slow then find so I have use conditional based and use regular express to find true results, I am sharing with you nodejs code I hope it useful for you or my other code lover friends.
router.get('/publisher/:q',function(req,res){
var q = ucfirst( req.params['q'] );
var qSplit = q.split(" ");
var db = mongojs(CONNECTION_STRING, ['tags_helper','users']);
var query = { "status" : 1, isAdmin : { $exists : false } };
console.log(qSplit);
if( qSplit.length > 1 )
{
query["firstName"] = new RegExp(qSplit[0],"i");
query["lastName"] = new RegExp(qSplit[1],"i");
}
else
{
qSplit[0] = new RegExp(qSplit[0],"i");
qSplit[1] = new RegExp(qSplit[0],"i");
//query.push( { $or : [ {"firstName": new RegExp(q,"i")},{"lastName": new RegExp(q,"i")} ] } );
query["$or"] = [ {"firstName": new RegExp(q,"i")},{"lastName": new RegExp(q,"i")} ];
}
db.users.find( query,{_id : 1,firstName:1,lastName:1,image:1,profileName:1}).limit(10,function (err, users) {
//TODO your further code ..
});
});
Have a happy coding day.