How to select a row by many to many relation connection? - mysql

Here are my type-orm entities:
#Entity()
export class Conversation {
#PrimaryGeneratedColumn()
id: number;
#ManyToMany((type) => User, (user) => user.conversations)
attendees: User[];
}
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#ManyToMany((type) => Conversation, (conversation) => conversation.attendees)
#JoinTable()
conversations: Conversation[];
}
Having two different user IDs, I want to select a single conversation entity, which contains both of those users as attendees.
What I managed to get so far are the conversations of one user:
const conversationsUsers = await conversationRepository
.createQueryBuilder("c")
.innerJoinAndSelect("c.attendees", "user", "user.id = :authorId", { authorId })
.getMany();
Being far from becoming SQL expert, I think I should create another JOIN with users table (with an alias for e.g. user.id => attende_id, but have no idea how to approach this.
Thanks in advance for any thoughts :)

Related

Typeorm SQL one to one relation using same user ID in both tables

I wanted to create one to one relation in typeorm (NestJS). Just like in the picture below.
I wanted then to signup user with email and password and create row in custom_auth and user_profile table.
relations
I cant find solution to create that type of relation. I was only able to create typical relation using foreign key (solution in typeorm docs)
export class CustomAuth {
#PrimaryGeneratedColumn()
id: number
#Column({ unique: true })
email: string
#Column()
password: string
#OneToOne(() => UserProfile, (userProfile) => userProfile.customAuth, {
cascade: true,
})
userProfile: UserProfile
}
export class UserProfile {
#PrimaryGeneratedColumn()
userId: number
#Column()
name: string
#Column({ nullable: true })
address: string
#OneToOne(() => CustomAuth, (customAuth) => customAuth.userProfile, {})
#JoinColumn()
customAuth: CustomAuth
}

How to use onDelete: 'CASCADE' on one-to-one relationship

I try to delete user's profile when user was deleted. But it's delete nothing on profile.
User Entity
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToOne(type => Profile, profile => profile.user,
{ eager: true, cascade: true, onDelete: 'CASCADE' })
#JoinColumn()
profile: Profile;
}
Profile Entity
#Entity()
export class Profile {
#PrimaryGeneratedColumn()
id: number;
#Column()
gender: string;
#Column()
photo: string;
#OneToOne(type => User, user => user.profile)
user: User;
}
How I delete a user.
const userRepository = connection.getRepository(User);
const user = await userRepository.findOne({ where: {id: 'someuserId'}, relations: ["profile"] });
await user.remove()
User was removed but nothing happen on user's profile.
Can anyone explain how to delete relationship on one-to-one?
User
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToOne(type => Profile, profile => profile.user,
{ eager: true, onDelete: 'CASCADE' })
#JoinColumn()
profile: Profile;
}
Profile
#Entity()
export class Profile {
#PrimaryGeneratedColumn()
id: number;
#Column()
gender: string;
#Column()
photo: string;
#OneToOne(type => User, user => user.profile, { onDelete: "CASCADE" })
user: User;
}
I ran this code for removing entries from both the entities.
const userRepository = connection.getRepository(User);
const user = await userRepository.findOne({ where: {id: 'someuserId'} });
const profileRepository = connection.getRepository(Profile);
const profile = await profileRepository.findOne({ where: {id: user.profile.id} });
await profileRepository.remove(profile)
This is the expected behaviour for one-to-one relation. You have to delete referencing side to take cascade deletion to take in effect. discussion link
edit: raised issue on github
Solution
I recently found myself in the same spot. I created a inheritance relationship in my database. Multiple entities reference one common entity which provides basic attributes for each specialized table. For example: Common attribute holder table "Car" and specialized tables "Truck", "Bus". Now I wanted to delete the corresponding "Car"-entity whenever a "Truck" or "Bus" is deleted.
In typeorm the specialized tables should reference the common attribute table. To make the CASCADE DELETE work, you have to specify the side which holds the id using the #JoinColumn() annotation. Then you have to specify the onDelete: "CASCADE" on the One-to-One relationship of the same side as the #JoinColumn() annotation.
To validate the created table you can use SHOW CREATE TABLE table_name in your database. There you will see if the CASCADE DELETE is present on the correct foreign key constraint. (It should be on the table holding the foreign key).
That means, it is not required to set the CASCADE DELETE on both sides.
Code example
(see Gaurav Sharma's answer - modified)
// referenced table
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
// you can use eager on the none-owning side
// this can be very helpful for inheritance
#OneToOne(type => Profile, profile => profile.user, { eager: true })
profile: Profile;
}
// owning side - foreign key in this table
#Entity()
export class Profile {
#PrimaryGeneratedColumn()
id: number;
#Column()
gender: string;
#Column()
photo: string;
// specify onDelete here because owning side FK constraint would fail
// and it needs to know what is going to happen
#OneToOne(type => User, user => user.profile, { onDelete: "CASCADE" })
#JoinColumn()
user: User;
}
From here on you would create a new user. Set the saved user in the profile to save and save the profile.
Deleting the profile will not delete the user.
Deleting the user will delete the profile.
I also looked into CASCADE DELETE from both sides. From what I have found, you would have to specify the #JoinColumn() annotation on both sides, declaring onDelete: "CASCADE" on both sides. This will require at least two saves for new entities and I did not find further information in the docs concerning the bidirectional CASCADE DELETE. I would consider deleting manually in these situations because it might become very unclear what is going to happen and when entities need to be saved.
I solved my problem (for now)
I just manual remove in both entities.
const userRepository = connection.getRepository(User);
const user = await userRepository.findOne({ where: {id: 'someuserId'}, relations: ["profile"] });
const profileRepository = connection.getRepository(Profile);
const profile = await profileRepository.findOne({where: {id: user.profile.id}}).getOne()
await profile.remove()
await user.remove()

Add a field in a many-to-one relation in NestJS

I am trying to develop a small application to record cooking recipes. To do this, I declared 2 entities with nestJS allowing me to manage the recipes and another to manage the ingredients. I also created a 3rd entity to record the quantities of ingredients needed :
Database diagram
// recipe.entity.js
#Entity()
export class Recipe {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
title: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.recipe)
ingredients: RecipeIngredients[];
}
// ingredient.entity.js
#Entity()
export class Ingredient {
#PrimaryGeneratedColumn()
id: number
#Column('datetime')
createdAt: Date
#Column('datetime')
updatedAt: Date
#Column('varchar', { length: 100 })
name: string;
#Column('varchar', {nullable: true})
image: string;
#OneToMany(type => RecipeIngredients, recipeIngredients => recipeIngredients.ingredient)
recipes: RecipeIngredients[];
}
// recipe_ingredients.entity.js
#Entity()
export class RecipeIngredients {
#PrimaryGeneratedColumn()
id: number
#ManyToOne(type => Recipe, recipe => recipe.ingredients)
recipe: Recipe
#ManyToOne(type => Ingredient)
ingredient: Ingredient
#Column()
quantity: string;
}
First, I would like to be able to retrieve a recipe with the list of necessary ingredients:
const recipe = await this.recipesRepository.createQueryBuilder('recipe')
.where('recipe.id = :recipeId', {recipeId: _id})
.leftJoin('recipe.ingredients', 'recipe_ingredients')
.leftJoin('recipe_ingredients.ingredient', 'ingredient')
.getMany();
But this method returns only my recipe object without the ingredients...
[
{
"id": 1,
"createdAt": "2020-04-30T09:12:22.000Z",
"updatedAt": "2020-04-30T09:12:22.000Z",
"title": "Test",
"image": null
}
]
From there, I'm lost ... How can I get the list of my ingredients (at least the name and quantity fields) directly from my service?
Thank you in advance for your help.
Using leftJoin will able you to join data without its selection. It selects the recipe if it has ingredients, but won't return its ingredients.
As read in TypeORM's documentation :
You can join data without its selection. To do that, use leftJoin or
innerJoin:
const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne();
This will generate:
SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'
This will select Timber if he has photos, but won't return his photos.
To select the ingredients try using leftJoinAndSelect instead.

Typeorm how to get relations of relations

I am getting the Object ChatRoomEntity with entitymanager.findOne method. The ChatRoomEntity has the variable messages which is a OneToMany - ManyToOne Relation. I have no problems to select that but how do I get the user which sent the message. Its a variable on MessageEntity with a OneToMany Relation.
So basically I want to select a room and all messages of it. But all messages should also have their values on fromUser.
I select the room like this:
this.entityManager.findOne(ChatRoomEntity, {where: {id: roomToJoin.id}, relations: ['activeUsers', 'messages']}).then(roomEntity => {
// some code
}
Here my entities:
UserEntity
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#CreateDateColumn()
registrationDate: Date;
#ManyToMany(type => ChatRoomEntity, room => room.activeUsers, {cascade: true})
#JoinTable()
activeChatRooms: ChatRoomEntity[];
#OneToMany(type => ChatRoomMessageEntity, msg => msg.fromUser)
chatRoomMessages: ChatRoomMessageEntity[];
}
ChatRoomEntity
#Entity()
export class ChatRoomEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
title: string;
#OneToMany(type => ChatRoomMessageEntity, chatrmsg => chatrmsg.chatRoom)
messages: ChatRoomMessageEntity[];
#ManyToMany(type => UserEntity, user => user.activeChatRooms)
activeUsers: UserEntity[];
}
ChatRoomMessageEntity
#Entity()
export class ChatRoomMessageEntity {
#PrimaryGeneratedColumn()
id: number;
#Column('varchar', {nullable: true})
message: string;
#CreateDateColumn()
creationDate: Date;
#ManyToOne(type => UserEntity, user => user.chatRoomMessages)
fromUser: UserEntity;
#ManyToOne(type => ChatRoomEntity, chatRoom => chatRoom.messages)
chatRoom: ChatRoomEntity;
}
We can load sub-relations by using 'relation.subrelation' within the relations array itself like this:
relations: ['relation1', 'relation2', 'relation2.subrelation1']
So for your case, instead of using join you can simply do something like this:
this.entityManager.findOne(ChatRoomEntity, {
where: {id: roomToJoin.id},
relations: ['activeUsers', 'messages', 'messages.fromUser'],
}).then(roomEntity => {
...
This is specified here: https://github.com/typeorm/typeorm/blob/master/docs/find-options.md#basic-options
Using QueryBuilder
await getRepository(UserEntity)
.createQueryBuilder('user')
.leftJoinAndSelect('user.profile', 'profile')
.leftJoinAndSelect('profile.images', 'images')
.getMany()
Using FindOptions
await getRepository(UserEntity).find({
relations: ['profile', 'profile.images']
})
Update v0.4.0
Typeorm is ditching findOne, you should use findOneBy for the queries without relations, and if you want relations, simply use find.
const users = await userRepository.find({
where: { /* conditions */ },
relations: { /* relations */ }
})
// users[0] if you want a first row
here is the #gradii/fedaco orm how to implement this feature.
use can use multi relations by method with
export class User extends Model {
#HasOneColumn({related: Profile})
profile;
}
export class Profile extends Model {
#HasOneColumn({related: Image})
image;
}
export class Image extends Model {
name;
}
eager load
User.createQuery().with('profile.image').get()
lazy load
const user = await User.createQuery().first();
const image await (await user.profile).image

How do i write this query using TypeORM's QueryBuilder?

I ran this code directly on mySQL server and i'm getting the desired results, now how do i replicate this in my code using typeORM's queryBuilder?
SELECT user.id, user.email, user.image, user.phone_number,
user.merchantLocationId, ugg.userGroupsId AS groupId, ug.description AS
groupName, ur.id as roleId, ur.description AS role_name, ugr.priviledges
FROM ipay.user, ipay.user_user_group_user_groups AS ugg,
ipay.user_group_roles AS ugr, ipay.user_groups AS ug,
ipay.user_roles AS ur
WHERE user.id = ugg.userId AND ugg.userGroupsId = ugr.group_id AND
ugr.role_id = ur.id AND user.email = "press#xyz.com";
It's a bit difficult to translate that sort of query into typeorm. The closest example I could give would be something like this
#Injectable()
export class UserService {
constructor(#InjectRepository(User) private readonly userRepository: Repository<User>) {}
async findUserByEmail(email: string): Promise<User | null> {
return await this.userRepository({
relations: ['roles', 'groups'],
where: {
email,
},
});
}
}
Where your UserEntity looks like this
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({
unique: true,
})
email: string;
#ManyToMany(type => Group, group => group.users)
groups: Group[];
#ManyToOne(type => Role, role => role.users)
role: Role;
}
It's not an exact match but hopefully will give you some insight into how you can achieve your query.