Ma'lumotlar bazalari

SQL: bog'lanishlar va JOIN

Relatsion bazaning eng kuchli tomoni โ€” jadvallarni bir-biriga bog'lash qobiliyatidir. Ma'lumot bir joyda takrorlanmasligi uchun uni bir necha jadvalga bo'lamiz, so'ng JOIN orqali birlashtirib o'qiymiz. Ushbu darsda kalitlar, munosabatlar, JOIN turlari va guruhlash bilan tanishamiz.

Nima uchun ma'lumotni bo'lamiz?

Aytaylik, foydalanuvchilar va ularning buyurtmalarini bitta jadvalda saqlamoqchimiz. Har buyurtma qatorida foydalanuvchi ismini takrorlashga to'g'ri keladi:

Yomon usul (hamma narsa bitta jadvalda):

 order_id |  user_name | product   | price
 -------- + ---------- + --------- + -----
    1     |   Ali      | Kitob     | 50
    2     |   Ali      | Ruchka    | 5
    3     |   Vali     | Daftar    | 10

Bu yerda "Ali" ikki marta yozilgan. Agar Ali ismini o'zgartirsak, ikkala qatorni ham yangilashimiz kerak. Ma'lumot ko'p bo'lsa, bu chalkashlik va xatolikka olib keladi. To'g'ri yechim โ€” ma'lumotni ikki jadvalga bo'lish:

users jadvali:            orders jadvali:
 id |  name              id | user_id | product | price
 -- + -----             -- + ------- + ------- + -----
  1 |  Ali               1 |    1    | Kitob   |  50
  2 |  Vali              2 |    1    | Ruchka  |   5
                        3 |    2    | Daftar  |  10

Endi "Ali" faqat bir marta saqlanadi. orders jadvalidagi user_id ustuni qaysi foydalanuvchiga tegishli ekanligini ko'rsatadi. Bu โ€” jadvallar orasidagi bog'lanish.

PRIMARY KEY โ€” asosiy kalit

Har bir jadvalda odatda asosiy kalit (primary key) bo'ladi โ€” bu qatorni yagona aniqlaydigan ustun. Uning qiymati takrorlanmaydi va hech qachon NULL bo'lmaydi. Odatda bu id ustuni:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name VARCHAR(100) NOT NULL
);

Yuqorida id โ€” asosiy kalit. Bazadagi har bir foydalanuvchining o'ziga xos idsi bor: 1, 2, 3... Ana shu id orqali biz uni boshqa jadvallardan aniq topa olamiz.

FOREIGN KEY โ€” tashqi kalit

Tashqi kalit (foreign key) โ€” bir jadvaldagi ustun boshqa jadvalning asosiy kalitiga ishora qiladi. orders jadvalidagi user_id โ€” bu tashqi kalit, u users jadvalining idsiga bog'langan:

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INT REFERENCES users(id),
  product VARCHAR(100),
  price DECIMAL(10, 2)
);

REFERENCES users(id) qismi bazaga aytadi: "bu user_id ustuni users jadvalining idsiga ishora qiladi". Bu bog'lanish ma'lumot butunligini kafolatlaydi.

Tashqi kalit tufayli baza mavjud bo'lmagan foydalanuvchiga buyurtma qo'shishga ruxsat bermaydi. Masalan, agar usersda id = 99 yo'q bo'lsa, ordersga user_id = 99 qo'sha olmaysiz โ€” baza xatolik beradi. Bu "ma'lumot butunligi" (referential integrity) deyiladi.

Munosabatlar: 1:N

Jadvallar orasida turli munosabatlar bo'ladi. Eng keng tarqalgani โ€” bir-ko'p (one-to-many, 1:N):

JOIN โ€” jadvallarni birlashtirish

Endi eng qiziq qismi. Bizga "har buyurtma qaysi foydalanuvchiniki" degan savolga javob kerak โ€” ya'ni ikki jadvalni birlashtirib o'qishimiz kerak. Buning uchun JOIN ishlatiladi.

INNER JOIN

INNER JOIN ikki jadvaldagi mos keluvchi qatorlarni birlashtiradi. Faqat ikkala tomonda ham mosligi bor qatorlar natijaga tushadi:

SELECT users.name, orders.product, orders.price
FROM orders
INNER JOIN users ON orders.user_id = users.id;

ON orders.user_id = users.id โ€” bu bog'lanish sharti: buyurtmadagi user_id foydalanuvchining idsiga teng bo'lgan qatorlarni birlashtir. Natija:

 name  | product | price
 ----- + ------- + -----
 Ali   | Kitob   |  50
 Ali   | Ruchka  |   5
 Vali  | Daftar  |  10
Jadval nomlarini har safar yozish uzun bo'ladi. Shuning uchun taxallus (alias) beriladi: FROM orders o INNER JOIN users u ON o.user_id = u.id. Endi o va u deb qisqartirasiz.

LEFT JOIN

LEFT JOIN chap jadvaldagi (FROM'dan keyingi) barcha qatorlarni oladi, o'ng jadvalda mos kelmasa ham. Mos kelmagan joylar NULL bilan to'ldiriladi:

-- barcha foydalanuvchilar, buyurtmasi bo'lmaganlar ham
SELECT users.name, orders.product
FROM users
LEFT JOIN orders ON orders.user_id = users.id;

Agar biror foydalanuvchining hech qanday buyurtmasi bo'lmasa, u baribir natijada ko'rinadi, lekin product ustuni NULL bo'ladi. Bu "buyurtma bermagan foydalanuvchilarni topish" kabi savollar uchun juda foydali.

RIGHT JOIN

RIGHT JOIN โ€” LEFT JOIN'ning aksi: o'ng jadvaldagi barcha qatorlarni oladi. Amaliyotda kamroq ishlatiladi, chunki jadvallar o'rnini almashtirib LEFT JOIN qilish mumkin.

Farqni eslab qolish oson: INNER โ€” faqat ikkala tomonda mosligi bor qatorlar; LEFT โ€” chap jadvalning hammasi; RIGHT โ€” o'ng jadvalning hammasi. Ko'p hollarda sizga INNER yoki LEFT JOIN yetarli bo'ladi.

GROUP BY va agregat funksiyalar

Ko'pincha bizga alohida qatorlar emas, umumlashtirilgan ma'lumot kerak: "nechta buyurtma bor?", "jami summa qancha?", "o'rtacha narx?". Buning uchun agregat funksiyalar ishlatiladi:

Oddiy agregat (butun jadval bo'yicha):

-- jami nechta buyurtma bor?
SELECT COUNT(*) FROM orders;

-- barcha buyurtmalar summasi va o'rtacha narxi
SELECT SUM(price), AVG(price) FROM orders;

Endi eng muhimi. GROUP BY qatorlarni guruhlarga bo'ladi va agregatni har bir guruh uchun hisoblaydi. Masalan, "har bir foydalanuvchi nechta buyurtma bergan?":

SELECT user_id, COUNT(*) AS buyurtmalar_soni
FROM orders
GROUP BY user_id;

Natija โ€” har bir user_id uchun bitta qator, va uning yonida buyurtmalar soni. AS kalit so'zi natija ustuniga nom (taxallus) beradi.

GROUP BY'ni JOIN bilan birlashtirib, ismlar bilan chiroyliroq natija olamiz:

SELECT u.name, COUNT(o.id) AS buyurtmalar_soni, SUM(o.price) AS jami
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.name;
GROUP BY ishlatilganda SELECTdagi har bir ustun yo GROUP BYda bo'lishi, yoki agregat funksiya ichida bo'lishi kerak. Masalan, SELECT product, COUNT(*) FROM orders GROUP BY user_id xato โ€” product guruhlanmagan va agregatda ham emas.

HAVING โ€” guruhlarni filtrlash

WHERE alohida qatorlarni filtrlaydi, lekin u guruhlashdan oldin ishlaydi. Guruhlangan natijani (agregatni) filtrlash uchun HAVING ishlatiladi:

-- 2 tadan ortiq buyurtma bergan foydalanuvchilar
SELECT user_id, COUNT(*) AS soni
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 2;
Farqni yaxshi eslab qoling: WHERE โ€” guruhlashdan oldin, alohida qatorlarga qo'llanadi; HAVING โ€” guruhlashdan keyin, guruhlarga (agregat natijalarga) qo'llanadi. Ular birga ham ishlatiladi.

To'liq tartibda SQL so'rovi quyidagi ketma-ketlikda yoziladi:

SELECT ustunlar
FROM jadval
JOIN boshqa_jadval ON shart
WHERE qatorlar_sharti
GROUP BY ustun
HAVING guruh_sharti
ORDER BY ustun
LIMIT n;

Xulosa