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

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: '...' },
},
],
},
},
});

Related

Join two collection in mongodb

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

How to search for/select by included entity but include all related entities into result set

In my application, I am using sequelize ORM. There are several entities: A Tool can have Tags and Categories.
Now I want to search for all Tools, that have a specific Tag, but I want to include all relating Tags of that tool (not just the specific one). If I now place a where statement into the include, only specified Tags are included into the result set (see [2]). I tried to limit the Tags in the outer where statement (see [1]), but this does not help either.
Example
Tool A has Tags t1, t2 and t3. Now I want to search all Tools that have the Tag t3, but the result set shall contain all three tags.
Expected result:
Tool A
\
- Tag t1
- Tag t2
- Tag t3
db.Tool.scope('published').findAll({
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
// [2] Would limit the result specified tag
// where: {
// name: {
// [Op.and]: filter.tag
// }
// }
},
{
model: db.Category,
attributes: ['id', 'name', 'views'],
through: { attributes: ['relevance'] },
where: {
id: {
[Op.and]: filter.category
}
}
}
],
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I know I could perform this by performing a select via the Tag in the first place (db.Tag.findAll() instead of db.Tool.findAll(); I've already done this elsewhere in my project), but at the same time I also want to be able to filter by another entity (Category) the same way. So the Tool.findAll() should be the starting point.
Any help appreciated!
First off, you have two where clauses in your top-level query:
where: { '$tool.tag.name$': filter.tag }, // [1] Does not work
// ...
where: {
title: {
[Op.like]: `%${filter.term}%`,
}
},
I think your best approach is going to be with a literal subquery in the WHERE clause. Basically we want to find the ids of all of the tools that have the right tag and that contain the filter.term.
The subquery part for the WHERE looks something like...
SELECT ToolId FROM ToolTags WHERE TagId='t2';
Inspired by the subquery solution from this post Sequelize - subquery in where clause
// assuming your join table is named 'ToolTags' in the database--we need the real table name not the model name
const tempSQL = sequelize.dialect.QueryGenerator.selectQuery('ToolTags',{
attributes: ['ToolId'],
where: {
TagId: filter.tag
}})
.slice(0,-1); // to remove the ';' from the end of the SQL
db.Tool.scope('published').findAll({
where: {
title: {
[Op.like]: `%${filter.term}%`,
},
id: {
[Op.In]: sequelize.literal(`(${tempSQL})`)
}
},
include: [
{
model: db.User,
attributes: ['id', 'username']
},
{
model: db.Tag,
attributes: ['name'],
through: { attributes: [] },
},
// {
// model: db.Category,
// attributes: ['id', 'name', 'views'],
// through: { attributes: ['relevance'] },
// where: {
// id: {
// [Op.and]: filter.category
// }
// }
// }
],
attributes: ['id', 'title', 'description', 'slug', 'docLink', 'vendor', 'vendorLink', 'views', 'status', 'createdAt'],
order: [['title', 'ASC'], [db.Tag, 'name', 'ASC']]
})
I commented out your category join for now. I think you should try to isolate the solution for the tags before adding more onto the query.

Sequelize define association on Array

I have two models in Sequelize as below:
export const User = db.define('user', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
});
export const Class = db.define('class', {
id: {
type: Sequelize.UUID,
primaryKey: true,
},
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' },
})
});
How can I define an association between my Class model and the user model?
I have tried the below but it gives me an error.
Class.hasMany(User, { foreignKey: 'students' });
User.belongsTo(Class);
DatabaseError [SequelizeDatabaseError]: column "class_id" does not exist
I think you are missing the syntax
students: Sequelize.ARRAY({
type: Sequelize.UUID,
references: { model: 'users', key: 'id' }, // this has no effect
})
Should be
students: {
type: DataTypes.ARRAY({ type: DataTypes.UUID }),
references: { model: 'users', key: 'id' }
}
This won't work either, because the data type of students (ARRAY) and id (UUID) of User does not match.
Also, with these associations, you are adding two columns on User referencing id of Class but you only need one
Class.hasMany(User, { foreignKey: 'students' }); //will add students attribute to User
User.belongsTo(Class); //will add classId attribute to User
if you want to name the foreign key column passe the same name to both associations, by default Sequelize will add classId, however if you configured underscored: true on the models it will be class_id
Here is a working solution
const User = sequelize.define('user', {
id: { type: DataTypes.UUID, primaryKey: true },
});
const Class = sequelize.define('class', {
id: { type: DataTypes.UUID, primaryKey: true },
students: DataTypes.ARRAY({ type: DataTypes.UUID }),
});
Class.hasMany(User, { foreignKey: 'class_id' });
User.belongsTo(Class, { foreignKey: 'class_id' });

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.