IlmHamroh
JavaScript Full-stack/26-qism. SQL va PostgreSQL24/64-dars19 daqiqa
Mundarija (35)

SQL JOIN: INNER, LEFT, RIGHT, FULL, CROSS va self-join PostgreSQL misollarida

Qisqacha: JOIN (birlashtirish) — ikki jadval qatorlarini shart (ON) bo'yicha juftlab, bitta natijaga yig'ish. INNER JOIN faqat jufti borlarni qoldiradi, LEFT JOIN chap jadvalning hammasini (juftsizda o'ng tomon NULL), FULL JOIN ikkala tomonning hammasini. Eng ko'p uchraydigan tuzoq: LEFT JOIN dan keyin o'ng jadval shartini WHERE ga yozish — u LEFT ni jimgina INNER ga aylantiradi; shart ON ga yozilishi kerak.

Bu darsda

  • INNER, LEFT, RIGHT, FULL va CROSS JOIN farqini misol bilan tushuntirasiz.
  • Uch-to'rt jadvalni zanjir qilib birlashtirasiz (vazifa → teg → foydalanuvchi).
  • Jadvalni o'zi bilan birlashtirasiz (self-join): kichik vazifa va uning otasi.
  • LEFT JOIN + WHERE tuzog'ini va qatorlar ko'payib ketishini (fan-out) taniysiz.
  • 25-qism API'si uchun "vazifalar teglari bilan" so'rovini yozasiz.

Oldin bilishingiz kerak: Foreign key va referential actions, Agregatlar, GROUP BY va HAVING, UPSERT va MERGE.

1. Nega bu kerak?

UPSERT va MERGE darsida teglar va vazifa_teglari jadvallarini qo'shdik. Endi vazifalar bazasida to'rtta jadval bor. Har biri bitta narsani saqlaydi:

erDiagram
  foydalanuvchilar ||--o{ vazifalar : "egasi"
  foydalanuvchilar ||--o{ teglar : "egasi"
  vazifalar |o--o{ vazifalar : "ota_id"
  vazifalar ||--o{ vazifa_teglari : "vazifa"
  teglar ||--o{ vazifa_teglari : "teg"
  foydalanuvchilar {
    bigint id PK
    text ism
  }
  vazifalar {
    bigint id PK
    bigint egasi_id FK
    text matn
    bigint ota_id FK
  }
  teglar {
    bigint id PK
    bigint egasi_id FK
    text nom
  }
  vazifa_teglari {
    bigint vazifa_id PK
    bigint teg_id PK
  }

Diagrammadagi har chiziq — tashqi kalit (foreign key). Chiziq uchidagi "panja" belgisi — "ko'p" tomon: bitta foydalanuvchida ko'p vazifa. Bu belgilarni ER modellash darsida batafsil o'qiymiz.

Ma'lumot bo'lingan — bu yaxshi: Azizning ismi bitta joyda, 12 marta takrorlanmaydi. Lekin ekranda bizga birlashgan ko'rinish kerak: "Kranni almashtirish — Aziz Karimov — #ta'mir #uy". Uchta jadvaldan bitta qator. Buni JOIN (birlashtirish) qiladi.

O'xshatish: maktabda ikki ro'yxat bor — o'quvchilar (ism, sinf raqami) va sinflar (raqam, sinf rahbari). "Har o'quvchining sinf rahbari kim?" savoliga javob berish uchun ikkala ro'yxatni sinf raqami bo'yicha yonma-yon qo'yasiz. JOIN aynan shu: umumiy ustun bo'yicha juftlash.

2. INNER JOIN

2.1 Birinchi birlashtirish

Gulnora va Bekzodning vazifalari, egasining ismi bilan:

sql
SELECT v.id, v.matn, f.ism
FROM vazifalar v
JOIN foydalanuvchilar f ON f.id = v.egasi_id
WHERE v.egasi_id IN (6, 7) AND v.ota_id IS NULL
ORDER BY v.id;
text
 id |                    matn                     |       ism
----+---------------------------------------------+-----------------
  4 | Somsa uchun xamir tayyorlash #oshxona       | Gulnora Saidova
 12 | Buyurtmalarni Telegram'da qabul qilish #ish | Gulnora Saidova
 19 | Universitet kontrakti #to'lov               | Bekzod Nazarov
 22 | Un va yog' xarid qilish #bozor #oshxona     | Gulnora Saidova
 25 | Algoritmlar: 5 ta masala #o'qish            | Bekzod Nazarov
 30 | Instagram uchun surat olish #ish            | Gulnora Saidova
 33 | Noutbukka SSD qo'yish #ta'mir               | Bekzod Nazarov
 39 | Do'stlar bilan choyxona #dam                | Bekzod Nazarov
(8 rows)

Biz buni o'z bazamizda ishga tushirdik (PostgreSQL 17.6, vazifalar holat 23). Qatorma-qator:

Qism Ma'nosi
FROM vazifalar v asosiy jadval, qisqa nomi (taxallus) v
JOIN foydalanuvchilar f ikkinchi jadval, taxallus f
ON f.id = v.egasi_id juftlash sharti: vazifaning egasi = foydalanuvchi id si
v.matn, f.ism qaysi jadvaldan qaysi ustun

JOIN va INNER JOIN — bir xil narsa, INNER so'zi ixtiyoriy. Postgres har vazifa qatori uchun ON sharti rost bo'lgan foydalanuvchini topadi va ikkalasini bitta qatorga "yopishtiradi".

2.2 Juftsizlar tushib qoladi

Endi har foydalanuvchining vazifalari soni (GROUP BY bilan). Bu va keyingi ko'p misollar — kanon tajribasidan (PostgreSQL 18.6):

sql
SELECT f.ism, count(*) AS vazifalar
FROM foydalanuvchilar f
JOIN vazifalar v ON v.egasi_id = f.id
GROUP BY f.id ORDER BY f.id;
text
       ism        | vazifalar
------------------+-----------
 Aziz Karimov     |        12
 Dilnoza Rahimova |         6
 Sardor Tursunov  |         6
 Madina Yusupova  |         9
 Javohir Olimov   |         6
 Gulnora Saidova  |         4
 Bekzod Nazarov   |         4
(7 rows)

Bazada 8 foydalanuvchi bor, natijada 7. Shahnoza Ergasheva yo'q — unda birorta ham vazifa yo'q. INNER JOIN uchun "jufti yo'q qator" mavjud emas. Bu xato emas, INNER'ning ta'rifi: faqat ikkala tomonda jufti borlar.

GROUP BY f.id va SELECT f.ism — id birlamchi kalit (primary key) bo'lgani uchun Postgres ismni ham guruh ichida bir xil deb biladi (Agregatlar darsidagi funksional bog'liqlik).

Tekshirib ko'ring: JOIN dagi ON f.id = v.egasi_id ni ON v.egasi_id = f.id deb yozsa, natija o'zgaradimi?

Javob

O'zgarmaydi. Tenglik ikki tomonlama: a = b va b = a — bir xil shart. Jadvallar tartibi (FROM vazifalar JOIN foydalanuvchilar yoki teskari) ham INNER JOIN natijasini o'zgartirmaydi — faqat ustunlar tartibi va qaysi jadval "asosiy" ko'rinishi o'zgaradi. LEFT JOIN'da esa tartib muhim — keyingi bo'limda.

3. LEFT JOIN

3.1 Chap jadvalning hammasi

"Hamma foydalanuvchilar va ularning vazifalari soni, nol bo'lsa ham":

sql
SELECT f.ism, count(v.id) AS vazifalar
FROM foydalanuvchilar f
LEFT JOIN vazifalar v ON v.egasi_id = f.id
GROUP BY f.id ORDER BY f.id;
text
        ism         | vazifalar
--------------------+-----------
 Aziz Karimov       |        12
 Dilnoza Rahimova   |         6
 Sardor Tursunov    |         6
 Madina Yusupova    |         9
 Javohir Olimov     |         6
 Gulnora Saidova    |         4
 Bekzod Nazarov     |         4
 Shahnoza Ergasheva |         0
(8 rows)

LEFT JOIN (chap birlashtirish) — chap (FROM dagi) jadvalning har qatori natijada qoladi. Jufti bo'lmasa, o'ng jadval ustunlari NULL bilan to'ldiriladi. Shahnoza qaytdi: uning qatorida v.id, v.matn — hammasi NULL.

flowchart LR
  F["foydalanuvchilar<br/>8 qator"] --> I["INNER JOIN<br/>7 kishi"]
  F --> L["LEFT JOIN<br/>8 kishi"]
  L --> N["Shahnoza:<br/>v.* = NULL"]

Diagrammada ko'ring: bir xil jadval, bir xil shart — INNER'da 7 kishi, LEFT'da 8. Farq — juftsiz qator taqdiri.

3.2 count(*) va count(v.id) farqi

Nega count(v.id), count(*) emas? Tekshiramiz (biz ishga tushirdik, PostgreSQL 17.6):

sql
SELECT f.ism, count(*) AS yulduzcha, count(v.id) AS v_id
FROM foydalanuvchilar f
LEFT JOIN vazifalar v ON v.egasi_id = f.id
WHERE f.id IN (7, 8)
GROUP BY f.id ORDER BY f.id;
text
        ism         | yulduzcha | v_id
--------------------+-----------+------
 Bekzod Nazarov     |         4 |    4
 Shahnoza Ergasheva |         1 |    0
(2 rows)

count(*) qatorlarni sanaydi — Shahnozaning bitta qatori bor (o'ng tomoni NULL bo'lsa ham). count(v.id) esa faqat NULL bo'lmagan qiymatlarni sanaydi (NULL mantiqi). LEFT JOIN'dan keyin sanashda doim o'ng jadvalning NOT NULL ustunini sanang.

3.3 RIGHT JOIN

RIGHT JOIN — LEFT'ning ko'zgudagi aksi: o'ng jadvalning hammasi qoladi. A RIGHT JOIN B = B LEFT JOIN A. Amalda deyarli hamma faqat LEFT yozadi — o'qish oson. RIGHT misolini quyida FULL bilan birga ko'ramiz.

4. LEFT JOIN + WHERE tuzog'i

4.1 Shahnoza yana yo'qoldi

Vazifa: "har foydalanuvchida nechta bajarilmagan vazifa bor, nol bo'lsa ham". Birinchi urinish — shartni WHERE ga:

sql
SELECT f.ism, count(v.id) AS bajarilmagan
FROM foydalanuvchilar f
LEFT JOIN vazifalar v ON v.egasi_id = f.id
WHERE NOT v.bajarildi
GROUP BY f.id ORDER BY f.id;
text
       ism        | bajarilmagan
------------------+--------------
 Aziz Karimov     |            7
 Dilnoza Rahimova |            3
 Sardor Tursunov  |            3
 Madina Yusupova  |            5
 Javohir Olimov   |            3
 Gulnora Saidova  |            2
 Bekzod Nazarov   |            2
(7 rows)

7 qator — Shahnoza yo'q, garchi biz LEFT JOIN yozgan bo'lsak ham. Nima bo'ldi? Tartib bo'yicha kuzatamiz:

Qadam Shahnozaning qatori
1. LEFT JOIN qoldi: v.bajarildi = NULL
2. WHERE NOT v.bajarildi NOT NULL → NULL → qator tashlandi
3. GROUP BY Shahnoza yo'q

WHERE JOIN'dan keyin ishlaydi (SQL tili darsidagi bajarilish tartibi). U o'ng jadval ustunini tekshirganda, LEFT JOIN qo'shgan NULL qatorlar shartdan o'tmaydi. Natijada LEFT JOIN jimgina INNER JOIN'ga aylanadi. Xato xabari yo'q — shuning uchun bu tuzoq xavfli.

4.2 To'g'risi — shart ON da

sql
SELECT f.ism, count(v.id) AS bajarilmagan
FROM foydalanuvchilar f
LEFT JOIN vazifalar v
  ON v.egasi_id = f.id AND NOT v.bajarildi
GROUP BY f.id ORDER BY f.id;
text
        ism         | bajarilmagan
--------------------+--------------
 Aziz Karimov       |            7
 Dilnoza Rahimova   |            3
 Sardor Tursunov    |            3
 Madina Yusupova    |            5
 Javohir Olimov     |            3
 Gulnora Saidova    |            2
 Bekzod Nazarov     |            2
 Shahnoza Ergasheva |            0
(8 rows)

Endi shart juftlash qoidasining bir qismi: "bajarilmagan vazifani juft deb ol". Shahnozaga bunday juft topilmadi — LEFT JOIN uni baribir NULL bilan qoldirdi.

Qoida bitta gapda: LEFT JOIN'da o'ng jadvalga oid shart — ON ga, chap jadvalga oid shart — WHERE ga. Masalan WHERE f.id < 5 — xavfsiz, u chap jadvalni filtrlaydi.

Tekshirib ko'ring: WHERE v.id IS NULL shartli LEFT JOIN nima beradi? Bu ham tuzoqmi?

Javob

Bu tuzoq emas, ataylab ishlatiladigan naqsh: "jufti yo'q qatorlar". foydalanuvchilar LEFT JOIN vazifalar ... WHERE v.id IS NULL — vazifasi yo'q foydalanuvchilar (Shahnoza). IS NULL — NULL qatorlar aynan o'tadigan shart. Bu anti-join deyiladi; uning boshqa yo'lini Subquery va LATERAL darsida ko'ramiz.

5. Ko'p jadvalli zanjir

5.1 Vazifa → bog'lovchi → teg

Teglar vazifa bilan to'g'ridan bog'lanmagan — o'rtada vazifa_teglari turadi. Uchta jadvalni zanjir qilamiz:

sql
SELECT v.id, v.matn, string_agg(t.nom, ' ' ORDER BY t.nom) AS teglar
FROM vazifalar v
JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
JOIN teglar t ON t.id = vt.teg_id
WHERE v.egasi_id = 1 GROUP BY v.id ORDER BY v.id;
text
 id |                       matn                       |     teglar
----+--------------------------------------------------+-----------------
  1 | JavaScript kursining 26-qismini boshlash #o'qish | #o'qish
  2 | Postgres'ni Docker'da ko'tarish #o'qish #docker  | #docker #o'qish
  8 | Bozordan kartoshka va piyoz olish #bozor         | #bozor
  9 | Onamga dori olib borish #oila                    | #oila
 17 | Portfolio saytiga blog yozish #portfolio         | #portfolio
 23 | Oshxonadagi jo'mrakni almashtirish #ta'mir #uy   | #ta'mir #uy
 28 | Elektr to'lovini Click orqali qilish #to'lov     | #to'lov
 34 | SQL JOIN mashqlarini yechish #o'qish             | #o'qish
(8 rows)

Har JOIN oldingi natijaga yangi jadvalni qo'shadi. Avval vazifa + uning bog'lanishlari, keyin + teg nomi. string_agg bir vazifaning bir nechta tegini bitta satrga yig'adi (Agregatlar). Azizning 12 vazifasidan 8 tasi chiqdi — tegsizlari INNER JOIN'da tushib qoldi.

5.2 Teskari yo'nalish: tegdan odamga

#to'lov tegli vazifalar, kimniki ekani bilan — to'rt jadval:

sql
SELECT f.ism, v.matn, v.bajarildi
FROM teglar t
JOIN vazifa_teglari vt ON vt.teg_id = t.id
JOIN vazifalar v ON v.id = vt.vazifa_id
JOIN foydalanuvchilar f ON f.id = v.egasi_id
WHERE t.nom = '#to''lov' ORDER BY f.ism, v.id;
text
       ism        |                     matn                     | bajarildi
------------------+----------------------------------------------+-----------
 Aziz Karimov     | Elektr to'lovini Click orqali qilish #to'lov | t
 Bekzod Nazarov   | Universitet kontrakti #to'lov                | f
 Dilnoza Rahimova | Gaz to'lovi #to'lov                          | t
 Madina Yusupova  | Yotoqxona to'lovi #to'lov                    | t
 Sardor Tursunov  | Soliq hisobotini topshirish #ish #to'lov     | f
(5 rows)

Beshta foydalanuvchida #to'lov bor — har biri o'z tegi (id lari har xil), nomi bir xil. Zanjir qaysi jadvaldan boshlanishi muhim emas: yo'l (FK'lar) bir xil.

5.3 Fan-out: qatorlar ko'payib ketadi

JOIN "bittaga ko'p" tomonga yurganda qatorlar ko'payadi. Bu ba'zan sanashni buzadi. Dilnozada 6 vazifa bor. Lekin teglar bilan birlashtirib sanasak (biz ishga tushirdik, PostgreSQL 17.6):

sql
SELECT f.ism, count(v.id) AS vazifalar
FROM foydalanuvchilar f
JOIN vazifalar v ON v.egasi_id = f.id
JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
WHERE f.id = 2
GROUP BY f.id;
text
       ism        | vazifalar
------------------+-----------
 Dilnoza Rahimova |         7
(1 row)

7 — noto'g'ri. "Ingliz tili: 20 ta yangi so'z #o'qish #ingliz" ikki tegli, JOIN'dan keyin u ikki qator bo'ldi va ikki marta sanaldi. Bu hodisa fan-out (yelpig'ich) deyiladi. Tuzatish — takrorsiz sanash va tegsizlarni yo'qotmaslik uchun LEFT JOIN:

sql
SELECT f.ism, count(DISTINCT v.id) AS vazifalar,
       count(vt.teg_id) AS boglanishlar
FROM foydalanuvchilar f
JOIN vazifalar v ON v.egasi_id = f.id
LEFT JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
WHERE f.id = 2
GROUP BY f.id;
text
       ism        | vazifalar | boglanishlar
------------------+-----------+--------------
 Dilnoza Rahimova |         6 |            7
(1 row)

count(DISTINCT v.id) — har vazifa bir marta. Ikki "ko'p" tomonni (masalan teglar va izohlar) bitta so'rovda birlashtirsangiz, ko'payish ko'paytmaga aylanadi: 3 teg × 4 izoh = 12 qator. Bunday hollarda har birini alohida sanash kerak — Subquery va LATERAL va CTE darslarida.

6. Self-join: jadval o'zi bilan

Kichik vazifaning otasi ham vazifalar jadvalida. Jadvalni o'zi bilan birlashtirish uchun unga ikki xil taxallus beramiz — go'yo ikki nusxa:

sql
SELECT k.id, k.matn AS kichik_vazifa, o.matn AS ota
FROM vazifalar k
JOIN vazifalar o ON o.id = k.ota_id
ORDER BY k.id;
text
 id |        kichik_vazifa        |                      ota
----+-----------------------------+-----------------------------------------------
 41 | Jadvallarni loyihalash      | Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish
 42 | Adabiyotlar ro'yxati        | Kurs ishi: ma'lumotlar bazasi bo'limi #o'qish
 43 | Blog uchun mavzu tanlash    | Portfolio saytiga blog yozish #portfolio
 44 | Qoralama yozish             | Portfolio saytiga blog yozish #portfolio
 45 | Rasmlarni siqish            | Portfolio saytiga blog yozish #portfolio
 46 | ER diagramma chizish        | Jadvallarni loyihalash
 47 | Normalizatsiyani tekshirish | Jadvallarni loyihalash
(7 rows)

k — "kichik vazifa" nusxasi, o — "ota" nusxasi. Bu self-join (o'z-o'zi bilan birlashtirish). 46 ning otasi 41, 41 ning otasi 7 — ikki daraja. Bitta JOIN faqat bitta darajani ko'radi. Hamma darajani bitta so'rovda yurish — Rekursiv CTE darsida.

Tekshirib ko'ring: shu so'rovda taxalluslarsiz FROM vazifalar JOIN vazifalar ON ... deb yozib bo'ladimi?

Javob

Yo'q. Ikki nusxani ajratib bo'lmaydi va Postgres table name "vazifalar" specified more than once xatosini beradi. Self-join'da taxallus majburiy.

7. FULL JOIN, USING va CROSS JOIN

7.1 RIGHT va FULL — vazifasiz teg

Vazifasiz teg yaratamiz (Shahnozaga) va uni RIGHT JOIN bilan topamiz:

sql
INSERT INTO teglar (egasi_id, nom) VALUES (8, '#bosh_teg');
SELECT v.id AS vazifa, t.nom AS teg FROM vazifa_teglari vt
RIGHT JOIN teglar t ON t.id = vt.teg_id
LEFT JOIN vazifalar v ON v.id = vt.vazifa_id WHERE v.id IS NULL;
text
INSERT 0 1
 vazifa |    teg
--------+-----------
        | #bosh_teg
(1 row)

RIGHT JOIN teglar — hamma teglar qoladi. WHERE v.id IS NULL — faqat vazifasizlari (anti-join naqshi).

FULL JOIN (to'liq birlashtirish) — ikkala tomonning juftsizlari ham qoladi. Vazifalar va bog'lanishlarni FULL JOIN qilib, uch xil qatorni sanaymiz:

sql
SELECT count(*) FILTER (WHERE v.id IS NULL) AS faqat_teg,
       count(*) FILTER (WHERE vt.teg_id IS NULL) AS tegsiz_vazifa,
       count(*) AS jami
FROM vazifalar v FULL JOIN vazifa_teglari vt ON vt.vazifa_id = v.id;
text
 faqat_teg | tegsiz_vazifa | jami
-----------+---------------+------
         0 |             8 |   54
(1 row)

8 ta tegsiz vazifa (chap juftsiz), 0 ta vazifasiz bog'lanish (o'ng juftsiz — FK bunga yo'l qo'ymaydi). Jami 54 = 46 juft + 8. FULL JOIN ko'pincha ikki manbani solishtirishda kerak: "eski tizimda bor, yangisida yo'q" va teskarisi.

7.2 USING

Ikki jadvalda juftlash ustuni bir xil nomlansa, ON a.x = b.x o'rniga USING (x):

sql
SELECT vazifa_id, count(*) FROM vazifa_teglari
JOIN (SELECT id AS vazifa_id FROM vazifalar WHERE egasi_id = 2) d
USING (vazifa_id) GROUP BY 1 ORDER BY 1;
text
 vazifa_id | count
-----------+-------
         5 |     2
        14 |     1
        16 |     1
        26 |     1
        29 |     1
        36 |     1
(6 rows)

USING natijada bitta umumiy vazifa_id ustunini qoldiradi — taxallussiz yozsa bo'ladi. Bizning sxemada nomlar odatda farq qiladi (id va vazifa_id), shuning uchun ON ko'proq ishlatiladi.

NATURAL JOIN — USING ni hamma bir xil nomli ustunlar bo'yicha o'zi tuzadi. Bu xavfli. Biz sinadik (PostgreSQL 17.6):

sql
SELECT f.ism, v.matn
FROM foydalanuvchilar f NATURAL JOIN vazifalar v LIMIT 2;
text
 ism | matn
-----+------
(0 rows)

0 qator, xatosiz! Ikkala jadvalda id va yaratilgan_vaqt bor — NATURAL ularni tenglashtirdi: "foydalanuvchi id si = vazifa id si VA vaqtlar teng". Ertaga jadvalga bir xil nomli ustun qo'shilsa, so'rov ma'nosi jimgina o'zgaradi. NATURAL JOIN'ni ishlatmang.

7.3 CROSS JOIN

CROSS JOIN — shartsiz, har qator har qator bilan (dekart ko'paytma):

sql
SELECT count(*) FROM foydalanuvchilar
CROSS JOIN unnest(enum_range(NULL::ustuvorlik)) u;
text
 count
-------
    24
(1 row)

8 foydalanuvchi × 3 ustuvorlik = 24. Foydali joyi — hisobot "katagi": har foydalanuvchi uchun har ustuvorlik qatori bo'lsin, keyin LEFT JOIN bilan sonlar qo'yilsin (nollar ham ko'rinadi). enum_range — enum qiymatlari massivi, unnest uni qatorlarga yoyadi.

Xavfli tomoni: ON unutilsa, eski vergulli yozuv ham dekart ko'paytma beradi:

sql
SELECT count(*) AS dekart FROM foydalanuvchilar, vazifalar;
text
 dekart
--------
    376
(1 row)

8 × 47 = 376. Shu 8 foydalanuvchi va 2 mln vazifada bu 16 mln qator bo'lardi; kanonning katta bazasida (20 000 foydalanuvchi × 2 mln vazifa) esa 40 mlrd. Vergulli yozuv (FROM a, b WHERE a.x = b.x) eski kodda uchraydi — ishlaydi, lekin shartni unutish oson. Doim JOIN ... ON yozing.

8. Ko'p uchraydigan xatolar

8.1 column reference "id" is ambiguous

sql
SELECT id, matn FROM vazifalar v
JOIN foydalanuvchilar f ON f.id = v.egasi_id;
text
ERROR:  column reference "id" is ambiguous
LINE 1: SELECT id, matn FROM vazifalar v
               ^

Tarjimasi: "id ustuniga havola ikki ma'noli". Ikkala jadvalda ham id bor. Yechim — v.id yoki f.id. Odat qiling: JOIN'li so'rovda har ustunni taxallus bilan yozing, hatto yagona bo'lsa ham.

8.2 ON siz JOIN

sql
SELECT * FROM vazifalar v JOIN foydalanuvchilar f;
text
ERROR:  syntax error at or near ";"
LINE 1: SELECT * FROM vazifalar v JOIN foydalanuvchilar f;
                                                         ^

JOIN (INNER, LEFT, RIGHT, FULL) shartsiz bo'lmaydi — Postgres ON yoki USING ni kutgan joyda ; ni ko'rdi. Faqat CROSS JOIN shartsiz.

8.3 LEFT JOIN Shahnozani "yo'qotdi"

O'ng jadval sharti WHERE da — «LEFT JOIN + WHERE tuzog'i» bo'limi. Shartni ON ga ko'chiring.

8.4 Son kutilganidan katta

Fan-out: bir nechta "ko'p" jadval birlashtirilgan. count(DISTINCT ...) yoki alohida sanash.

8.5 count(*) LEFT JOIN'da 0 o'rniga 1 berdi

Juftsiz qator ham bitta qator. O'ng jadval ustunini sanang: count(v.id).

9. Mashqlar

Mashq fayllari kurs/mashqlar/26/24-join/ da. Hammasi vazifalar holat 23 da.

1-mashq (oson): kichik vazifalar egasi

Hamma kichik vazifalarni (ota_id IS NOT NULL) egasining ismi bilan chiqaring: id, matn, ism, id bo'yicha tartiblang. Yordam: «INNER JOIN» bo'limidagi kabi JOIN foydalanuvchilar f ON f.id = v.egasi_id.

Yechim
sql
SELECT v.id, v.matn, f.ism
FROM vazifalar v
JOIN foydalanuvchilar f ON f.id = v.egasi_id
WHERE v.ota_id IS NOT NULL
ORDER BY v.id;
text
 id |            matn             |       ism
----+-----------------------------+-----------------
 41 | Jadvallarni loyihalash      | Madina Yusupova
 42 | Adabiyotlar ro'yxati        | Madina Yusupova
 43 | Blog uchun mavzu tanlash    | Aziz Karimov
 44 | Qoralama yozish             | Aziz Karimov
 45 | Rasmlarni siqish            | Aziz Karimov
 46 | ER diagramma chizish        | Madina Yusupova
 47 | Normalizatsiyani tekshirish | Madina Yusupova
(7 rows)

WHERE v.ota_id IS NOT NULL — chap jadval (vazifalar) sharti, INNER JOIN'da WHERE ga yozish to'g'ri.

2-mashq (o'rta): yuqori ustuvorlikdagilar, nol bilan

Har foydalanuvchida nechta yuqori ustuvorlikdagi vazifa borligini chiqaring — Shahnoza ham 0 bilan ko'rinsin. Ustunlar: ism, yuqori; id bo'yicha. Yordam: shart qayerga yoziladi — «LEFT JOIN + WHERE tuzog'i».

Yechim
sql
SELECT f.ism, count(v.id) AS yuqori
FROM foydalanuvchilar f
LEFT JOIN vazifalar v
  ON v.egasi_id = f.id AND v.ustuvorlik = 'yuqori'
GROUP BY f.id ORDER BY f.id;
text
        ism         | yuqori
--------------------+--------
 Aziz Karimov       |      3
 Dilnoza Rahimova   |      1
 Sardor Tursunov    |      2
 Madina Yusupova    |      1
 Javohir Olimov     |      1
 Gulnora Saidova    |      1
 Bekzod Nazarov     |      1
 Shahnoza Ergasheva |      0
(8 rows)

v.ustuvorlik = 'yuqori' — o'ng jadval sharti, shuning uchun ON da. WHERE da bo'lsa Shahnoza tushib qolardi.

3-mashq (qiyin): teglar soni, fan-out'siz

Har foydalanuvchi uchun: ism, vazifalar soni, turli teglar soni (bir nechta vazifada ishlatilgan teg bir marta). Teglari yo'q va vazifasi yo'q foydalanuvchi ham 0 bilan chiqsin. Yordam: ikki LEFT JOIN (vazifalar, vazifa_teglari) va count(DISTINCT ...).

Yechim
sql
SELECT f.ism,
       count(DISTINCT v.id) AS vazifalar,
       count(DISTINCT vt.teg_id) AS teglar
FROM foydalanuvchilar f
LEFT JOIN vazifalar v ON v.egasi_id = f.id
LEFT JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
GROUP BY f.id ORDER BY f.id;
text
        ism         | vazifalar | teglar
--------------------+-----------+--------
 Aziz Karimov       |        12 |      8
 Dilnoza Rahimova   |         6 |      6
 Sardor Tursunov    |         6 |      5
 Madina Yusupova    |         9 |      3
 Javohir Olimov     |         6 |      3
 Gulnora Saidova    |         4 |      3
 Bekzod Nazarov     |         4 |      4
 Shahnoza Ergasheva |         0 |      0
(8 rows)

Ikkinchi LEFT JOIN ham LEFT bo'lishi shart: aks holda tegsiz vazifalar va Shahnoza tushib qoladi. DISTINCT siz vazifalar soni fan-out bilan oshib ketardi.

4-mashq: SQL qadami — ro'yxat so'rovi teglar bilan

25-qism API'si vazifalar ro'yxatini teglar massivi bilan qaytaradi. O'z bazangizda Azizning (egasi_id = 1) oxirgi 4 vazifasini teglar massivi bilan chiqaring; tegsiz vazifada bo'sh massiv {} bo'lsin. Sxema o'zgarmaydi — commit yo'q, so'rovni eslatma sifatida saqlang. Yordam: array_agg(... ) FILTER (WHERE ...) va coalesce (Agregatlar, NULL mantiqi).

Yechim
sql
SELECT v.id, v.matn,
  coalesce(array_agg(t.nom ORDER BY t.nom)
           FILTER (WHERE t.nom IS NOT NULL), '{}') AS teglar
FROM vazifalar v
LEFT JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
LEFT JOIN teglar t ON t.id = vt.teg_id
WHERE v.egasi_id = 1
GROUP BY v.id
ORDER BY v.yaratilgan_vaqt DESC, v.id DESC
LIMIT 4;
text
 id |                      matn                      |    teglar
----+------------------------------------------------+---------------
 38 | Velosiped zanjirini moylash                    | {}
 34 | SQL JOIN mashqlarini yechish #o'qish           | {#o'qish}
 28 | Elektr to'lovini Click orqali qilish #to'lov   | {#to'lov}
 23 | Oshxonadagi jo'mrakni almashtirish #ta'mir #uy | {#ta'mir,#uy}
(4 rows)

FILTER (WHERE t.nom IS NOT NULL) siz tegsiz vazifada {NULL} chiqardi (LEFT JOIN'ning NULL qatori). Barcha qatorlar filtrlansa array_agg NULL beradi — coalesce uni {} ga almashtiradi. JavaScript'ga bu bo'sh massiv bo'lib keladi.

10. Real ishda

  • Har API endpoint ichida JOIN bor: buyurtma + mijoz + mahsulotlar, post + muallif + izohlar soni. ORM'lar (Prisma include, Drizzle with) ham ichida JOIN yoki alohida so'rovlar yasaydi (27-qism).
  • LEFT JOIN + WHERE — kod review'da eng ko'p topiladigan SQL xatolaridan biri: hisobotda "nol"lar yo'qoladi va hech kim sezmaydi.
  • Fan-out — dashboard raqamlari "ikki baravar" chiqishining odatiy sababi.
  • Tezlik: JOIN ustunlari (FK) indeksli bo'lsin — Foreign key darsida vazifalar_egasi_id_idx ni shuning uchun qo'shdik. JOIN algoritmlari (nested loop, hash, merge) — EXPLAIN ANALYZE'ni o'qish darsida.
  • Intervyu: "INNER va LEFT farqi", "LEFT JOIN'da shart ON yoki WHERE'da — farqi nima?" — eng ko'p beriladigan SQL savollari.

Xulosa

  • JOIN ... ON — ikki jadval qatorlarini shart bo'yicha juftlaydi; har ustunni taxallus bilan yozing.
  • INNER — faqat juftlilar; LEFT — chapning hammasi (NULL bilan); FULL — ikkala tomonning hammasi; RIGHT — LEFT'ning aksi.
  • LEFT JOIN'da o'ng jadval sharti ON ga; WHERE ga yozilsa, LEFT jimgina INNER bo'ladi.
  • LEFT JOIN'dan keyin count(o'ng.id) sanang; ko'p tomonli JOIN'da fan-out'dan ehtiyot bo'ling (count(DISTINCT)).
  • Self-join — bitta jadval, ikki taxallus; CROSS JOIN — har kombinatsiya; NATURAL JOIN — ishlatmang.

Keyingi dars: Subquery va LATERAL — so'rov ichida so'rov va "har foydalanuvchining oxirgi 2 vazifasi".

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
SQL JOIN: INNER, LEFT, RIGHT, FULL, CROSS va self-join PostgreSQL misollarida — IlmHamroh