One-to-One — qachon kerak

~15 daq · PostgreSQL · JOIN va Munosabatlar

Muammodan boshlaylik

Foydalanuvchilar tizimi yozyapsiz. Har bir foydalanuvchining profil ma'lumotlari bor: rasm, bio, tug'ilgan kun, ijtimoiy tarmoq linklari. Bularni foydalanuvchilar jadvaliga qo'shish mumkin — lekin jadval 20-30 ustunli bo'lib ketadi.

Bundan tashqari, ba'zi foydalanuvchilar profilni to'ldirmasligi mumkin — unda ko'p ustun NULL bo'lib turadi.

Yechim: profil ma'lumotlarini alohida jadvalga ko'chirish. Bir foydalanuvchi — bitta profil. Bu — One-to-One.


One-to-One nima?

One-to-One — birinchi jadvaldagi har bir yozuv ikkinchi jadvaldagi faqat bitta yozuvga bog'liq, va ikkinchi jadvaldagi har bir yozuv ham birinchi jadvaldagi faqat bitta yozuvga bog'liq bo'lgan munosabat.

Hayotiy o'xshatma: pasport va fuqaro. Har bir fuqaroning faqat bitta pasporti bor. Har bir pasport faqat bitta fuqaroga tegishli. Biri boshqasini takrorlamaydi.

foydalanuvchilar          profillar
─────────────────         ──────────────────────
id │ email                id │ foydalanuvchi_id │ bio │ rasm
───┼──────────            ───┼──────────────────┼─────┼──────
1  │ ali@mail.com         1  │ 1                │ ... │ ...
2  │ vali@mail.com        2  │ 2                │ ... │ ...
3  │ soli@mail.com
   ↑                          ↑
   bitta                      bitta

Nima uchun alohida jadval?

Hammani bitta jadvalga yozsa ham bo'lmaydimi? Ba'zan bo'ladi. Lekin ajratish quyidagi hollarda mantiqiy:

Ma'lumotlar ixtiyoriy bo'lganda. Har bir foydalanuvchi profil to'ldirmaydi. Alohida jadval bo'lsa — profilsiz foydalanuvchi uchun hech qanday NULL ustun yo'q, shunchaki profillar jadvalida yozuv yo'q.

Jadval juda katta bo'lib ketganda. Asosiy jadvalda faqat tez-tez ishlatiladigan ustunlar qoladi. Kamroq ishlatiladigan katta ma'lumotlar (bio, sozlamalar, konfiguratsiya) alohida jadvala — so'rovlar tezroq ishlaydi.

Xavfsizlik bo'lganda. Masalan, foydalanuvchilar jadvalida parol hash, bank karta ma'lumotlari saqlanmaydi — ular alohida jadvalga chiqariladi. Ilovaning turli qismlari turli jadvalga murojaat qiladi.


Qanday loyihalanadi?

One-to-One da ikkinchi jadvalga foreign key qo'yiladi — lekin u UNIQUE bo'lishi shart. UNIQUE bo'lmasa One-to-Many bo'lib ketadi.

CREATE TABLE foydalanuvchilar (
  id     SERIAL PRIMARY KEY,
  email  TEXT UNIQUE NOT NULL,
  parol  TEXT NOT NULL
);

CREATE TABLE profillar (
  id                SERIAL PRIMARY KEY,
  foydalanuvchi_id  INT UNIQUE NOT NULL REFERENCES foydalanuvchilar(id) ON DELETE CASCADE,
  -- ↑ UNIQUE — bitta foydalanuvchiga faqat bitta profil
  ism               TEXT,
  bio               TEXT,
  rasm_url          TEXT,
  tug_ilgan_kun     DATE
);

UNIQUE NOT NULL REFERENCES — bu uch kalit so'z birgalikda One-to-One ni kafolatlaydi:

  • REFERENCES — bog'lanish bor
  • UNIQUE — ikkinchi profildan himoya
  • NOT NULL — har bir profilning foydalanuvchisi bo'lishi shart

Ma'lumot qo'shish

INSERT INTO foydalanuvchilar (email, parol) VALUES
  ('ali@mail.com',  'hash1'),
  ('vali@mail.com', 'hash2');

-- Profil ixtiyoriy — Soli hali to'ldirmagan
INSERT INTO profillar (foydalanuvchi_id, ism, bio) VALUES
  (1, 'Ali Karimov',  'Dasturchi'),
  (2, 'Vali Rahimov', 'Dizayner');

-- ❌ — bir foydalanuvchiga ikkinchi profil — xato
INSERT INTO profillar (foydalanuvchi_id, ism) VALUES (1, 'Boshqa profil');
-- ERROR: duplicate key value violates unique constraint

So'rovlar

Foydalanuvchi bilan profil birga

SELECT
  f.email,
  p.ism,
  p.bio
FROM foydalanuvchilar f
LEFT JOIN profillar p ON f.id = p.foydalanuvchi_id
WHERE f.id = 1;

LEFT JOIN — profil to'ldirilmagan foydalanuvchilar ham ko'rinadi, faqat profil ustunlari NULL bo'ladi.

Hali profil to'ldirmagan foydalanuvchilar

SELECT f.email
FROM foydalanuvchilar f
LEFT JOIN profillar p ON f.id = p.foydalanuvchi_id
WHERE p.id IS NULL;

Foreign key qaysi tomonda bo'ladi?

One-to-One da foreign key ikki tomonga ham qo'yish mumkin. Qaysi tomonga qo'yish mantiqan to'g'riroq?

Qoida: ixtiyoriy tomonga qo'yiladi.

foydalanuvchi    ←──────────    profil
(har doim bor)   foydalanuvchi  (ixtiyoriy)
                 _id

Foydalanuvchi har doim mavjud, profil ixtiyoriy. Shu sababli profillar jadvaliga foreign key qo'yildi. Agar foydalanuvchilar jadvaliga profil_id qo'ysak — har bir foydalanuvchi yaratilganda darhol profil ham yaratish majburiy bo'ladi. Bu noqulay.


Munosabatlarni solishtirish

Uchala munosabatni birga ko'rish:

One-to-One
──────────
Foydalanuvchi 1 ←──► Profil 1
Foydalanuvchi 2 ←──► Profil 2

One-to-Many
───────────
Foydalanuvchi 1 ◄──┬── Buyurtma 1
                   ├── Buyurtma 2
                   └── Buyurtma 3
Foydalanuvchi 2 ◄──── Buyurtma 4

Many-to-Many
────────────
Talaba 1 ──┬──► Kurs A
           └──► Kurs B
Talaba 2 ──┬──► Kurs A
           └──► Kurs C

Loyihalashda munosabat turini aniqlash uchun ikki savolga javob bering:

  1. "Bitta A uchun nechta B bo'lishi mumkin?" → bir yoki ko'p?
  2. "Bitta B uchun nechta A bo'lishi mumkin?" → bir yoki ko'p?
A uchun B B uchun A Munosabat
Bir Bir One-to-One
Ko'p Bir One-to-Many
Ko'p Ko'p Many-to-Many

Tez-tez uchraydigan xatolar

UNIQUE ni unutish:

-- ❌ — UNIQUE yo'q — bu One-to-Many ga aylanadi
foydalanuvchi_id INT NOT NULL REFERENCES foydalanuvchilar(id)

-- ✅ — UNIQUE bor — One-to-One kafolat
foydalanuvchi_id INT UNIQUE NOT NULL REFERENCES foydalanuvchilar(id)

UNIQUE yo'q bo'lsa — bir foydalanuvchiga ikki profil qo'shish mumkin. Munosabat buziladi.

Hammani bitta jadvalga solish:

Agar jadvalda 30 dan ortiq ustun bo'lsa — bu signal. Ehtimol, ba'zi ustunlarni alohida jadvalga chiqarish vaqti kelgan. Katta jadvalda har bir SELECT * ko'p ma'lumot tortib keladi — kerak bo'lmagan ustunlar ham.


Xulosa

One-to-One — bitta yozuvga bitta yozuv. Pasport va fuqaro kabi.

Qachon kerak:

  • Asosiy jadval juda katta bo'lib ketganda — katta/ixtiyoriy ma'lumotlarni ajratish
  • Xavfsizlik — maxfiy ma'lumotlarni alohida jadvalda saqlash
  • Profil, sozlamalar, konfiguratsiya kabi ixtiyoriy tuzilmalar

Kalit qoida: Foreign key qo'yilgan jadvalga UNIQUE — o'sha UNIQUE One-to-Many dan One-to-One ni ajratib turadi.


Keyingi dars

16-dars: Subquery — WHERE, FROM, SELECT ichida, EXISTS