Subquery — WHERE, FROM, SELECT ichida, EXISTS
Muammodan boshlaylik
O'rtacha buyurtma summasidan yuqori buyurtmalarni topish kerak. Birinchi fikr — ikki qadam:
-- 1-qadam: o'rtacha summani topamiz
SELECT AVG(summa) FROM buyurtmalar; -- 850000
-- 2-qadam: shu raqamdan foydalanib filtr qilamiz
SELECT * FROM buyurtmalar WHERE summa > 850000;
Ishlaydi, lekin ikki alohida so'rov. Birinchi so'rovning natijasini qo'lda ikkinchisiga kiritish kerak. Agar o'rtacha summa o'zgarsa — birinchi so'rovni qayta ishlatish kerak.
Yaxshisi — bitta so'rovda hal qilish. Buning uchun subquery kerak.
Subquery nima?
Subquery — so'rov ichidagi so'rov. Tashqi so'rov natija olishi uchun ichki so'rov avval bajariladi va uning natijasi tashqi so'rovga uzatiladi.
Hayotiy o'xshatma: kassir va menejer. Kassir mijozdan to'lov olayotganda "bu mahsulotning narxi to'g'rimi?" deb menejerdan so'raydi. Menejer javob beradi — kassir shu javob asosida qaror qiladi. Subquery — shu menejerga savol.
Tashqi so'rov
└── Ichki so'rov (subquery) bajariladi
↓ natija
Tashqi so'rov natijadan foydalanadi
Namuna jadvallar
CREATE TABLE foydalanuvchilar (
id SERIAL PRIMARY KEY,
ism TEXT,
shahar TEXT
);
CREATE TABLE buyurtmalar (
id SERIAL PRIMARY KEY,
foydalanuvchi_id INT REFERENCES foydalanuvchilar(id),
mahsulot TEXT,
summa NUMERIC
);
INSERT INTO foydalanuvchilar VALUES
(1, 'Ali', 'Toshkent'),
(2, 'Vali', 'Samarqand'),
(3, 'Soli', 'Toshkent'),
(4, 'Jamol', 'Buxoro');
INSERT INTO buyurtmalar VALUES
(1, 1, 'Noutbuk', 5000000),
(2, 1, 'Sichqon', 150000),
(3, 2, 'Klaviatura', 350000),
(4, 3, 'Monitor', 2800000),
(5, 3, 'Headset', 450000);
-- Jamol buyurtma bermagan
WHERE ichida subquery
Eng ko'p ishlatiladigan joy. Subquery bitta qiymat yoki qiymatlar to'plami qaytaradi, va tashqi so'rov shu asosida filtrlaydi.
Bitta qiymat qaytaradigan subquery
O'rtacha summasidan yuqori buyurtmalar:
SELECT mahsulot, summa
FROM buyurtmalar
WHERE summa > (SELECT AVG(summa) FROM buyurtmalar);
-- ↑ bu bitta raqam qaytaradi: 1750000
mahsulot │ summa
-----------+---------
Noutbuk │ 5000000
Monitor │ 2800000
Subquery avval bajariladi → 1750000 → tashqi so'rov summa > 1750000 deb filtrlaydi.
Ro'yxat qaytaradigan subquery — IN
Toshkentdagi foydalanuvchilarning buyurtmalari:
SELECT mahsulot, summa
FROM buyurtmalar
WHERE foydalanuvchi_id IN (
SELECT id FROM foydalanuvchilar WHERE shahar = 'Toshkent'
);
-- ↑ bu ro'yxat qaytaradi: (1, 3)
mahsulot │ summa
-----------+---------
Noutbuk │ 5000000
Sichqon │ 150000
Monitor │ 2800000
Headset │ 450000
Subquery (1, 3) qaytaradi → tashqi so'rov foydalanuvchi_id IN (1, 3) deb filtrlaydi.
FROM ichida subquery
Subquery natijasi vaqtinchalik jadval sifatida ishlatiladi. Bu jadvalga AS bilan nom beriladi.
Har bir foydalanuvchining umumiy xarid summasi, faqat 1 000 000 dan yuqorilar:
SELECT ism, jami
FROM (
SELECT
f.ism,
SUM(b.summa) AS jami -- ← har bir foydalanuvchi summasi
FROM foydalanuvchilar f
JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id
GROUP BY f.id, f.ism
) AS statistika -- ← vaqtinchalik jadval nomi
WHERE jami > 1000000; -- ← endi shu jadvaldan filtr
ism │ jami
------+---------
Ali │ 5150000
Soli │ 3250000
Nima uchun subquery kerak edi? GROUP BY dan keyin WHERE da aggregate funksiyani to'g'ridan-to'g'ri ishlata olmaysiz — HAVING ishlatiladi. Lekin HAVING faqat GROUP BY bo'lgan ustunlarga qo'llanadi. Murakkab holatlarda subquery yanada moslashuvchan.
SELECT ichida subquery
Har bir qator uchun alohida hisoblash kerak bo'lganda. Bu correlated subquery — ichki so'rov tashqi so'rovning har bir qatoriga bog'liq.
Har bir foydalanuvchi uchun uning buyurtmalar sonini birga ko'rsatish:
SELECT
f.ism,
(SELECT COUNT(*) FROM buyurtmalar b
WHERE b.foydalanuvchi_id = f.id) AS buyurtmalar_soni
-- ↑ tashqi so'rovning f.id si
FROM foydalanuvchilar f;
ism │ buyurtmalar_soni
-------+------------------
Ali │ 2
Vali │ 1
Soli │ 2
Jamol │ 0
Bu subquery har bir foydalanuvchi qatori uchun alohida bajariladi. 4 ta foydalanuvchi — 4 marta bajariladi.
⚠️ Diqqat: SELECT ichidagi correlated subquery ko'p qatorda sekin ishlashi mumkin. Ko'pincha
LEFT JOIN+GROUP BYbilan almashtiriladi — bu tezroq. Lekin kichik jadvallar uchun o'qilishi osonroq.
EXISTS — bormi yo'qmi
EXISTS — subquery hech bo'lmasa bitta qator qaytaradimi, yo'qmi deb tekshiradi. Qiymatlarni taqqoslamaydi — faqat "bor" yoki "yo'q".
Kamida bitta buyurtma bergan foydalanuvchilar:
SELECT ism
FROM foydalanuvchilar f
WHERE EXISTS (
SELECT 1 FROM buyurtmalar b
WHERE b.foydalanuvchi_id = f.id
);
ism
------
Ali
Vali
Soli
Jamol yo'q — uning buyurtmasi yo'q.
SELECT 1 — EXISTS uchun qaysi ustun qaytarilishi muhim emas, faqat qator bor-yo'qligi tekshiriladi. Shu sababli SELECT 1 yoki SELECT * bir xil ishlaydi, lekin SELECT 1 aniqroq niyatni ko'rsatadi.
NOT EXISTS — yo'q narsani topish
Hech qachon buyurtma bermagan foydalanuvchilar:
SELECT ism
FROM foydalanuvchilar f
WHERE NOT EXISTS (
SELECT 1 FROM buyurtmalar b
WHERE b.foydalanuvchi_id = f.id
);
ism
-------
Jamol
Xuddi shu natijani LEFT JOIN bilan ham olish mumkin edi:
SELECT f.ism
FROM foydalanuvchilar f
LEFT JOIN buyurtmalar b ON f.id = b.foydalanuvchi_id
WHERE b.id IS NULL;
Ikkalasi ham to'g'ri. NOT EXISTS — niyatni aniqroq ifodalaydi: "buyurtmasi yo'q foydalanuvchilar". LEFT JOIN IS NULL — texnik jihatdan bir xil, lekin o'qishda biroz boshqacharoq.
Qayerda subquery, qayerda JOIN?
Subquery yaxshiroq:
→ Bitta qiymat kerak bo'lganda (AVG, MAX, MIN)
→ EXISTS / NOT EXISTS — bormi yo'qmi tekshirish
→ FROM ichida murakkab hisoblash
JOIN yaxshiroq:
→ Ikki jadvalning ustunlari birga kerak bo'lganda
→ Ko'p qatorli natija kerak bo'lganda
→ Performance muhim bo'lganda (katta jadvallarda JOIN tezroq)
Tez-tez uchraydigan xatolar
Subquery bir nechta qator qaytarganda uni = bilan solishtirish:
-- ❌ — subquery bir nechta qator qaytaradi
SELECT * FROM buyurtmalar
WHERE foydalanuvchi_id = (SELECT id FROM foydalanuvchilar WHERE shahar = 'Toshkent');
-- ERROR: more than one row returned by a subquery
-- ✅ — ro'yxat uchun IN ishlatiladi
WHERE foydalanuvchi_id IN (SELECT id FROM foydalanuvchilar WHERE shahar = 'Toshkent');
= — bitta qiymat kutadi. Bir nechta qiymat bo'lsa — IN ishlatiladi.
FROM ichidagi subquery ga alias bermaslik:
-- ❌ — alias yo'q
SELECT ism FROM (SELECT ism FROM foydalanuvchilar);
-- ERROR: subquery in FROM must have an alias
-- ✅ — alias bor
SELECT ism FROM (SELECT ism FROM foydalanuvchilar) AS vaqtinchalik;
FROM ichidagi har bir subquery AS bilan nom olishi shart — PostgreSQL uchun qoida.
Xulosa
Subquery — so'rov ichida so'rov. Ichkisi avval bajariladi, natijasi tashqiga uzatiladi.
WHERE ichida → filtr uchun (bitta qiymat yoki IN ro'yxat)
FROM ichida → vaqtinchalik jadval yaratish
SELECT ichida → har bir qator uchun alohida hisoblash
EXISTS → bor yoki yo'qligini tekshirish
Subquery o'qilishi oson — niyatni aniq ifodalaydi. Lekin katta ma'lumotlarda JOIN ko'pincha tezroq. Ikkalasini bilish kerak — vaziyatga qarab tanlash uchun.
Keyingi dars
17-dars: CTE — WITH, rekursiv CTE, materialized CTE