Updating a many-to-many relationship in Prisma - prisma

I'm trying to figure out the right way to implement an upsert/update of the following schema:
model Post {
author String #Id
lastUpdated DateTime #default(now())
categories Category[]
}
model Category {
id Int #id
posts Post[]
}
Here is what I'd like to do. Get a post with category ids attached to it and insert it into the schema above.
The following command appears to insert the post
const post = await prisma.post.upsert({
where:{
author: 'TK'
},
update:{
lastUpdated: new Date()
},
create: {
author: 'TK'
}
})
My challenge is how do I also upsert the Category. I'll be getting a list of Catogories in the like 1,2,3 and if they do not exist I need to insert it into the category table and add the post to it. If the category does exist, I need to update the record with the post I inserted above preserving all attached posts.
Would appreciate it if I could be pointed in the right direction.

For the model, it can be simplified as follows as Prisma supports #updatedAt which will automatically update the column:
model Post {
author String #id
lastUpdated DateTime #updatedAt
categories Category[]
}
model Category {
id Int #id
posts Post[]
}
As for the query, it would look like this:
const categories = [
{ create: { id: 1 }, where: { id: 1 } },
{ create: { id: 2 }, where: { id: 2 } },
]
await db.post.upsert({
where: { author: 'author' },
create: {
author: 'author',
categories: {
connectOrCreate: categories,
},
},
update: {
categories: { connectOrCreate: categories },
},
})
connectOrCreate will create if not present and add the categories to the posts as well.

Related

Get created relation on upsert in Prisma

I'm just getting started with Prisma, and have the following schema:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Client {
id Int #id #default(autoincrement())
email String #unique
first String?
last String?
tickets Ticket[]
}
model Ticket {
id String #id #default(uuid())
purchasedAt DateTime #default(now())
pricePaid Int
currency String #db.VarChar(3)
productId String
client Client #relation(fields: [clientId], references: [id])
clientId Int
}
When a client buys a new ticket, I want to create a new ticket entry, and the associated client entry, if the client doesn't exist. Much to my surprise, the following Just Worked:
const ticketOrder = {
// details
};
const client = await prisma.client.upsert({
where: {
email: email,
}, update: {
tickets: {
create: [ ticketOrder ]
}
}, create: {
first: first,
last: last,
email: email,
tickets: {
create: [ ticketOrder ]
}
}
});
However, what gets returned is just the client entry, and what I need is the newly created ticket entry (actually, just the id of the newly created ticket entry). Is there any way to get that in one go, or do I have to do some sort of query after the upsert executes?
You can use select or include as you would in a find operation to include referenced objects, e.g.
const client = await prisma.client.upsert({
where: {
email: email,
},
update: {
tickets: {
create: [ ticketOrder ]
}
},
create: {
first: first,
last: last,
email: email,
tickets: {
create: [ ticketOrder ]
}
},
include: {
tickets: true
}
});
This would return all tickets.
To me, it seems strange to upsert the client, if you primarily want to create at ticket. You could instead create a ticket and create or connect the client:
const ticket = await prisma.ticket.create({
data: {
// ...
client: {
connectOrCreate: // ...
}
},
})
See: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#connect-or-create-a-record

Count or Include filtered relations prisma

I am currently stuck on a problem with my prisma queries.
I have an asset which has a 1 to Many relationship to views. I am trying to perform a findMany() on assets which returns either;
The asset with a list of views created within the last day
Or the asset with a count of views created in the last day
Finally I need to be able to orderBy this count or the count of views in my include statement. (this is what I am stuck on)
return await prisma.asset.findMany({
take: parseInt(pageSize),
skip: (pageSize * pageNumber),
include: {
_count: {
select: {
views: true
},
},
views: {
where: {
createdAt: dateFilter
},
},
likes: {
where: {
createdAt: dateFilter
}
},
transactions: true,
},
orderBy: { views: { _count: 'desc' } }
My queries does correctly return only views in my date range but how do I go about ordering the assets based on the count of these views. I have been stuck for quite some time on this. My raw SQL is not strong enough to write it from scratch at the moment.
If anyone has any ideas, thanks.
Will something like this work?
// First we group the views, with pagination
const groupedViews = await prisma.view.groupBy({
take: 10,
skip: 0,
by: ['postId'],
where: { createdAt: dateFilter },
_count: { postId: true },
orderBy: { _count: { postId: 'desc' } },
});
// Fetch the posts from the grouped views
const _posts = await prisma.post.findMany({
where: {
id: { in: groupedViews.map(({ postId }) => postId) },
},
include: {
_count: { select: { views: true } },
views: { where: { createdAt: dateFilter } },
},
});
// Map the fetched posts back for correct ordering
const posts = groupedViews.map(({ postId }) =>
_posts.find(({ id }) => id === postId)
);
Model:
model Post {
id String #id #default(cuid())
views View[]
}
model View {
id String #id #default(cuid())
createdAt DateTime #default(now())
postId String
post Post #relation(fields: [postId], references: [id])
}
This uses 2 separate queries, but does not require raw sql

Unable to filter an array inside of a related model with Prisma 2

I'm trying to check if the provided value exists inside of an array. I've been trying to figure this one out and from what I gathered, I have to use has. The array I'm trying to filter is inside of a related model. I tried looking for a solution, but couldn't find much on this subject. Am I doing something wrong? Is it at all possible to filter an array inside of a related model?
Here's my schema. Job and Company models are related, and inside Company we have a parking array.
model Company {
id Int #id #default(autoincrement())
name String #db.VarChar(200)
state String #db.VarChar(30)
parking String[]
...
createdAt DateTime #default(now())
updated_at DateTime #updatedAt
##map(name: "company")
}
model Job {
id Int #id #default(autoincrement())
type String
company Company #relation(fields: [company_id], references: [id])
company_id Int
createdAt DateTime #default(now())
updated_at DateTime #updatedAt
UserJobs UserJobs[]
##map(name: "job")
}
Below, I'm trying to find many jobs which match various values. One of the values I'm trying to match is inside of an array in the related Company model. Here's what I tried:
const jobs = await prisma.job.findMany({
where: {
AND: [
{
type: {
contains: req.body.type,
}
},
{
company: {
state: {
contains: req.body.state
}
}
},
...
{
company: {
parking: {
has: req.body.parkingState
}
}
}
]
},
include: {
company: true,
}
})
If you want to match a single value in a list has should be used, but if you want to match multiple values in a list then you would need to use hasEvery or hasSome depending upon your use case.
Here is the query which matches a single value in a list
const jobs = await prisma.job.findMany({
where: {
AND: [
{
type: {
contains: 'Software Engineer',
},
},
{
company: {
state: {
contains: 'CA',
},
},
},
{
company: {
parking: {
has: 'Employee',
},
},
},
],
},
include: {
company: true,
},
});
console.log(JSON.stringify(jobs, null, 2));
}
Here is the response for the above query:
[
{
"id": 1,
"type": "Software Engineer",
"company_id": 1,
"createdAt": "2022-02-28T08:53:03.949Z",
"updated_at": "2022-02-28T08:53:03.950Z",
"company": {
"id": 1,
"name": "Apple",
"state": "CA",
"parking": [
"Admin",
"Manager",
"Employee"
],
"createdAt": "2022-02-28T08:50:50.030Z",
"updated_at": "2022-02-28T08:50:50.031Z"
}
}
]
This is the sample data with which the above query fetched the results.
Job Table:
Company Table:
If you want to match multiple values in parking array you could achieve it by replacing has with hasSome in this manner.
const jobs = await prisma.job.findMany({
where: {
AND: [
{
type: {
contains: 'Software Engineer',
},
},
{
company: {
state: {
contains: 'CA',
},
},
},
{
company: {
parking: {
hasSome: ['Employee', 'Manager'],
},
},
},
],
},
include: {
company: true,
},
});
console.log(JSON.stringify(jobs, null, 2));
}

How to filter on relation in Prisma ORM

I am working currently on a course service. Users have the possibility to register and deregister for courses. The entire system is built in a microservice architecture, which means that users are managed by another service. Therefore, the data model of the course service looks like this:
model course {
id Int #id #default(autoincrement())
orderNumber Int #unique
courseNumber String #unique #db.VarChar(255)
courseName String #db.VarChar(255)
courseOfficer String #db.VarChar(255)
degree String #db.VarChar(255)
ectCount Int
faculty String #db.VarChar(255)
isWinter Boolean #default(false)
isSummer Boolean #default(false)
courseDescription String? #db.VarChar(255)
enrollmentCourse enrollmentCourse[]
}
model enrollmentCourse {
id Int #id #default(autoincrement())
userId String #db.VarChar(1024)
course course #relation(fields: [courseId], references: [id])
courseId Int
}
I want to find all the courses in which a certain user has enrolled.
I have written 2 queries. One goes over the courses and tries to filter on the enrollmentCourse. However, this one does not work and I get all the courses back. Whereas the second one goes over the enrollmentCourse and then uses a mapping to return the courses. This works, but I don't like this solution and would prefer the 1st query if it worked:
(I have used this guide in order to write the first query: here)
const result1 = await this.prisma.course.findMany({
where: { enrollmentCourse: { every: { userId: user.id } } },
include: { enrollmentCourse: true }
});
console.log('Test result 1: ');
console.log(result1);
const result2 = await this.prisma.enrollmentCourse.findMany({
where: { userId: user.id },
include: { course: { include: { enrollmentCourse: true } } }
});
console.log('Test result 2: ');
console.log(result2.map((enrollment) => enrollment.course));
If now the user is not enrolled in a course the result of both queries are:
Test result 1:
[
{
id: 2,
orderNumber: 1,
courseNumber: 'test123',
courseName: 'testcourse',
courseOfficer: 'testcontact',
degree: 'Bachelor',
ectCount: 5,
faculty: 'testfaculty',
isWinter: true,
isSummer: false,
courseDescription: 'test.pdf',
enrollmentCourse: []
}
]
Test result 2:
[]
If now the user has enrolled courses it looks like this:
Test result 1:
[
{
id: 2,
orderNumber: 1,
courseNumber: 'test123',
courseName: 'testcourse',
courseOfficer: 'testcontact',
degree: 'Bachelor',
ectCount: 5,
faculty: 'testfaculty',
isWinter: true,
isSummer: false,
courseDescription: 'test.pdf',
enrollmentCourse: [ [Object] ]
}
]
Test result 2:
[
{
id: 2,
orderNumber: 1,
courseNumber: 'test123',
courseName: 'testcourse',
courseOfficer: 'testcontact',
degree: 'Bachelor',
ectCount: 5,
faculty: 'testfaculty',
isWinter: true,
isSummer: false,
courseDescription: 'test.pdf',
enrollmentCourse: [ [Object] ]
}
]
As we can see the first query does not work correctly. Can anybody give me a hint? Is there anything that I'm missing?
As per the doc you mentioned, you need to use some instead of every as you need at least one user returned if it matches.
const result1 = await this.prisma.course.findMany({
where: { enrollmentCourse: { some: { userId: user.id } } },
include: { enrollmentCourse: true }
});
This should give all the courses where the user is registered

findUnique query returns null for array fields

I read the Prisma Relations documentation and it fixed my findMany query which is able to return valid data but I'm getting inconsistent results with findUnique.
Schema
model User {
id Int #id #default(autoincrement())
fname String
lname String
email String
password String
vehicles Vehicle[]
}
model Vehicle {
id Int #id #default(autoincrement())
vin String #unique
model String
make String
drivers User[]
}
Typedefs
const typeDefs = gql'
type User {
id: ID!
fname: String
lname: String
email: String
password: String
vehicles: [Vehicle]
}
type Vehicle {
id: ID!
vin: String
model: String
make: String
drivers: [User]
}
type Mutation {
post(id: ID!, fname: String!, lname: String!): User
}
type Query {
users: [User]
user(id: ID!): User
vehicles: [Vehicle]
vehicle(vin: String): Vehicle
}
'
This one works
users: async (_, __, context) => {
return context.prisma.user.findMany({
include: { vehicles: true}
})
},
However, for some reason the findUnique version will not resolve the array field for "vehicles"
This one doesn't work
user: async (_, args, context) => {
const id = +args.id
return context.prisma.user.findUnique({ where: {id} },
include: { vehicles: true}
)
},
This is what it returns
{
"data": {
"user": {
"id": "1",
"fname": "Jess",
"lname": "Potato",
"vehicles": null
}
}
}
I was reading about fragments and trying to find documentation on graphql resolvers but I haven't found anything relevant that can solve this issue.
Any insight would be appreciated! Thanks!
You need to fix the arguments passed to findUnique. Notice the arrangement of the { and }.
Change
return context.prisma.user.findUnique({ where: { id } },
// ^
include: { vehicles: true}
)
to
return context.prisma.user.findUnique({
where: { id },
include: { vehicles: true }
})