Database Relations — One-to-Many, Many-to-Many (Sequelize)
Muammo
Siz foydalanuvchilar va ularning postlarini saqlayapsiz. Ikkita alohida jadval bor: users va posts. Lekin savol tug'iladi: "Ali ning barcha postlarini qanday olaman?" yoki "Bu post kimniki?"
Agar relationlarni to'g'ri sozlamasangiz, har safar qo'lda WHERE userId = ... yozishga majbur bo'lasiz. Sequelize relationlari bu muammoni hal qiladi — bir marta sozlab, keyin user.getPosts() deb chaqirasiz.
Relation turlari
Ma'lumotlar bazasida jadvallar orasidagi bog'liqlikning uch asosiy turi bor:
One-to-Many (Bir-ko'p): Bir foydalanuvchining ko'p postlari bo'lishi mumkin. Lekin har bir post faqat bitta foydalanuvchiga tegishli. Masalan: User → Posts, Category → Products.
Many-to-Many (Ko'p-ko'p): Bir talaba ko'p kurslarga yozilishi mumkin, va bir kursda ko'p talaba bo'lishi mumkin. Masalan: Students ↔ Courses, Posts ↔ Tags.
One-to-One (Bir-bir): Har bir foydalanuvchining bitta profili bor. Masalan: User ↔ Profile.
Hayotiy o'xshatma
Tasavvur qiling: kutubxona. Bir muallif ko'p kitob yozishi mumkin — bu One-to-Many. Bir kitob ko'p janrga tegishli bo'lishi mumkin, va bir janrda ko'p kitob bo'lishi mumkin — bu Many-to-Many. Kutubxona bu bog'liqliklarni alohida ro'yxatlarda saqlaydi.
Ma'lumotlar bazasida ham xuddi shunday: bog'liqlik foreign key (tashqi kalit) orqali saqlanadi.
Nima uchun shunday?
Nima uchun foreign key ishlatiladi?
Agar posts jadvalida userId maydoni bo'lmasa, qaysi post kimniki ekanini bilish imkonsiz. foreign key — bu bir jadvaldan ikkinchisiga "ko'rsatkich". U ma'lumotlar yaxlitligini ta'minlaydi: mavjud bo'lmagan foydalanuvchiga post qo'shib bo'lmaydi.
Nima uchun Many-to-Many uchun oraliq jadval kerak?
students va courses jadvallarini to'g'ridan-to'g'ri bog'lab bo'lmaydi — chunki bir yacheykaga bir nechta qiymat yozib bo'lmaydi. Shuning uchun student_courses oraliq jadvali yaratiladi: u faqat studentId va courseId ni saqlaydi.
One-to-Many: User → Posts
Modellarni yaratish
// user.model.ts
import { Column, HasMany, Model, Table } from 'sequelize-typescript';
import { Post } from './post.model';
@Table({ tableName: 'users' })
export class User extends Model {
@Column
name: string;
@Column
email: string;
@HasMany(() => Post) // ← "Mening ko'p postlarim bor"
posts: Post[];
}
// post.model.ts
import { BelongsTo, Column, ForeignKey, Model, Table } from 'sequelize-typescript';
import { User } from './user.model';
@Table({ tableName: 'posts' })
export class Post extends Model {
@Column
title: string;
@Column
content: string;
@ForeignKey(() => User) // ← Bu maydon User ga bog'liq
@Column
userId: number;
@BelongsTo(() => User) // ← "Men bitta userga tegishliман"
user: User;
}
@HasManyva@BelongsTojuft bo'lishi shart. Biri "ega", ikkinchisi "tegishli" deydi. Ikkisini ham yozmasangiz, relation to'liq ishlamaydi.
Modulga qo'shish
// posts.module.ts
@Module({
imports: [
SequelizeModule.forFeature([Post, User]), // ← Ikkala modelni ham import qiling
],
})
export class PostsModule {}
CRUD va relation bilan ishlash
// posts.service.ts
@Injectable()
export class PostsService {
constructor(
@InjectModel(Post) private postModel: typeof Post,
@InjectModel(User) private userModel: typeof User,
) {}
// Post yaratish
async create(userId: number, title: string, content: string) {
return this.postModel.create({ userId, title, content }); // ← userId avtomatik bog'laydi
}
// Foydalanuvchining barcha postlarini olish
async findByUser(userId: number) {
return this.postModel.findAll({
where: { userId },
});
}
// Post + uning muallifi (JOIN)
async findOneWithUser(id: number) {
return this.postModel.findOne({
where: { id },
include: [User], // ← JOIN: post bilan birga user ma'lumotini ham oladi
});
}
// Foydalanuvchi + uning barcha postlari
async findUserWithPosts(userId: number) {
return this.userModel.findOne({
where: { id: userId },
include: [Post], // ← User bilan birga postlarni ham oladi
});
}
}
include— bu SQL dagiJOIN.include: [User]deb yozsangiz, Sequelize avtomatikLEFT JOIN users ON posts.userId = users.idso'rovini bajaradi.
Many-to-Many: Students ↔ Courses
Modellar
// student.model.ts
import { BelongsToMany, Column, Model, Table } from 'sequelize-typescript';
import { Course } from './course.model';
import { StudentCourse } from './student-course.model';
@Table({ tableName: 'students' })
export class Student extends Model {
@Column
name: string;
@BelongsToMany(() => Course, () => StudentCourse) // ← "Men ko'p kurslarga tegishliман"
courses: Course[];
}
// course.model.ts
import { BelongsToMany, Column, Model, Table } from 'sequelize-typescript';
import { Student } from './student.model';
import { StudentCourse } from './student-course.model';
@Table({ tableName: 'courses' })
export class Course extends Model {
@Column
title: string;
@BelongsToMany(() => Student, () => StudentCourse) // ← "Menda ko'p talabalar bor"
students: Student[];
}
// student-course.model.ts — oraliq jadval
import { Column, ForeignKey, Model, Table } from 'sequelize-typescript';
import { Student } from './student.model';
import { Course } from './course.model';
@Table({ tableName: 'student_courses' })
export class StudentCourse extends Model {
@ForeignKey(() => Student)
@Column
studentId: number;
@ForeignKey(() => Course)
@Column
courseId: number;
}
Many-to-Many bilan ishlash
// students.service.ts
@Injectable()
export class StudentsService {
constructor(
@InjectModel(Student) private studentModel: typeof Student,
@InjectModel(Course) private courseModel: typeof Course,
) {}
// Talabani kursga yozish
async enroll(studentId: number, courseId: number) {
const student = await this.studentModel.findByPk(studentId);
const course = await this.courseModel.findByPk(courseId);
await student.$add('courses', course); // ← oraliq jadvalga yozuv qo'shadi
}
// Talabani kursdan chiqarish
async unenroll(studentId: number, courseId: number) {
const student = await this.studentModel.findByPk(studentId);
await student.$remove('courses', courseId); // ← oraliq jadvaldan o'chiradi
}
// Talabaning barcha kurslari
async getStudentCourses(studentId: number) {
return this.studentModel.findOne({
where: { id: studentId },
include: [Course], // ← JOIN orqali kurslarni ham oladi
});
}
// Kursning barcha talabalari
async getCourseStudents(courseId: number) {
return this.courseModel.findOne({
where: { id: courseId },
include: [Student],
});
}
}
$add,$remove,$set— Sequelize ning Many-to-Many uchun maxsus metodlari. Ular oraliq jadval bilan avtomatik ishlaydi.
One-to-One: User ↔ Profile
// user.model.ts
@HasOne(() => Profile) // ← "Mening bitta profilim bor"
profile: Profile;
// profile.model.ts
@ForeignKey(() => User)
@Column
userId: number;
@BelongsTo(() => User)
user: User;
@HasOne va @HasMany ning farqi faqat bitta: @HasOne faqat bitta bog'liq yozuvga ruxsat beradi.
Tez-tez uchraydigan xatolar
1. include ishlamaydi — bo'sh array qaytadi
Sabab: Model SequelizeModule.forFeature([...]) ga qo'shilmagan. Har bir modul o'zi ishlatadigan barcha modellarni import qilishi shart.
// ✅ To'g'ri
SequelizeModule.forFeature([Post, User]) // ← User ni ham qo'shing
2. @HasMany yozildi, lekin @BelongsTo yozilmadi
Sabab: Relation bir tomonlama bo'lib qoladi. user.getPosts() ishlaydi, lekin post.getUser() ishlamaydi. Har doim juft yozing.
3. Many-to-Many da $add xato beradi
Sabab: findByPk null qaytargan — ya'ni bunday ID li yozuv yo'q. $add ni chaqirishdan oldin tekshiring:
if (!student || !course) throw new NotFoundException();
await student.$add('courses', course);
4. Circular import xatosi
Sabab: User modeli Post ni import qiladi, Post modeli User ni import qiladi — bu circular dependency. Yechim: () => Post kabi lazy reference ishlatiladi (Sequelize dekoratorlarida bu allaqachon shunday).
5. include da chuqur nested relation
// ✅ Nested include — post → user → user ning profili
include: [{
model: User,
include: [Profile],
}]
Xulosa
- One-to-Many:
@HasMany+@BelongsTo+@ForeignKey— eng ko'p ishlatiladigan relation - Many-to-Many:
@BelongsToMany+ oraliq jadval —$add,$removemetodlari bilan boshqariladi - One-to-One:
@HasOne+@BelongsTo— profil, sozlamalar kabi yagona bog'liqliklar uchun include— SQL JOIN ning Sequelize ko'rinishi, relation ma'lumotlarini birga olish uchun- Har doim ikkala tomonni ham yozing:
@HasManybo'lsa,@BelongsToham bo'lsin
Keyingi dars: 15-dars: Database Migrations (Sequelize)