Prisma Many To Many multiple update - many-to-many

I have a problem with the Prisma ORM.
I have a Many To Many relation, this is the structure :
id Int #id #default(autoincrement())
last_name String #db.VarChar(255)
first_name String #db.VarChar(255)
...
report report_author[]
...
}
model report {
id Int #id #default(autoincrement())
type Int
content String #db.LongText
appreciation String? #db.VarChar(255)
meeting_id Int
meeting meeting #relation(fields: [meeting_id], references: [id], onDelete: Cascade, map: "report_ibfk_1")
users report_author[]
##index([meeting_id], map: "report_ibfk_1")
}
model report_author {
id Int #id #default(autoincrement())
report_id Int
author_id Int
user user #relation(fields: [author_id], references: [id], onDelete: Cascade, map: "report_author_ibfk_1")
report report #relation(fields: [report_id], references: [id], onDelete: Cascade, map: "report_author_ibfk_2")
##index([report_id], map: "report_author_ibfk_1")
##index([author_id], map: "report_author_ibfk_2")
}
Well, there is no problem about it ! My goal is to CONNECT an array of USERS who gonna be linked in my request. To be clear, my sended object will be like:
id: 2,
type: 2,
content: '<p>I love it !</p>',
appreciation: 'Favorable',
meeting_id: 13,
report_author: [{report_id:2, author_id:70},{report_id:2, author_id:70}]
But sometimes, in this array:
report_author: [{report_id:2, author_id:70},{report_id:2, author_id:70}] I will have 3,4 maybe 5 object.
My question is :
How can I insert (link) a variable number of user to a REPORT thanks to prisma update, upsert, connect or any query ?
Thank you !

Related

Prisma returns empty response when fetching data

I have data in the table but prisma returns empty response.
Database is hosted on Planetscale and it is MySQL.
This is schema of the table:
model BindingTeacherLessons {
bindingId Int
teacherId Int
lessons Int
binding Binding #relation(fields: [bindingId], references: [id], onDelete: Cascade)
teacher Teacher #relation(fields: [teacherId], references: [id], onDelete: Cascade)
##id([bindingId, teacherId])
##index([bindingId])
##index([teacherId])
}
This query returns {} and no errors
const response = prisma.bindingTeacherLessons.findMany({})
It seems that the problem was that I have populated BindingTeacherLessons table with createMany(). Fix was to populate it with create().

How to create one to many relationship from many to many join table in Prisma

I am creating a workout app and would like to model the relationship between users and workout programs. A user can create a program multiple times.
Here are my Prisma models:
model User {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
email String #unique
firstName String #db.VarChar(50)
lastName String #db.VarChar(50)
password String #db.VarChar(191)
programs ProgramEnrollment[]
}
model ProgramEnrollment {
program Program #relation(fields: [programId], references: [id])
programId Int // relation scalar field (used in the `#relation` attribute above)
user User #relation(fields: [userId], references: [id])
userId Int // relation scalar field (used in the `#relation` attribute
assignedAt DateTime #default(now())
##id([programId, userId])
}
model Program {
id Int #id #default(autoincrement())
name String
users ProgramEnrollment[]
}
The above works nicely, but now what I am trying to do is let the user record their personal program results, so I add the following:
model ProgramEnrollment {
program Program #relation(fields: [programId], references: [id])
programId Int // relation scalar field (used in the `#relation` attribute above)
user User #relation(fields: [userId], references: [id])
userId Int // relation scalar field (used in the `#relation` attribute
assignedAt DateTime #default(now())
userProgram UserProgram[]
##id([programId, userId])
}
model UserProgram {
id Int #id #default(autoincrement())
name String
userProgramEnrollment ProgramEnrollment #relation(fields: [programEnrollmentId], references: [id])
programEnrollmentId Int // relation scalar field (used in the `#relation` attribute above)
}
When I make the above changes I get the following error: Error validating: The argument references must refer only to existing fields in the related model ProgramEnrollment. The following fields do not exist in the related model: id
Why will it not let me create a one to many relationship from a many to many join table?
As docs states composite ID (##id) cannot be defined on a relation field.
You can probably use ##unique to define a compound unique constraint instead, like that: #unique([programId, userId]), and then just use regular autogenerated id for ProgramEnrollment and then you will be able to use it in a relation for UserProgram
I just need to adjust the UserProgram model a bit to account for multi-field id in the ProgramEnrollment model.
model ProgramEnrollment {
program Program #relation(fields: [programId], references: [id])
programId Int // relation scalar field (used in the `#relation` attribute above)
user User #relation(fields: [userId], references: [id])
userId Int // relation scalar field (used in the `#relation` attribute
assignedAt DateTime #default(now())
userProgram UserProgram[]
##id([programId, userId])
}
model UserProgram {
id Int #id #default(autoincrement())
name String
userProgramEnrollment ProgramEnrollment #relation(fields: [programEnrollment_programId, programEnrollment_userId], references: [programId, userId])
programEnrollment_programId Int
programEnrollment_userId Int
}
Since ProgramEnrollment uses two fields for its id, we have to reference both of them in the UserProgram model.

Workouts App Schema for MySQL using Prisma

I am creating a workout app using MySQL and Prisma, and I am struggling to design a schema for the data.
The app will have users and workout programs. For example a workout program 'Get Jacked', could consist of 3 blocks (each block is 1 month). Each block will contain 5 workouts per week, each workout will contain multiple exercises and a warm up. Some important things to note: each User should be able to record their personal sets and reps for each exercise within a workout. They should also be able to complete a program ('Get Jacked'), as many times as they like and each time they should be able to record new values for their reps and sets.
Here's my models so far:
model User {
id Int #id #default(autoincrement())
email String #unique
name String?
role Role #default(USER)
workouts Workout[]
}
model Program {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
name String
published Boolean #default(false)
author User #relation(fields: [authorId], references: [id])
authorId Int
}
model Block {
id Int #id #default(autoincrement())
name String
program Program #relation(fields: [programId], references: [id])
programId Int
}
model Workout {
id Int #id #default(autoincrement())
name String
week String
day String
block Block #relation(fields: [blockId], references: [id])
blockId Int
}
model WorkoutSet {
id Int #id #default(autoincrement())
name String
sets Int
reps Int
workout Workout #relation(fields: [workoutId], references: [id])
workoutId Int
exercise Exercise #relation(fields: [exerciseId], references: [id])
exerciseId Int
}
model Exercise {
id Int #id #default(autoincrement())
name String
}
model LogWorkout {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
workout Workout #relation(fields: [workoutId], references: [id])
workoutId Int
}
model LogWorkoutSet {
id Int #id #default(autoincrement())
createdAt DateTime #default(now())
sets Int
reps Int
weight Int
logWorkout LogWorkout #relation(fields: [logWorkoutId], references: [id])
logWorkoutId Int
workoutSet User #relation(fields: [workoutSetId], references: [id])
workoutSetId Int
}
I am relatively new to relational databases and what I can't seem to get my head around is how the recording of the reps ties back to the user and how the user can complete the workout program multiple times.
Any help would be much appreciated.
Thanks,
Adam
Your'e getting close, just a couple of recommendations:
Use VSCode with the Prisma extension, it will help you format the relations easily and find errors faster
Declare both sides of the relationships
Try to abstract your DB model into easy-to-understand concepts, that way maintenance will become easier later on
This might be what you want:
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #id #default(autoincrement())
email String #unique
name String?
role Role #default(USER)
programs Program[]
programRecords ProgramRecord[]
exercises ExerciseRecord[]
}
// Programs available
model Program {
id Int #id #default(autoincrement())
name String
published Boolean #default(false)
authorId Int
author User #relation(fields: [authorId], references: [id])
blocks Block[]
records ProgramRecord[]
createdAt DateTime #default(now())
}
// Blocks within a program
model Block {
id Int #id #default(autoincrement())
name String
programId Int
program Program #relation(fields: [programId], references: [id])
workouts Workout[]
}
// Workouts within a block
model Workout {
id Int #id #default(autoincrement())
name String
week String
day String
blockId Int
block Block #relation(fields: [blockId], references: [id])
exercises ExerciseOnWorkout[]
}
// Exercises to be done in workout (Relation table)
model ExerciseOnWorkout {
id Int #id #default(autoincrement())
workoutId Int
workout Workout #relation(fields: [workoutId], references: [id])
exerciseId Int
exercise Exercise #relation(fields: [exerciseId], references: [id])
name String
sets Int
reps Int
weight Int
records ExerciseRecord[]
createdAt DateTime #default(now())
// Restrict to do not repeat combinations with same name
##unique([workoutId, exerciseId, name])
}
// Exercise options
model Exercise {
id Int #id #default(autoincrement())
name String
workouts ExerciseOnWorkout[]
}
// New "enrollment" record of a user in a program
model ProgramRecord {
id Int #id #default(autoincrement())
programId Int
program Program #relation(fields: [programId], references: [id])
userId Int
user User #relation(fields: [userId], references: [id])
exerciseRecords ExerciseRecord[]
// TODO: You could track the status to prevent users starting a new one
isComplete Boolean #default(false)
createdAt DateTime #default(now())
}
// User personal record for a workout exercise
model ExerciseRecord {
id Int #id #default(autoincrement())
userId Int
user User #relation(fields: [userId], references: [id])
exerciseId Int
exercise ExerciseOnWorkout #relation(fields: [exerciseId], references: [id])
programRecordId Int
programRecord ProgramRecord #relation(fields: [programRecordId], references: [id])
name String
sets Int
reps Int
weight Int
createdAt DateTime #default(now())
##unique([userId, exerciseId, programRecordId, name])
}
enum Role {
USER
}

LEFT JOINS and aggregation in a single Prisma query

I have a database with multiple tables that frequently need to be queried with LEFT JOIN so that results contain aggregated data from other tables. Snippet from my Prisma schema:
model posts {
id Int #id #unique #default(autoincrement())
user_id Int
movie_id Int #unique
title String #db.Text
description String? #db.Text
tags Json?
created_at DateTime #default(now()) #db.DateTime(0)
image String? #default("https://picsum.photos/400/600/?blur=10") #db.VarChar(256)
year Int
submitted_by String #db.Text
tmdb_rating Decimal? #default(0.0) #db.Decimal(3, 1)
tmdb_rating_count Int? #default(0)
}
model ratings {
id Int #unique #default(autoincrement()) #db.UnsignedInt
entry_id Int #db.UnsignedInt
user_id Int #db.UnsignedInt
rating Int #default(0) #db.UnsignedTinyInt
created_at DateTime #default(now()) #db.DateTime(0)
updated_at DateTime? #db.DateTime(0)
##id([entry_id, user_id])
}
If I wanted to return the average rating when querying posts, I could use a query like:
SELECT
p.*, ROUND(AVG(rt.rating), 1) AS user_rating
FROM
posts AS p
LEFT JOIN
ratings AS rt ON rt.entry_id = p.id
GROUP BY p.id;
I'm not exactly sure how/whether I can achieve something similar with Prisma, because as it stands right now, it seems like this would require two separate queries, which isn't optimal because there is sometimes the need for 2 or 3 joins or SELECTs from other tables.
How can I make a query/model/something in Prisma to achieve the above?
Yes, this is possible with Prisma!. For making this work, you need to specify on your "schema.prisma" file how are models related with each other. That way, code generation will set the possible queries/operations.
Change it to this:
model Post {
id Int #id #unique #default(autoincrement()) #map("id")
userId Int #map("user_id")
movieId Int #unique #map("movie_id")
title String #map("title") #db.Text
description String? #map("description") #db.Text
tags Json? #map("tags")
createdAt DateTime #default(now()) #map("created_at") #db.DateTime(0)
image String? #default("https://picsum.photos/400/600/?blur=10") #map("image") #db.VarChar(256)
year Int #map("year")
submittedBy String #map("submitted_by") #db.Text
tmdbRating Decimal? #default(0.0) #map("tmdb_rating") #db.Decimal(3, 1)
tmdbRatingCount Int? #default(0) #map("tmdb_rating_count")
ratings Rating[]
##map("posts")
}
model Rating {
id Int #unique #default(autoincrement()) #map("id") #db.UnsignedInt
userId Int #map("user_id") #db.UnsignedInt
rating Int #default(0) #map("rating") #db.UnsignedTinyInt
entryId Int
entry Post #relation(fields: [entryId], references: [id])
createdAt DateTime #default(now()) #map("created_a") #db.DateTime(0)
updatedAt DateTime? #map("updated_a") #db.DateTime(0)
##id([entryId, userId])
##map("ratings")
}
Note: Please follow the naming conventions (singular form, PascalCase). I made those changes for you at the schema above. ##map allows you to set the name you use on your db tables.
Then, after generating the client, you will get access to the relational operations.
// All posts with ratings data
const postsWithRatings = await prisma.post.findMany({
include: {
// Here you can keep including data from other models
ratings: true
},
// you can also "select" specific properties
});
// Calculate on your API
const ratedPosts = postsWithRatings.map( post => {
const ratingsCount = post.ratings.length;
const ratingsTotal = post.ratings.reduce((acc, b) => acc + b.rating, 0)
return {
...post,
userRating: ratingsTotal / ratingsCount
}
})
// OR...
// Get avg from db
const averages = await prisma.rating.groupBy({
by: ["entryId"],
_avg: {
rating: true
},
orderBy: {
entryId: "desc"
}
})
// Get just posts
const posts = await prisma.post.findMany({
orderBy: {
id: "desc"
}
});
// then match the ratings with posts
const mappedRatings = posts.map( (post, idx) => {
return {
...post,
userRating: averages[idx]._avg.rating
}
})
You could also create a class with a method for making this easier.
But I strongly recommend you to implement GraphQL on your API. That way, you can add a virtual field inside your post type. Any time a post is requested alone or in a list, the average will be calculated. In that same way, you would have the flexibility to request data from other models and the "JOINS" will get handled for you automatically.
Last but not least, if you ever want to do a lot of queries at the same time, you can take advantage of the Prisma transactions.

How to create many to many relationship in prisma2

I am using Prisma2+GraphQL and I would like to write schema.prisma
this is my code below
model Message {
id Int #id #default(autoincrement())
text String
from User
to User
room Room
createdAt DateTime #default(now())
User User #relation("from", fields:[from], references:[id])
User User #relation("to", fields:[to], references:[id])
}
and I got an error like Field "User" is already defined on model "Message".
My Question is How can I relate from & to columns to User in prisma2?
Here is the right way to do the relation between User and Messages.
model User {
id Int #id #default(autoincrement())
name String
fromMessages Message[] #relation("fromUser")
toMessages Message[] #relation("toUser")
}
model Message {
id Int #id #default(autoincrement())
text String
fromId Int
toId Int
from User #relation("fromUser", fields: [fromId], references: [id])
to User #relation("toUser", fields: [toId], references: [id])
createdAt DateTime #default(now())
}