table references a table which in turn references another table - mysql

I'm using gorm, and I have this structure where the User table contains a foreign key referencing Address, which then references Country.
type User struct {
ID int `gorm:"column:id; primaryKey; autoIncrement" json:"id"`
Address Address
AddressID int `gorm:"column:address_id;foreignKey:AddressID;references:ID" json:"address"`
}
type Address struct {
ID int `gorm:"column:ID;primaryKey;autoIncrement;" json:"id"`
CountryCode int `gorm:"column:country_code; foreignKey:CountryCode; references:Code" json:"country_code"`
Country Country
}
type Country struct {
Code int `gorm:"column:code; primaryKey; autoIncrement" json:"code"`
Name string `gorm:"column:name" json:"name"`
ContinentName string `gorm:"column:continent_name" json:"continentName"`
}
relationship explained in the photo:
Now when I return a user using:
db.Model(&user).Where().First() // or Find()
I get the Address, and Country Empty, like this:
{
ID: 1,
AddressID: 2,
Address: {
// empty address.
}
}
I did create function repopulating the Address and Country records for me, similar to this:
func PopulateUser(user) User {
addr = FindAddresByID(user.ID)
cntr = FindCountryByCode(addr.Code)
addr.Country = cntr
user.Address = addr
return user
}
but my questions:
is there a function in Gorm which can do that for me without me creating the function?
can Associations help in this case?
if I want the address to be deleted when the user deleted, how I can do this in Gorm?
I tried to find answers on my own, but the documentation is a bit messy.

The documentation shows the foreign key tags to go at the struct reference.
i.e in your case those should be at Address and Country instead of AddressID and CountryCode. Something like:
type User struct {
Address Address `gorm:"foreignKey:AddressID;references:ID"`
AddressID int `gorm:"column:address_id"`
}
type Address struct {
CountryCode int `gorm:"column:country_code"`
Country Country gorm:"foreignKey:CountryCode; references:Code"`
}
Please try with those.
For
Please see eager loading here
db.Preload("User").Preload("Address").Find(&users)
You can use cascade tag on the column.
type User struct {
gorm.Model
Name string
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

Related

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.

Prisma Many To Many multiple update

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 !

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())
}

Grails - [1:N] Relationship issue

I have 2 different domain classes, one for Employee and one for Departments. The relationship between them is [1:N],which means that many employees can work at one Department but not vice versa. The issue is that, after Grails creates the tables from the domain classes when the project runs, for one employee, the department Id of that table references the id on Department Table. For example, for a user named "Peter", department id would be 1.
The department table also has names for the departments, along with the department id's.
How can I reference the department_id in employee table to point at department.name instead of department.id ?
Department Domain Class :
class Department {
String name
static hasMany = [
employees: Employee
]
static constraints = {
}
static mapping = {
version false
}
def String toString() {
name
}
}
Employee Domain Class :
class Employee {
String firstName
String lastName
String email
String country
int born
static belongsTo = [department: Department]
static constraints = {
firstName(blank: false)
lastName(blank: false)
born(blank: false)
email(blank: false, email: true)
country(blank: false)
}
static mapping = {
version false
}
}
What I need is, when in Employee table, the department_id column to reference at department.name instead of department.id.
I guess you need to configure that the Primary Key of the Department table is 'name' instead of the default 'id' column. Grails will then use the name as the Foreign Key.
i.e.:
class Department {
...
static mapping = {
id name: 'name'
}
...
}
ref (Grails 3.1) : http://docs.grails.org/3.1.x/ref/Database%20Mapping/id.html