JSONB — saqlash, o'qish, yangilash, operatorlar

~25 daq · PostgreSQL · PostgreSQL Maxsus Imkoniyatlari

Muammodan boshlaylik

Mahsulotlar katalogi yozyapsiz. Har bir mahsulotning o'lchamlari, ranglari, texnik xususiyatlari bor. Lekin har xil mahsulot har xil xususiyatlarga ega — televizorning "ekran o'lchami" bor, oyoq kiyimning "o'lcham jadval" bor, kitobning "sahifalar soni" bor.

Har birini alohida ustun qilish mumkin emas — 50 ta ustun, ko'pchiligi NULL. Alohida xususiyatlar jadvali ham murakkab — har bir mahsulot uchun 10-20 ta qator.

Yechim: xususiyatlarni JSON formatida bitta ustunda saqlash. PostgreSQL ning JSONB turi — ana shu uchun.


JSON va JSONB — farqi nima?

PostgreSQL da ikkita JSON turi bor:

JSON                        JSONB
────────────────────        ────────────────────────────────
Matn sifatida saqlaydi      Binary formatda saqlaydi
Yozish tezroq               O'qish va qidirish tezroq
Tartib saqlanadi            Tartib saqlanmaydi, takror kalitlar o'chadi
Index qo'yib bo'lmaydi      GIN index qo'yish mumkin

Amalda deyarli har doim JSONB ishlatiladi. JSON faqat asl yozuvni (tartib, takrorlar) saqlash zarur bo'lganda.


JSONB ustun yaratish

CREATE TABLE mahsulotlar (
  id           SERIAL PRIMARY KEY,
  nomi         TEXT NOT NULL,
  narx         NUMERIC NOT NULL,
  xususiyatlar JSONB          -- ← JSONB ustun
);

Ma'lumot qo'shish

JSONB ga oddiy JSON qiymati yoziladi:

INSERT INTO mahsulotlar (nomi, narx, xususiyatlar) VALUES
(
  'Noutbuk Pro',
  8500000,
  '{"brend": "Dell", "ram": 16, "ssd": 512, "ranglar": ["qora", "kumush"], "ekran": {"o_lcham": 15.6, "razreshenie": "1920x1080"}}'
),
(
  'Simsiz Quloqchin',
  1200000,
  '{"brend": "Sony", "battery": 30, "ranglar": ["qora", "oq", "ko_k"], "noise_cancelling": true}'
),
(
  'Mexanik Klaviatura',
  950000,
  '{"brend": "Keychron", "switch": "Red", "backlight": true, "ranglar": ["qora"]}'
);

O'qish operatorlari

JSONB dan qiymat olish uchun ikkita asosiy operator:

->   kalit yoki indeks bo'yicha — JSONB qaytaradi
->>  kalit yoki indeks bo'yicha — TEXT qaytaradi

Vizual:

xususiyatlar -> 'brend'    → "Dell"    (JSONB, qo'shtirnoq bilan)
xususiyatlar ->> 'brend'   →  Dell     (TEXT, oddiy matn)

Asosiy qiymat olish

SELECT
  nomi,
  xususiyatlar -> 'brend'    AS brend_jsonb,
  xususiyatlar ->> 'brend'   AS brend_text,
  xususiyatlar ->> 'ram'     AS ram
FROM mahsulotlar;
       nomi        │ brend_jsonb │ brend_text │  ram
-------------------+-------------+------------+------
 Noutbuk Pro       │ "Dell"      │ Dell       │ 16
 Simsiz Quloqchin  │ "Sony"      │ Sony       │ NULL
 Mexanik Klaviatura│ "Keychron"  │ Keychron   │ NULL

Quloqchin da ram yo'q — NULL qaytadi, xato bermaydi.

Ichma-ich (nested) qiymat olish

-> larni zanjirlab yozish mumkin:

SELECT
  nomi,
  xususiyatlar -> 'ekran' ->> 'o_lcham' AS ekran_olcham
FROM mahsulotlar;
       nomi        │ ekran_olcham
-------------------+--------------
 Noutbuk Pro       │ 15.6
 Simsiz Quloqchin  │ NULL
 Mexanik Klaviatura│ NULL

Massiv elementini olish

SELECT
  nomi,
  xususiyatlar -> 'ranglar' -> 0    AS birinchi_rang_jsonb,
  xususiyatlar -> 'ranglar' ->> 0   AS birinchi_rang_text
FROM mahsulotlar;
       nomi        │ birinchi_rang_jsonb │ birinchi_rang_text
-------------------+---------------------+--------------------
 Noutbuk Pro       │ "qora"              │ qora
 Simsiz Quloqchin  │ "qora"              │ qora
 Mexanik Klaviatura│ "qora"              │ qora

#> va #>> — yo'l bilan olish

Chuqur nested qiymat uchun yo'lni massiv sifatida berib, bir qadam bilan olish:

SELECT
  nomi,
  xususiyatlar #>> '{ekran, o_lcham}' AS ekran_olcham
FROM mahsulotlar;

{ekran, o_lcham} — avval ekran kalitiga, keyin o_lcham kalitiga tush.


Filtrlash

Aniq qiymatga teng

-- brendu Dell bo'lgan mahsulotlar
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar ->> 'brend' = 'Dell';

@> — ichida bor operatori (contains)

JSONB ning eng kuchli operatori. Qiymat ichida berilgan tuzilma bormi yo'qmi tekshiradi:

-- noise_cancelling: true bo'lgan mahsulotlar
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar @> '{"noise_cancelling": true}';
       nomi
------------------
 Simsiz Quloqchin
-- ranglar ichida "qora" bor mahsulotlar
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar @> '{"ranglar": ["qora"]}';
       nomi
--------------------
 Noutbuk Pro
 Simsiz Quloqchin
 Mexanik Klaviatura

@> — GIN index bilan juda tez ishlaydi. Bu operatorsiz qidirish katta jadvalda sekin.

? — kalit bormi

-- "battery" kaliti bor mahsulotlar
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar ? 'battery';
       nomi
------------------
 Simsiz Quloqchin
-- "battery" yoki "ssd" kalitlaridan biri bor mahsulotlar (?| — birortasi)
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar ?| ARRAY['battery', 'ssd'];

-- "battery" va "noise_cancelling" ikkalasi ham bor (?& — hammasi)
SELECT nomi FROM mahsulotlar
WHERE xususiyatlar ?& ARRAY['battery', 'noise_cancelling'];

Yangilash

Bitta kalit qiymatini o'zgartirish — ||

|| operatori ikki JSONB ni birlashtiradi. Bir xil kalit bo'lsa — o'ngdagisi ustunlik qiladi:

UPDATE mahsulotlar
SET xususiyatlar = xususiyatlar || '{"ram": 32}'
WHERE nomi = 'Noutbuk Pro';
-- ram: 16 → 32 ga o'zgaradi

jsonb_set — aniq kalit yangilash

UPDATE mahsulotlar
SET xususiyatlar = jsonb_set(
  xususiyatlar,      -- ← asl qiymat
  '{ram}',           -- ← qaysi kalit
  '32'               -- ← yangi qiymat (JSONB sifatida)
)
WHERE nomi = 'Noutbuk Pro';

Nested qiymat:

UPDATE mahsulotlar
SET xususiyatlar = jsonb_set(
  xususiyatlar,
  '{ekran, o_lcham}',
  '17.3'
)
WHERE nomi = 'Noutbuk Pro';

Kalit o'chirish — -

UPDATE mahsulotlar
SET xususiyatlar = xususiyatlar - 'battery'
WHERE nomi = 'Simsiz Quloqchin';

Foydali funksiyalar

-- Barcha kalitlarni ro'yxat sifatida
SELECT jsonb_object_keys(xususiyatlar) FROM mahsulotlar WHERE id = 1;

-- JSONB ni jadval ko'rinishiga yoyish
SELECT * FROM jsonb_each_text(
  '{"brend": "Dell", "ram": "16", "ssd": "512"}'
);
-- kalit │ qiymat
-- ------+-------
-- brend │ Dell
-- ram   │ 16
-- ssd   │ 512

-- Massivdagi elementlar sonini hisoblash
SELECT jsonb_array_length(xususiyatlar -> 'ranglar')
FROM mahsulotlar;

Tez-tez uchraydigan xatolar

-> va ->> ni aralashtirib yuborish:

-- ❌ — -> TEXT bilan taqqoslab bo'lmaydi
WHERE xususiyatlar -> 'brend' = 'Dell';
-- ERROR: operator does not exist: jsonb = text

-- ✅ — ->> TEXT qaytaradi
WHERE xususiyatlar ->> 'brend' = 'Dell';

-> JSONB qaytaradi — "Dell" (qo'shtirnoq bilan). ->> TEXT qaytaradi — Dell. Oddiy matn bilan taqqoslashda ->> ishlatiladi.

Raqamli qiymatni TEXT sifatida olish:

-- ❌ — ->> TEXT qaytaradi, raqam sifatida taqqoslab bo'lmaydi
WHERE xususiyatlar ->> 'ram' > 8;
-- ERROR: operator does not exist: text > integer

-- ✅ — CAST qilish kerak
WHERE (xususiyatlar ->> 'ram')::INT > 8;

Xulosa

JSONB — PostgreSQL da tuzilmasi o'zgaruvchan ma'lumotlarni saqlash uchun.

->    kalit bo'yicha JSONB olish
->>   kalit bo'yicha TEXT olish
#>>   yo'l bo'yicha TEXT olish
@>    ichida tuzilma bormi (GIN index bilan tez)
?     kalit bormi
||    birlashtirish / kalit yangilash
-     kalit o'chirish

JSONB har bir qator uchun har xil tuzilmaga ruxsat beradi — relatsion jadvallarning qattiqligi va JSON ning moslashuvchanligi birga.


Keyingi dars

23-dars: JSONB — GIN index, so'rovlarni tezlashtirish