Subquery — WHERE, FROM, SELECT ichida
Muammodan boshlaylik
Savol: "O'rtacha narxdan qimmat mahsulotlar qaysilar?"
Ikki qadamda yechish mumkin:
SELECT AVG(narx) FROM mahsulotlar; -- 6445714
SELECT nom, narx FROM mahsulotlar WHERE narx > 6445714;
Lekin ertaga yangi mahsulot qo'shilsa — o'rtacha o'zgaradi va ikkinchi so'rovdagi raqam eskiradi. Bu raqamni qo'lda ko'chirish emas, bir so'rovning natijasini ikkinchisiga to'g'ridan-to'g'ri berish kerak.
Subquery (ichki so'rov) — boshqa so'rov ichiga joylashtirilgan SELECT.
WHERE ichida — bitta qiymat
SELECT nom, narx
FROM mahsulotlar
WHERE narx > (SELECT AVG(narx) FROM mahsulotlar);
+----------------+----------+
| nom | narx |
+----------------+----------+
| iPhone 15 | 12500000 |
| MacBook Air M3 | 16900000 |
| Lenovo IdeaPad | 7200000 |
+----------------+----------+
MySQL avval qavs ichidagi so'rovni bajaradi (6445714.29), keyin natijani tashqi so'rovga qo'yadi.
Qavs ichidagi so'rov bitta qiymat (bitta qator, bitta ustun) qaytaradi — bu skalyar subquery. Uni oddiy son o'rnida istalgan joyda ishlatish mumkin.
Agar ko'p qator qaytsa:
SELECT nom FROM mahsulotlar WHERE narx = (SELECT narx FROM mahsulotlar WHERE toifa_id = 1);
-- ERROR 1242: Subquery returns more than 1 row
Eng qimmat mahsulot (bir xil narxli bir nechtasi bo'lsa — hammasi):
SELECT nom, narx FROM mahsulotlar
WHERE narx = (SELECT MAX(narx) FROM mahsulotlar);
WHERE ichida — ro'yxat (IN)
Subquery bir ustun va ko'p qator qaytarsa — IN bilan:
-- Kamida bir marta sotilgan mahsulotlar
SELECT nom FROM mahsulotlar
WHERE id IN (SELECT mahsulot_id FROM buyurtma_qatorlari);
-- Toshkentlik mijozlarning buyurtmalari
SELECT id, holat FROM buyurtmalar
WHERE mijoz_id IN (SELECT id FROM mijozlar WHERE shahar = 'Toshkent');
NOT IN va NULL tuzog'i
-- Hech qachon sotilmagan mahsulotlar
SELECT nom FROM mahsulotlar
WHERE id NOT IN (SELECT mahsulot_id FROM buyurtma_qatorlari);
-- Lenovo IdeaPad ✅
Ishlaydi. Lekin subquery natijasida bitta NULL bo'lsa:
SELECT 5 NOT IN (1, 2); -- 1 (TRUE)
SELECT 5 NOT IN (1, 2, NULL); -- NULL — TRUE emas!
-- Mahsuloti yo'q toifalar (bizda hammasida bor, lekin muammo boshqa joyda)
SELECT nom FROM toifalar
WHERE id NOT IN (SELECT toifa_id FROM mahsulotlar);
-- Empty set — chunki "Sovg'a kartasi" ning toifa_id si NULL
5 NOT IN (1, 2, NULL) → "5 ≠ 1 VA 5 ≠ 2 VA 5 ≠ NULL" → oxirgisi NULL → butun ifoda NULL → qator tashlanadi. Bitta NULL butun NOT IN ni bo'sh natijaga aylantiradi.
[!WARNING]
NOT IN (subquery)ishlatsangiz, subquery ustunidaNULLbo'lmasligiga ishonch hosil qiling (WHERE ustun IS NOT NULLqo'shing). Yoki xavfsiz alternativ —NOT EXISTS(22-dars) yokiLEFT JOIN ... IS NULL(16-dars).
FROM ichida — vaqtinchalik jadval
Subquery natijasi — jadval. Uni FROM da ham ishlatish mumkin. Bunga derived table deyiladi:
-- Mijozlar bo'yicha o'rtacha chek
SELECT ROUND(AVG(jami)) AS ortacha_chek
FROM (
SELECT b.id, SUM(q.soni * q.narx) AS jami
FROM buyurtmalar b
JOIN buyurtma_qatorlari q ON q.buyurtma_id = b.id
WHERE b.holat <> 'bekor'
GROUP BY b.id
) AS cheklar;
Ikki bosqichli hisob:
- Ichkarida — har bir buyurtmaning summasi
- Tashqarida — shu summalarning o'rtachasi
Bitta GROUP BY bilan buni qilib bo'lmaydi — "aggregate ning aggregate i" kerak.
Derived table ga taxallus berish shart (AS cheklar):
SELECT * FROM (SELECT 1);
-- ERROR 1248: Every derived table must have its own alias
Guruhlangan natijani filtrlash va JOIN qilish
-- Har bir toifaning eng qimmat mahsuloti
SELECT p.nom, p.narx, p.toifa_id
FROM mahsulotlar p
JOIN (
SELECT toifa_id, MAX(narx) AS max_narx
FROM mahsulotlar
WHERE toifa_id IS NOT NULL
GROUP BY toifa_id
) AS eng ON eng.toifa_id = p.toifa_id AND eng.max_narx = p.narx;
+----------------+----------+----------+
| nom | narx | toifa_id |
+----------------+----------+----------+
| iPhone 15 | 12500000 | 1 |
| MacBook Air M3 | 16900000 | 2 |
| AirPods Pro | 3100000 | 3 |
+----------------+----------+----------+
"Har bir guruhdagi eng ..." — klassik masala. Window function bilan yanada qulay (29-dars).
SELECT ichida — har bir qator uchun qiymat
SELECT
nom,
narx,
narx - (SELECT AVG(narx) FROM mahsulotlar) AS ortachadan_farq
FROM mahsulotlar;
Bog'liq qiymat bilan (correlated — keyingi dars):
SELECT
m.ism,
(SELECT COUNT(*) FROM buyurtmalar b WHERE b.mijoz_id = m.id) AS buyurtmalar
FROM mijozlar m;
Bu LEFT JOIN + GROUP BY ning muqobili. Kichik jadvallarda qulay va o'qishli. Katta jadvallarda — har bir qator uchun alohida subquery bajarilishi mumkin, shuning uchun EXPLAIN bilan tekshiring (32-dars).
Subquery INSERT, UPDATE, DELETE da
-- Hech narsa sotib olmagan, 1 yildan eski mijozlarni arxivlash
INSERT INTO arxiv_mijozlar
SELECT * FROM mijozlar
WHERE yaratilgan < NOW() - INTERVAL 1 YEAR
AND id NOT IN (SELECT mijoz_id FROM buyurtmalar);
-- O'rtachadan qimmat mahsulotlarga 5% chegirma
UPDATE mahsulotlar
SET narx = ROUND(narx * 0.95)
WHERE narx > (SELECT * FROM (SELECT AVG(narx) FROM mahsulotlar) AS t);
Oxirgi misoldagi g'alati (SELECT * FROM (...) AS t) — MySQL cheklovi: o'zgartirilayotgan jadvaldan to'g'ridan-to'g'ri subquery qilib bo'lmaydi:
ERROR 1093: You can't specify target table 'mahsulotlar' for update in FROM clause
Ichki so'rovni derived table ga o'rash MySQL ni natijani avval vaqtinchalik jadvalga yozishga majbur qiladi va muammo yo'qoladi.
Subquery yoki JOIN?
Ko'p masalani ikkala usulda yechish mumkin:
-- Subquery
SELECT nom FROM mahsulotlar
WHERE id IN (SELECT mahsulot_id FROM buyurtma_qatorlari);
-- JOIN
SELECT DISTINCT p.nom FROM mahsulotlar p
JOIN buyurtma_qatorlari q ON q.mahsulot_id = p.id;
| Subquery | JOIN | |
|---|---|---|
| O'qilishi | "Savolga" yaqin: "sotilganlar orasidagi" | Jadvallar aloqasini ko'rsatadi |
| Ikkinchi jadval ustunlari natijada | ❌ | ✅ |
| Takrorlanish | Yo'q | DISTINCT kerak bo'lishi mumkin |
| Tezlik | MySQL 8 ko'pincha ichki ravishda JOIN ga aylantiradi | — |
Qoida: ikkinchi jadvaldan ustun kerak bo'lsa — JOIN. Faqat filtr sifatida kerak bo'lsa — subquery (IN / EXISTS) ko'pincha o'qishliroq. Shubha bo'lsa — EXPLAIN bilan solishtiring.
Nima uchun shunday?
Nima uchun SQL so'rov ichida so'rov yozishga ruxsat beradi?
Chunki SQL ning asosiy g'oyasi — har bir so'rov natijasi ham jadval. Jadval qayerda kerak bo'lsa (FROM, IN, JOIN), u yerga so'rov qo'yish mumkin. Bitta qiymat (1×1 jadval) kerak bo'lsa — skalyar subquery. Bu "kompozitsiya" — kichik, tushunarli so'rovlardan murakkab savollarni qurish imkonini beradi. Keyingi darslardagi CTE shu g'oyaning yanada o'qishli shakli.
Xulosa
Eslab qoling:
- Skalyar subquery — bitta qiymat:
WHERE narx > (SELECT AVG(narx) ...) IN (subquery)— ro'yxat bo'yicha filtrNOT IN+NULL= bo'sh natija;NOT EXISTSyokiIS NOT NULLfiltrFROM (subquery) AS t— derived table, taxallus majburiy- "Aggregate ning aggregate i" va "har bir guruhdagi eng ..." — derived table bilan
- O'zgartirilayotgan jadvaldan subquery — derived table ga o'rang (1093 xatosi)
- Ustun kerak — JOIN, faqat filtr — subquery
Keyingi dars
22-dars: Correlated Subquery — EXISTS, NOT EXISTS