LATERAL JOIN — nima, qachon kerak

~20 daq · PostgreSQL · Ilg'or SQL

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 (LIMIT bilan)
  • 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