LATERAL JOIN — nima, qachon kerak
Muammodan boshlaylik
Har bir foydalanuvchi uchun uning oxirgi 3 ta buyurtmasini ko'rsatish kerak. Oddiy JOIN bilan barcha buyurtmalar keladi — keyin filtrash qiyin. Subquery bilan esa bir muammo: subquery tashqi so'rovning foydalanuvchi_id sini ko'ra olmaydi.
-- ❌ — bu xato beradi
SELECT f.ism, b.mahsulot
FROM foydalanuvchilar f
JOIN (
SELECT * FROM buyurtmalar
WHERE foydalanuvchi_id = f.id -- ← f.id ko'rinmaydi!
LIMIT 3
) b ON true;
-- ERROR: invalid reference to FROM-clause entry for table "f"
Subquery FROM ichida bo'lganda tashqi jadvalni ko'ra olmaydi. Bu muammoni hal qilish uchun LATERAL kerak.
LATERAL nima?
Oddiy JOIN da FROM ichidagi subquery tashqi jadvaldan mustaqil ishlaydi. LATERAL esa subquery ga "chap tomondagi jadvalning har bir qatorini ko'r" degan ruxsat beradi.
Hayotiy o'xshatma: do'konda navbat. Oddiy cashier har bir mijozni mustaqil xizmat qiladi — oldingi mijoz nima olganini bilmaydi. LATERAL — bu xodim har bir mijozning oldingi xaridlarini ko'rib, unga mos xizmat qilishi. Har bir mijoz uchun "oldingi xaridlarga qarab" boshqacha natija.
Oddiy JOIN: LATERAL JOIN:
───────────────── ──────────────────────────────
Subquery bir marta Subquery har bir qator uchun
ishlaydi, tashqi alohida ishlaydi, tashqi
jadvalni ko'rmaydi jadvalning qiymatini ko'radi
Namuna jadvallar
CREATE TABLE foydalanuvchilar (
id SERIAL PRIMARY KEY,
ism TEXT
);
CREATE TABLE buyurtmalar (
id SERIAL PRIMARY KEY,
foydalanuvchi_id INT REFERENCES foydalanuvchilar(id),
mahsulot TEXT,
sana DATE
);
INSERT INTO foydalanuvchilar VALUES (1,'Ali'), (2,'Vali'), (3,'Soli');
INSERT INTO buyurtmalar (foydalanuvchi_id, mahsulot, sana) VALUES
(1, 'Noutbuk', '2024-01-10'),
(1, 'Sichqon', '2024-02-15'),
(1, 'Klaviatura', '2024-03-01'),
(1, 'Monitor', '2024-04-20'),
(2, 'Headset', '2024-01-05'),
(2, 'Mikrofon', '2024-03-12'),
(3, 'Telefon', '2024-02-28');
LATERAL — asosiy misol
Har bir foydalanuvchi uchun oxirgi 2 ta buyurtma:
SELECT
f.ism,
b.mahsulot,
b.sana
FROM foydalanuvchilar f
LEFT JOIN LATERAL (
SELECT mahsulot, sana
FROM buyurtmalar
WHERE foydalanuvchi_id = f.id -- ← f.id ko'rinadi!
ORDER BY sana DESC
LIMIT 2
) b ON true;
ism │ mahsulot │ sana
-------+------------+------------
Ali │ Monitor │ 2024-04-20 ← oxirgi 2 ta
Ali │ Klaviatura │ 2024-03-01
Vali │ Mikrofon │ 2024-03-12 ← oxirgi 2 ta
Vali │ Headset │ 2024-01-05
Soli │ Telefon │ 2024-02-28 ← faqat 1 ta buyurtmasi bor
Nima bo'ldi:
Ali uchun: subquery ishladi → f.id = 1 → oxirgi 2 ta → Monitor, Klaviatura
Vali uchun: subquery ishladi → f.id = 2 → oxirgi 2 ta → Mikrofon, Headset
Soli uchun: subquery ishladi → f.id = 3 → oxirgi 1 ta → Telefon
Har bir foydalanuvchi uchun subquery alohida ishladi. Bu LATERAL ning mohiyati.
ON true — LATERAL bilan join sharti har doim rost, filtr subquery ichida.
LATERAL qachon kerak?
Uch holat bor, LATERAL yaxshi yechim bo'lgan:
1. Har bir qatordan N ta olish
Har bir kategoriyadan eng qimmat 3 ta mahsulot:
SELECT k.nomi, m.mahsulot, m.narx
FROM kategoriyalar k
LEFT JOIN LATERAL (
SELECT mahsulot, narx
FROM mahsulotlar
WHERE kategoriya_id = k.id
ORDER BY narx DESC
LIMIT 3
) m ON true;
Bu — LATERAL ning klassik ishlatilishi. Oddiy JOIN + subquery bilan hal qilib bo'lmaydi.
2. Subqueryda hisoblash, natijani bir nechta ustunda ko'rsatish
Foydalanuvchi statistikasi — buyurtmalar soni va umumiy summa:
SELECT
f.ism,
stats.soni,
stats.jami
FROM foydalanuvchilar f
LEFT JOIN LATERAL (
SELECT
COUNT(*) AS soni,
SUM(summa) AS jami
FROM buyurtmalar
WHERE foydalanuvchi_id = f.id
) stats ON true;
ism │ soni │ jami
-------+------+---------
Ali │ 4 │ 8650000
Vali │ 2 │ 950000
Soli │ 1 │ 450000
Bu SELECT ichidagi correlated subquery bilan ham qilsa bo'lardi — lekin har ustun uchun alohida subquery kerak bo'lardi. LATERAL bilan bir subquery, bir nechta ustun.
3. Funksiya bilan
PostgreSQL funksiyasi bir nechta qator qaytarsa — LATERAL bilan ishlatiladi:
SELECT f.ism, t.teg
FROM foydalanuvchilar f
LEFT JOIN LATERAL unnest(f.teglar) AS t(teg) ON true;
-- unnest — massivni qatorlarga yoyadi
LATERAL va correlated subquery farqi
Ikkalasi ham tashqi jadvalni ko'radi. Farq qayerda?
Correlated subquery: LATERAL:
────────────────── ──────────────────────
SELECT ichida FROM / JOIN ichida
Bitta qiymat qaytaradi Ko'p qator qaytarishi mumkin
Har qator uchun ishlaydi Har qator uchun ishlaydi
Agar subquery bir nechta qator qaytarishi kerak bo'lsa — faqat LATERAL ishlaydi.
Tez-tez uchraydigan xatolar
LATERAL kalit so'zini unutish:
-- ❌ — tashqi jadvalni ko'ra olmaydi
SELECT f.ism, b.mahsulot
FROM foydalanuvchilar f
JOIN (
SELECT * FROM buyurtmalar WHERE foydalanuvchi_id = f.id LIMIT 2
) b ON true;
-- ERROR: invalid reference to FROM-clause entry for table "f"
-- ✅
JOIN LATERAL (
SELECT * FROM buyurtmalar WHERE foydalanuvchi_id = f.id LIMIT 2
) b ON true;
JOIN o'rniga LEFT JOIN kerak bo'lganda:
-- ❌ — buyurtmasi yo'q foydalanuvchilar (Soli) tushib qoladi
JOIN LATERAL (...) b ON true;
-- ✅ — hamma foydalanuvchilar, buyurtmasi yo'q bo'lsa NULL
LEFT JOIN LATERAL (...) b ON true;
LATERAL subquery hech narsa qaytarmasa — JOIN bilan o'sha qator natijaga tushmaydi. LEFT JOIN bilan qoladi, ustunlar NULL bo'ladi.
Xulosa
LATERAL — subquery ga tashqi jadvalning har bir qatorini ko'rish imkonini beradi. Har bir qator uchun subquery alohida ishlaydi.
Oddiy JOIN: subquery bir marta, tashqi jadval ko'rinmaydi
LATERAL JOIN: subquery har qator uchun, tashqi jadval ko'rinadi
Qachon kerak:
- Har bir qatordan N ta olish (
LIMITbilan) - Subqueryda hisoblash, natijani bir nechta ustunda chiqarish
- Set-returning funksiyalar (
unnest,generate_series)
LEFT JOIN LATERAL ... ON true — standart LATERAL pattern.
Keyingi dars
22-dars: JSONB — saqlash, o'qish, yangilash, operatorlar