Join two collection in mongodb - mongodb-query

I'm new in mongodb. Could you please tell me how to perform join operation in this. I've two collection:
Collection 1 ("user")
{
_id: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
age: 12
}
Collection 2 ("square")
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
side: 4,
area: 16
}
Now I want to retrieve the data from collection 2 is like this.
Expected output:
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
side: 4,
area: 16
}
Thanks in advance :)

Here's one way to do it.
db.square.aggregate([
{
"$lookup": {
"from": "user",
"localField": "userId",
"foreignField": "_id",
"as": "userDoc"
}
},
{
"$set": {
"userName": {
"$first": "$userDoc.userName"
}
}
},
{ "$unset": "userDoc" }
])
Try it on mongoplayground.net.

You can keep the first documentid (_id) in the second document as userId for refrence and after that, you can use the join feature supported by MongoDB 3.2 and later versions. You can use joins by using an aggregate query.
You can do it using the below example :
db.user.aggregate([
// Join with square table
{
$lookup:{
from: "square", // other table name
localField: "_id", // name of user table field
foreignField: "userId", // name of square table field
as: "square" // alias for userinfo table
}
},
{ $unwind:"$user_info" }, // $unwind used for getting data in object or for one record only
// define some conditions here
{
$match:{
$and:[{"userName" : "XYZ User"}]
}
},
// define which fields are you want to fetch
{
$project:{
_id: 1,
userId: "$square.userId",
userName: 1,
side: "$square.side",
area: "$square.area"
}
}
]);
The Result will be
{
_id: "ef6f6ac2-a08a-4f68-a63c-0b4a70285427",
userId: "d04d53dc-fb88-433e-a1c5-dd41a68d7655",
userName: "XYZ User",
side: 4,
area: 16
}
Cheers

Related

How to create or update many-to-many relation in Prisma?

I have the following models, and many-to-many relation between them:
model User {
id String #id #default(cuid())
name String?
email String? #unique
followings Artist[]
}
model Artist {
id String #id #default(cuid())
name String #unique
spotifyId String #unique
followers User[]
}
When a user logs into my app, I retrieve their current followed artists, and need to update my database.
I have managed to select artists data from database (for updating user <-> artist relation), sample data:
const followings = [
{
id: '...',
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A'
},
{
id: '...',
name: 'Dua Lipa',
spotifyId: '6M2wZ9GZgrQXHCFfjv46we'
},
]
Now, this is my user object:
const user = {
id: 'someId',
name: 'someName',
email: 'someEmail'
}
I tried to insert or update user <-> artist relation with this query but I'm getting Bad Request error:
await prisma.user.upsert({
where: {
email: user.email
},
create: {
name: user.name,
email: user.email,
followings: {
connectOrCreate: followings
}
},
update: {
followings: {
connectOrCreate: followings
}
}
})
Please advise what I need to do. Thanks in advance.
P.S. I took the idea of the query from Updating a many-to-many relationship in Prisma post, but it didn't work for me, so please don't mark duplicate.
connectOrCreate should specify where key with id (so Prisma could find this entity) and create key with all required model fields (so Prisma could create it if it not already present), but you just passing an array of models. Change your code to this one:
await prisma.user.upsert({
where: {
email: 'user.email',
},
create: {
name: 'user.name',
email: 'user.email',
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
update: {
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
});

Counting $lookup and $unwind documents filtered with $match without getting rid of parent document when all results match

I have a collection "Owners" and I want to return a list of "Owner" matching a filter (any filter), plus the count of "Pet" from the "Pets" collection for that owner, except I don't want the dead pets. (made up example)
I need the returned documents to look exactly like an "Owner" document with the addition of the "petCount" field because I'm using Java Pojos with the Mongo Java driver.
I'm using AWS DocumentDB that does not support $lookup with filters yet. If it did I would use this and I'd be done:
db.Owners.aggregate( [
{ $match: {_id: UUID("b13e733d-2686-4266-a686-d3dae6501887")} },
{ $lookup: { from: 'Pets', as: 'pets', 'let': { ownerId: '$_id' }, pipeline: [ { $match: { $expr: { $ne: ['$state', 'DEAD'] } } } ] } },
{ $addFields: { petCount: { $size: '$pets' } } },
{ $project: { pets: 0 } }
]).pretty()
But since it doesn't this is what I got so far:
db.Owners.aggregate( [
{ $match: {_id: { $in: [ UUID("cbb921f6-50f8-4b0c-833f-934998e5fbff") ] } } },
{ $lookup: { from: 'Pets', localField: '_id', foreignField: 'ownerId', as: 'pets' } },
{ $unwind: { path: '$pets', preserveNullAndEmptyArrays: true } },
{ $match: { 'pets.state': { $ne: 'DEAD' } } },
{ "$group": {
"_id": "$_id",
"doc": { "$first": "$$ROOT" },
"pets": { "$push": "$pets" }
}
},
{ $addFields: { "doc.petCount": { $size: '$pets' } } },
{ $replaceRoot: { "newRoot": "$doc" } },
{ $project: { pets: 0 } }
]).pretty()
This works perfectly, except if an Owner only has "DEAD" pets, then the owner doesn't get returned because all the "document copies" got filtered out by the $match. I'd need the parent document to be returned with petCount = 0 when ALL of them are "DEAD". I cannot figure out how to do this.
Any ideas?
These are the supported operations for DocDB 4.0 https://docs.amazonaws.cn/en_us/documentdb/latest/developerguide/mongo-apis.html
EDIT: update to use $filter as $reduce not supported by aws document DB
You can use $filter to keep only not DEAD pets in the lookup array, then count the size of the remaining array.
Here is the Mongo playground for your reference.
$reduce version
You can use $reduce in your aggregation pipeline to to a conditional sum for the state.
Here is Mongo playground for your reference.
As of January 2022, Amazon DocumentDB added support for $reduce, the solution posted above should work for you.
Reference.

Search Algorithm Implementation using NodeJS + MongoDB(or SQL)

There is an application with search input that gives an opportunity to search for contacts by their information stored in database.
For example, I can type 0972133122 Alan and my search engine must return all contacts whose firstname is Alan & whose numbers match 0972133122 string.
Of course, I can just type Alan 0972, for instance, and there must be returned all possible contacts matching this pattern. The query order may be different, so that I can type 0972 Alan Smith, and if there are 2 contacts with Alan names and whose phone numbers start with 0972, then additional Smith clarification should return the only 1 contact.
I suggest built in phone applications for Android make use of this search algorithm:
So that my goal is to achieve similar result, but I do know how to do this. Here my code:
GraphQL query
query contacts {
contacts(input: {
contactQuery: "Alan Smith"
}) {
name {
firstName
lastName
}
}
}
NodeJS query to MongoDB
const conditions = {};
const expr = contactQuery
.split(' ')
.map((contact) => new RegExp(`${contact}`, 'i'))
conditions.$or = [
{ 'firstName': { $in: expr } },
{ 'lastName': { $in: expr } },
{ 'university': { $in: expr } },
{ emails: { $elemMatch: { email: { $in: expr } } } },
{ phones: { $elemMatch: { phone: { $in: expr } } } },
{ socials: { $elemMatch: { id: { $in: expr } } } },
]
const contacts = await this.contacts
.find(conditions, undefined)
.exec()
This works partly, but I receive unwanted documents from MongoDB:
{
contacts: [
{
firstName: "Alan",
lastName: "Smith",
university: "KNTU",
...
},
{
firstName: "Alan",
lastName: "Alderson", // should not be returned
university: "ZNU",
...
},
...
]
}
But I need to get one contact that has strictly Alan firstname and Smith lastname. If it's impossible to do with MongoDB, -- please, provide me an example of SQL query. Any suggestions & solutions will be accepted!
Please, let me know if my question still is not clear.
Firstly, you need to separate out the numbers and words from the search text and then you can create a possible combination of it for an example:
FirstName: Alan, LastName: Smith
FirstName: Smith, LastName: Alan
Using regex you can do this easily and then you can use logical operators of mongodb to create your query like this
Approach 1
db.collection.find({
$or: [
{
$and: [
{
firstName: {
$regex: "Alan",
$options: "i"
}
},
{
lastName: {
$regex: "Smith",
$options: "i"
}
}
]
},
{
$and: [
{
firstName: {
$regex: "Smith",
$options: "i"
}
},
{
lastName: {
$regex: "Alan",
$options: "i"
}
}
]
}
]
})
Here is the link to the playground for you to look at it in action Mongo Playground
Approach 2
Another way is where you concat all the searchable keys into one field and then use regex to filter it out like this
db.collection.aggregate([
{
$addFields: {
text: {
$concat: [
"$firstName",
" ",
"$lastName",
" ",
"$university",
" ",
"$phones"
]
}
}
},
{
$match: {
text: {
$regex: "(?=.*?(0972))(?=.*?(Alan))(?=.*?(Smith))",
$options: "i"
}
}
},
{
$project: {
text: 0
}
}
])
Code to build the query:
let text = "0972 Alan Smith";
let parts = text.split(" ");
let query = parts.map(part => "(?=.*?("+part+"))").join("");
console.log(query);
But you need to check the performance implication of this approach or you can create a view and then query to view to make your query more cleaner
Here is the link to the playground for you to look at it in action Mongo Playground

Select from multiple tables in sequelize

I'm struggling in how to select from two tables using the Sequelize.
Actually I'm trying to do it:
SELECT * FROM users, clients WHERE user.id = clients.user_id
I have no idea how to user two tables as I described, the only thing I did that got some results were:
const clients = await Client.findAll({
attributes: ["user_id"],
});
const users = [];
for (const client of clients) {
let user = await User.findAll({
where: {
id: {
[Op.eq]: client.user_id
}
}
});
users.push(user);
}
Which return me something:
[
[
{
"id": 1,
"first_name": "Velda",
"middle_name": "Zboncak",
"last_name": "Kris",
"email": "vkris10#hotmail.com",
"created_at": "2020-02-07T20:09:29.484Z",
"updated_at": "2020-02-07T20:09:29.484Z"
}
]
];
Model and Assossiation
First of all, you need to create the correct associations in the model of your table. In this case for the User and the Client, it's supposed to be an Client.belongsTo(...)
Take a look at User model:
const { Model, DataTypes } = require("sequelize");
class User extends Model {
static init(sequelize) {
super.init({
first_name: DataTypes.STRING,
middle_name: DataTypes.STRING,
last_name: DataTypes.STRING,
email: DataTypes.STRING
}, { sequelize });
}
}
module.exports = User;
Take a look at Client model:
const { Model, DataTypes } = require("sequelize");
class Client extends Model {
static init(sequelize) {
super.init({
user_id: DataTypes.INTEGER // The foreign key
}, { sequelize });
}
static associate(models) {
Client.belongsTo(models.User, {
foreignKey: "id", // Column name of associated table
as: "user" // Alias for the table
});
}
}
module.exports = Client;
When associating tables you need to have in mind those values inside the associate method, being the foreignKey: "id" the column name inside the models.ModelName, which will be used to make the joins, and the as: "user" which are used as an alias for the table like SELECT t.column1 FROM table AS t;
Controller
Okay, now you have set your models, you need to set your controller, where the magic happens. As you said you want to fetch results using:
SELECT * FROM users, clients WHERE user.id = clients.user_id
But to achieve the same result you can follow the sql join method to fetch the results from db, so it will be something like this:
SELECT
"user"."first_name", "user"."middle_name", "user"."last_name", "user"."email"
FROM "clients" AS "client"
LEFT JOIN "users" AS "user"
ON "client"."id" = "user"."id";
Knowing that we can talk about including tables in sequelize, which is the same as associations
const Client = require("./path/to/models/Client");
module.exports = {
async fetchAll(req, res) {
const results = await Client.findAll({
limit: 25,
include: [
{
association: "user",
attributes: ["first_name", "middle_name", "last_name", "email"]
}
]
});
return res.json(results);
},
};
Now lets talk about what is going on in the code:
The Model.findAll({}) will fetch all the result inside the specified table, in this case clients table.
The limit: 25 will limit your results in only 25 rows, you are free to remove or edit as you need.
The include: [], it will do the joins through the tables you specify, as you need only the users table, we are going to use only one object, so the assossiation: "user" will make this connection between tables, you must use the same alias you set inside the model. And at least the attributes: ["columns"] is where you set all the fields you want to fetch.
And that's it, you make you request, and the result of this will be exactly the same join as I mentioned. And the results will be:
[
{
"id": 1,
"user_id": 1,
"user": {
"first_name": "John",
"middle_name": "Ironsight",
"last_name": "Doe",
"email": "johndoe#example.com"
}
}, {...}
]
Can use where in include. Find the document at here
let user_id = client.user_id;
users = await User.findAll({
include: [
{
model: Client,
as: 'client',
where: {
user_id: user_id
}
}
]
});

MongoDB Query solution

What is the optimize Query for this situation
So the Situation is a User is following many XY user and these XY have got events, So what will the best and optimize query to get all the events from his followers XY in sorted form (sort by Date). I have got create Date in my schema
This is my User Schema
var userSchema = new Schema({
followers:[{
type: Schema.Types.ObjectId,
ref: 'XY'
}]
});
var User = mongoose.model('User',userSchema);
module.exports = User;
Here is My XY schema
var XY= new Schema({
events:[{
type: Schema.Types.ObjectId,
ref: 'Event'
}],
});
var XY= mongoose.model('XY',XY);
module.exports = XY;
Try this.
User.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(<userId here>) } },
{ $unwind: { path: "$followers" } },
{ $lookup: { from: 'XY', localField: 'followers', foreignField: '_id', as: 'followers' } },
{ $unwind: { path: "$followers" } },
{ $unwind: { path: "$followers.events" } },
{ $lookup: { from: 'Event', localField: 'followers.events', foreignField: '_id', as: 'followers.events' } },
{ $unwind: { path: "$followers.events" } },
{ $sort: { "followers.events.createdDate": **-1** } }, // -1 -> desc, 1 -> asc
{
"$project":
{
"_id": "$followers.events._id",
"createdDate": "$followers.events.createdDate",
// populate other event details here accordingly
}
}
], function (err, events, next) {
});
$lookup lets you populate a sub-document from a different schema.
After populating, the resultant documents will be an array, so $unwind is used before working on them.
note that $unwind is also used before doing a $lookup here as the field we are trying to populate is itself an array.