Correlated Subquery — EXISTS, NOT EXISTS

~20 daq · MySQL · Subquery va CTE

Muammodan boshlaylik

O'tgan darsdagi subquery lar mustaqil edi: SELECT AVG(narx) FROM mahsulotlar ni alohida ishga tushirsa ham ishlaydi.

Endi savol: "O'z toifasidagi o'rtacha narxdan qimmat mahsulotlar". Telefon uchun — telefonlar o'rtachasi, noutbuk uchun — noutbuklar o'rtachasi. Har bir mahsulot uchun boshqa o'rtacha kerak.

Subquery tashqi so'rovning joriy qatoriga bog'liq bo'lishi kerak. Bu — correlated subquery.


Correlated subquery

SELECT p.nom, p.toifa_id, p.narx
FROM mahsulotlar p
WHERE p.narx > (
    SELECT AVG(p2.narx)
    FROM mahsulotlar p2
    WHERE p2.toifa_id = p.toifa_id     -- ← tashqi so'rovdagi p ga ishora
);
+----------------+----------+----------+
| nom            | toifa_id | narx     |
+----------------+----------+----------+
| iPhone 15      |        1 | 12500000 |
| MacBook Air M3 |        2 | 16900000 |
| AirPods Pro    |        3 |  3100000 |
+----------------+----------+----------+

Qanday ishlaydi (mantiqan):

p = iPhone 15 (toifa 1)   → ichki: AVG toifa 1 = 8 650 000  → 12 500 000 > 8 650 000 ✅
p = Samsung   (toifa 1)   → ichki: AVG toifa 1 = 8 650 000  →  4 800 000 > ...        ❌
p = MacBook   (toifa 2)   → ichki: AVG toifa 2 = 12 050 000 → 16 900 000 > ...        ✅
...

Ichki so'rov har bir tashqi qator uchun qayta baholanadi. Uni alohida ishga tushirib bo'lmaydi — p.toifa_id nima ekani noma'lum.

Taxalluslar majburiy: p (tashqi) va p2 (ichki) — aks holda MySQL qaysi toifa_id nazarda tutilganini bilmaydi.


EXISTS — "kamida bittasi bormi?"

EXISTS (subquery) — subquery kamida bitta qator qaytarsa TRUE. Qaytgan qiymat muhim emas — faqat qator borligi.

-- Kamida bitta buyurtma bergan mijozlar
SELECT m.ism
FROM mijozlar m
WHERE EXISTS (
    SELECT 1 FROM buyurtmalar b WHERE b.mijoz_id = m.id
);

SELECT 1 — odat. SELECT * yoki SELECT b.id ham bir xil ishlaydi: EXISTS ustunlarga qaramaydi.

Tezlik afzalligi: MySQL birinchi mos qatorni topishi bilan to'xtaydi. Mijozning 10 000 ta buyurtmasi bo'lsa ham — bittasini topdi, yetarli. COUNT(*) > 0 esa hammasini sanab chiqadi.

-- ❌ Keraksiz ish
WHERE (SELECT COUNT(*) FROM buyurtmalar b WHERE b.mijoz_id = m.id) > 0

-- ✅
WHERE EXISTS (SELECT 1 FROM buyurtmalar b WHERE b.mijoz_id = m.id)

NOT EXISTS — "hech biri yo'q"

-- Hech qachon buyurtma bermagan mijozlar
SELECT m.ism, m.email
FROM mijozlar m
WHERE NOT EXISTS (
    SELECT 1 FROM buyurtmalar b WHERE b.mijoz_id = m.id
);
-- Nodira Usmonova
-- Hech qachon sotilmagan mahsulotlar
SELECT p.nom FROM mahsulotlar p
WHERE NOT EXISTS (
    SELECT 1 FROM buyurtma_qatorlari q WHERE q.mahsulot_id = p.id
);
-- Lenovo IdeaPad

NOT EXISTS va NOT IN

O'tgan darsda NOT IN ning NULL tuzog'ini ko'rdik. NOT EXISTS da bu muammo yo'q — u faqat "qator bormi?" deb so'raydi, qiymatlarni solishtirmaydi.

NOT IN NOT EXISTS LEFT JOIN ... IS NULL
NULL bilan xavfsiz ❌ ✅ ✅
Murakkab shart Qiyin Oson O'rtacha
O'qilishi Oson O'rtacha O'rtacha

Qoida: "yo'q" savollari uchun — NOT EXISTS yoki LEFT JOIN ... IS NULL. NOT IN (subquery) dan qoching.


Murakkab shartlar bilan EXISTS

EXISTS ichida istalgan shart yozish mumkin — bu uning asosiy kuchi:

-- Oxirgi 30 kunda yetkazilgan buyurtmasi bor mijozlar
SELECT m.ism FROM mijozlar m
WHERE EXISTS (
    SELECT 1 FROM buyurtmalar b
    WHERE b.mijoz_id = m.id
      AND b.holat = 'yetkazildi'
      AND b.yaratilgan >= NOW() - INTERVAL 30 DAY
);
-- AirPods sotib olgan, lekin hech qachon qopqoq olmagan mijozlar (cross-sell uchun)
SELECT m.ism FROM mijozlar m
WHERE EXISTS (
    SELECT 1 FROM buyurtmalar b
    JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
    WHERE b.mijoz_id = m.id AND q.mahsulot_id = 5
)
AND NOT EXISTS (
    SELECT 1 FROM buyurtmalar b
    JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
    WHERE b.mijoz_id = m.id AND q.mahsulot_id = 6
);

Har bir EXISTS — alohida, mustaqil savol. Ularni AND/OR bilan erkin birlashtirasiz. JOIN bilan bunday so'rov ancha chalkash bo'lardi.


Correlated subquery SELECT ichida

Har bir qator uchun hisoblangan qiymat:

SELECT
    m.ism,
    (SELECT MAX(b.yaratilgan) FROM buyurtmalar b WHERE b.mijoz_id = m.id) AS oxirgi_buyurtma,
    (SELECT COUNT(*) FROM buyurtmalar b WHERE b.mijoz_id = m.id)          AS jami
FROM mijozlar m;
+-----------------+---------------------+------+
| ism             | oxirgi_buyurtma     | jami |
+-----------------+---------------------+------+
| Aziz Karimov    | 2026-10-12 18:40:00 |    2 |
| Malika Rahimova | 2026-11-01 16:20:00 |    2 |
| Jasur Toshev    | 2026-10-20 09:30:00 |    1 |
| Nodira Usmonova | NULL                |    0 |
+-----------------+---------------------+------+

Tezlik haqida

"Har bir qator uchun qayta bajariladi" — sekin eshitiladi. Amalda:

  • MySQL 8 optimizatori ko'p correlated subquery larni ichki ravishda JOIN yoki semi-join ga aylantiradi
  • EXISTS birinchi topilgan qatorda to'xtaydi
  • Ichki so'rovdagi ustunda (b.mijoz_id) indeks bo'lsa — har bir tekshiruv bir zumda

Indekssiz esa — haqiqatan sekin: 10 000 mijoz × 1 000 000 buyurtma bo'ylab to'liq ko'rib chiqish. Shuning uchun correlated subquery dagi bog'lash ustuni doim indekslangan bo'lsin (foreign key lar avtomatik indekslanadi).


Nima uchun shunday?

Nima uchun EXISTS ichida SELECT 1 yozish odat bo'lgan?

Tarixan ba'zi bazalar EXISTS (SELECT * ...) da haqiqatan ham barcha ustunlarni o'qib chiqardi. SELECT 1 — optimizatorga va kodni o'qiyotgan odamga "menga qiymatlar kerak emas, faqat qator borligi" degan aniq signal. Bugungi MySQL da farq yo'q — lekin odat qoldi, chunki u niyatni aniq ifodalaydi.


Xulosa

Eslab qoling:

  • Correlated subquery — tashqi qatorga ishora qiladi (WHERE p2.toifa_id = p.toifa_id)
  • Har bir tashqi qator uchun qayta baholanadi; taxalluslar majburiy
  • EXISTS — "kamida bittasi bormi?"; birinchi topilganda to'xtaydi
  • COUNT(*) > 0 o'rniga — EXISTS
  • NOT EXISTS — "hech biri yo'q"; NULL bilan xavfsiz, NOT IN dan yaxshiroq
  • Bir nechta EXISTS / NOT EXISTS ni AND bilan birlashtirish — murakkab segmentlar
  • Bog'lash ustuni indekslangan bo'lsin

Keyingi dars

23-dars: CTE — WITH, rekursiv CTE