Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Massivlar
- 2.1 Massiv ustuni
- 2.2 Massiv ichidan qidirish
- 2.3 Massivni qatorlarga yoyish: unnest
- 2.4 O'zgartirish va massivning chegaralari
- 3. Range: vaqt oraliqlari
- 3.1 Bron tizimi
- 3.2 Oraliq bilan ishlash
- 3.3 Kanonik shakl, cheksizlik va bo'sh oraliq
- 3.4 Multirange: bo'sh vaqtni topish
- 4. DOMAIN: qoidasi bilan tur
- 4.1 Bir marta yozib, hamma joyda ishlatish
- 4.2 Tuzoq: domendagi NOT NULL
- 4.3 Qachon DOMAIN
- 5. ENUM, composite tur va citext
- 5.1 ENUM: tartibli ro'yxat
- 5.2 Composite tur
- 5.3 citext: registrga befarq matn
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson)
- 2-mashq (o'rta)
- 3-mashq (qiyin)
- 8. Loyiha qadami
- 9. Real ishda
- Xulosa
- Manbalar
PostgreSQL massivlar, range, DOMAIN va maxsus turlar: boy tur tizimi
Qisqacha: Postgres'da
intvatextdan tashqari ko'p tur bor: massiv (text[]), vaqt oralig'i (tstzrange), o'z qoidasi bilan tur (DOMAIN), sanoqli ro'yxat (ENUM), bir nechta maydonli tur (composite) va registrga befarq matn (citext). Ular ilova kodidagi tekshiruv va hisobning bir qismini bazaga oladi: masalan, kesishadigan bronni baza o'zi rad etadi, noto'g'ri email esa hech qaysi jadvalga kirmaydi.
Bu darsda
- Massiv bilan ishlaysiz: indeks 1 dan,
ANY,@>,&&,unnest,array_aggvacardinalitytuzog'i. - Vaqt oraliqlarini (
tstzrange,daterange) saqlaysiz,[)chegarasini va&&kesishishni tushuntirasiz. - Multirange bilan "qaysi vaqtlar bo'sh?" savoliga bitta so'rov bilan javob berasiz.
DOMAINbilan qayta ishlatiladigan tur yaratasiz va uningNOT NULLtuzog'ini bilasiz.ENUM, composite tur vacitextqachon kerakligini aytasiz.
Oldin bilishingiz kerak: JSONB, PostgreSQL ma'lumot turlari, Modellash naqshlari, Indeks turlari: GIN, GiST, BRIN.
1. Nega bu kerak?
O'tgan darsda JSONB'ni ko'rdik — ustun ichida butun hujjat. Bugun boshqa tomon: Postgres'ning tur tizimi. Uch vaziyatni olaylik.
Birinchi: Toshkentdagi o'quv markazi xonalarni bron qiladi. Ikki o'qituvchi bir xonaga 09:30 dan bron qilmasin. Ilovada "avval tekshir, keyin yoz" desangiz — ikki so'rov bir vaqtda kelganda ikkalasi ham "bo'sh" deb ko'radi (poyga holati). Bazada esa vaqt oralig'i turi va istisno cheklovi bor — u ikkinchisini o'zi rad etadi.
Ikkinchi: email foydalanuvchilar, izohlar, buyurtmalar jadvallarida bor. Har birida bir xil CHECK (email ~ …) yozasizmi? Bitta joyda unutildi — noto'g'ri email kirdi. DOMAIN — qoidasi bilan birga tur: bir marta yozasiz, hamma joyda ishlatasiz.
Uchinchi: "Aziz@Example.COM va aziz@example.com — bitta odam" degan qoida. citext turi buni o'zi biladi.
O'xshatish: ombordagi qutilar. Oddiy karton quti — text: ichiga nima solsangiz ketaveradi. Tuxum qutisi esa faqat tuxumga mo'ljallangan, har tuxumning o'z uyasi bor — noto'g'ri narsa sig'maydi. Maxsus turlar — tuxum qutisi: shakli ma'lumotning o'zini himoya qiladi.
Bu dars "faqat tajriba" darsi: misollar alohida jadvallarda, vazifalar sxemasi o'zgarmaydi. Nega — «Loyiha qadami» bo'limida.
2. Massivlar
2.1 Massiv ustuni
Ma'lumot turlari va Modellash naqshlari darslarida massivni ko'rdik: text[] — matnlar ro'yxati, teglar uchun. Kanon qarori: teglar — alohida jadval. Bu yerda muqobilni chuqurroq o'rganamiz. Bu darsdagi so'rovlarni o'z PostgreSQL 17.6 klasterimizda ishga tushirdik — holat 01 bazasida, lekin faqat yangi tajriba jadvallari bilan (kanon tajribasi (IlmHamroh, 2026-10, PostgreSQL 18.6) bilan mos; farq bo'lsa — aytamiz):
\pset null '(null)'
CREATE TABLE m_vazifa (
id int PRIMARY KEY,
matn text,
teglar text[] NOT NULL DEFAULT '{}'
);
INSERT INTO m_vazifa VALUES
(1, 'Non olish', '{#bozor,#uy}'),
(2, 'Hisobot', '{#ish}'),
(3, 'Yugurish', '{}'),
(4, 'To''lov', ARRAY['#uy', '#to''lov']);
SELECT id, teglar, teglar[1] AS birinchi, teglar[5] AS yoq,
cardinality(teglar) AS soni,
array_length(teglar, 1) AS uzunlik
FROM m_vazifa ORDER BY id;Null display is "(null)".
CREATE TABLE
INSERT 0 4
id | teglar | birinchi | yoq | soni | uzunlik
----+---------------+----------+--------+------+---------
1 | {#bozor,#uy} | #bozor | (null) | 2 | 2
2 | {#ish} | #ish | (null) | 1 | 1
3 | {} | (null) | (null) | 0 | (null)
4 | {#uy,#to'lov} | #uy | (null) | 2 | 2
(4 rows)Massivni ikki xil yozish mumkin: matn '{#bozor,#uy}' yoki ARRAY['#uy', …]. Ikkinchisi xavfsizroq — ichidagi vergul va qo'shtirnoqlarni o'ylash shart emas.
Uchta tuzoq bitta jadvalda:
- Indeks 1 dan boshlanadi.
teglar[1]— birinchi element. JS va JSONB'da 0 dan edi. - Chegaradan tashqari — xato emas,
NULL.teglar[5]hech qayerda yo'q, lekin xato chiqmadi. - Bo'sh massivda
array_length—NULL,cardinality— 0. "Tegsiz vazifalarni top" uchunarray_length(teglar, 1) = 0yozsangiz — hech narsa topilmaydi (NULL = 0—NULL, NULL mantiqi).cardinality(teglar) = 0yokiteglar = '{}'to'g'ri.
Tekshirib ko'ring:
array_length(teglar, 1)dagi1nima?
Javob
O'lcham (dimension) raqami. Postgres massivi ko'p o'lchamli bo'lishi mumkin (jadval kabi int[][]), 1 — birinchi o'lcham bo'yicha uzunlik. Bir o'lchamli ro'yxat uchun cardinality qulayroq: argumenti bitta va bo'shga 0 qaytaradi.
2.2 Massiv ichidan qidirish
SELECT id FROM m_vazifa WHERE '#uy' = ANY (teglar) ORDER BY id;
SELECT id FROM m_vazifa WHERE teglar @> '{#uy,#bozor}';
SELECT id FROM m_vazifa WHERE teglar && '{#ish,#to''lov}'
ORDER BY id; id
----
1
4
(2 rows)
id
----
1
(1 row)
id
----
2
4
(2 rows)| Ifoda | Savol |
|---|---|
'#uy' = ANY (teglar) |
Massivda shu element bormi? |
teglar @> '{#uy,#bozor}' |
Ro'yxatdagilarning hammasi bormi? (tartib muhim emas) |
teglar && '{#ish,#to''lov}' |
Kamida bittasi bormi? (kesishadimi) |
@> va && ni GIN indeksi qo'llaydi (Indeks turlari). = ANY esa Node kodida ko'p uchraydi: WHERE id = ANY($1) — bitta parametrga butun massivni berib, N ta so'rov o'rniga bitta yuborasiz (N+1 muammosi):
SELECT 3 = ANY ('{3,5,7}'::int[]) AS bor,
ARRAY[1,2] = ARRAY[2,1] AS teng,
ARRAY[1,2] @> ARRAY[2,1] AS ichida; bor | teng | ichida
-----+------+--------
t | f | t
(1 row)= massivlarni tartibi bilan solishtiradi: {1,2} va {2,1} — teng emas. @> esa to'plam kabi: tartibga qaramaydi.
2.3 Massivni qatorlarga yoyish: unnest
Statistika uchun massivni "yoyish" kerak: har element alohida qator. unnest shuni qiladi:
SELECT t, count(*) FROM m_vazifa, unnest(teglar) t
GROUP BY t ORDER BY 2 DESC, 1;
SELECT id, t, n
FROM m_vazifa, unnest(teglar) WITH ORDINALITY AS u(t, n)
WHERE id = 1; t | count
---------+-------
#uy | 2
#bozor | 1
#ish | 1
#to'lov | 1
(4 rows)
id | t | n
----+--------+---
1 | #bozor | 1
1 | #uy | 2
(2 rows)WITH ORDINALITY — har elementga uning o'rnini (1, 2, …) qo'shadi. Teskari amal — array_agg: qatorlarni massivga yig'adi (1-mashqda ishlatamiz).
Bo'sh massivli 3-vazifa birinchi natijada umuman yo'q: unnest('{}') nol qator beradi, m_vazifa, unnest(…) esa ichki birlashtirish kabi ishlaydi. Tegsiz vazifalarni ham ko'rish kerak bo'lsa — LEFT JOIN LATERAL unnest(teglar) t ON true (Subquery va LATERAL).
2.4 O'zgartirish va massivning chegaralari
BEGIN;
UPDATE m_vazifa
SET teglar = array_append(teglar, '#shoshilinch')
WHERE id = 2 RETURNING teglar;
UPDATE m_vazifa SET teglar = array_remove(teglar, '#uy')
WHERE id = 4 RETURNING teglar;
INSERT INTO m_vazifa VALUES (5, 'NULL teg', ARRAY[NULL]::text[])
RETURNING teglar, cardinality(teglar);
ROLLBACK;BEGIN
teglar
---------------------
{#ish,#shoshilinch}
(1 row)
UPDATE 1
teglar
-----------
{#to'lov}
(1 row)
UPDATE 1
teglar | cardinality
--------+-------------
{NULL} | 1
(1 row)
INSERT 0 1
ROLLBACKarray_append — oxiriga qo'shadi, array_remove — shu qiymatli hamma elementlarni olib tashlaydi. Oxirgi INSERT — massiv ichida NULL element: {NULL}, uzunligi 1. NOT NULL cheklovi faqat massivning o'ziga tegishli, ichidagi elementlarga emas.
Massivning boshqa chegarasi: elementga tashqi kalit qo'yib bo'lmaydi. teglar ichidagi #uy hech qaysi teglar jadvali qatoriga bog'lanmaydi — teg o'chirilsa, massivda "yetim" qoladi. Kanon teglarni ko'prik jadvalda saqlashining asosiy sababi shu (Modellash naqshlari).
Tekshirib ko'ring: Massivda takror elementlarni qanday taqiqlash mumkin? Masalan,
{#uy,#uy}kirmasin.
Javob
Massiv ustunida UNIQUE elementlar uchun ishlamaydi — u butun massivni solishtiradi. Bitta yo'l — CHECK: cardinality(teglar) = (SELECT count(DISTINCT x) FROM unnest(teglar) x)... lekin CHECK ichida ichki so'rov (subquery) taqiqlangan. Shuning uchun bunday qoida yo IMMUTABLE funksiyaga o'raladi, yo ilovada tekshiriladi, yo — eng to'g'risi — ko'prik jadvalda PRIMARY KEY (vazifa_id, teg_id) bilan. Bu massivning yana bir chegarasi.
3. Range: vaqt oraliqlari
3.1 Bron tizimi
Range (oraliq) — bitta qiymatda boshi va oxiri: "27-oktabr, 09:00 dan 10:00 gacha". Ikki ustun (boshlanish, tugash) o'rniga bitta. Afzalligi — oraliqlar uchun tayyor operatorlar va kesishishni taqiqlaydigan cheklov. Indeks turlari darsida EXCLUDE bilan bronni ko'rgan edik; endi tafsilotlariga kiramiz:
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE bron (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
xona text NOT NULL,
vaqt tstzrange NOT NULL,
EXCLUDE USING gist (xona WITH =, vaqt WITH &&)
);
INSERT INTO bron (xona, vaqt) VALUES
('A', '[2026-10-27 09:00+05, 2026-10-27 10:00+05)'),
('A', '[2026-10-27 10:00+05, 2026-10-27 11:00+05)');
INSERT INTO bron (xona, vaqt) VALUES
('A', '[2026-10-27 09:30+05, 2026-10-27 10:30+05)');CREATE EXTENSION
CREATE TABLE
INSERT 0 2
ERROR: conflicting key value violates exclusion constraint "bron_xona_vaqt_excl"
DETAIL: Key (xona, vaqt)=(A, ["2026-10-27 04:30:00+00","2026-10-27 05:30:00+00")) conflicts with existing key (xona, vaqt)=(A, ["2026-10-27 04:00:00+00","2026-10-27 05:00:00+00")).Qatorma-qator:
tstzrange— vaqt zonasi bilan vaqt (timestamptz) oralig'i. Sana uchun —daterange, butun son uchun —int4range, pul/son uchun —numrange.[— boshi kiradi,)— oxiri kirmaydi. Shuning uchun 09:00–10:00 va 10:00–11:00 kesishmaydi: 10:00 birinchisiga tegishli emas. Ikkala bron qabul qilindi.EXCLUDE USING gist (xona WITH =, vaqt WITH &&)— "xonasi teng va vaqti kesishadigan ikki qator bo'lmasin".&&— kesishish operatori.btree_gist— oddiy=ni GiST indeksi ichida ishlatish uchun kengaytma (extension). Kengaytmalarni Extension'lar darsida to'liq o'rganamiz.
Uchinchi bron (09:30–10:30) ikkalasi bilan kesishadi — rad etildi. Xato kodi 23P01 (exclusion_violation). DETAIL da vaqtlar UTC'da: sessiyamiz zonasi UTC, 09:30+05 — 04:30+00.
Rasmda — xuddi shu uch bron. Nimaga qarang: 1- va 2-bron bir-biriga tegib turadi, lekin ustma-ust emas; 3-bron ikkalasini kesadi:
gantt
title A xona, 27-oktabr
dateFormat HH:mm
axisFormat %H:%M
section Qabul qilindi
1-bron :a1, 09:00, 60m
2-bron :a2, 10:00, 60m
section Rad etildi
3-bron :crit, a3, 09:30, 60mMaslahat: Har doim
[)ishlating. Bu yarim ochiq oraliq — algoritmlar qismida ko'rgan qoida: ketma-ket oraliqlar orasida bo'shliq ham, ustma-ust tushish ham bo'lmaydi. Postgres'datstzrange(a, b)funksiyasi ham sukut bo'yicha[)yasaydi.
3.2 Oraliq bilan ishlash
SET TIME ZONE 'Asia/Tashkent';
SELECT id, lower(vaqt) AS boshi,
upper(vaqt) - lower(vaqt) AS davomiylik,
vaqt @> '2026-10-27 10:00+05'::timestamptz AS soat10
FROM bron ORDER BY id;
RESET TIME ZONE;SET
id | boshi | davomiylik | soat10
----+------------------------+------------+--------
1 | 2026-10-27 09:00:00+05 | 01:00:00 | f
2 | 2026-10-27 10:00:00+05 | 01:00:00 | t
(2 rows)
RESETlower / upper — boshi va oxiri. @> — "oraliq shu nuqtani o'z ichiga oladimi": soat 10:00 — 2-bronda (t), 1-bronda emas (f), chunki ). SET TIME ZONE faqat ko'rinishni o'zgartiradi — saqlangan qiymat bir xil (Sana va vaqt zonalari).
Boshqa operatorlar — butun sonli oraliqda ko'rish oson:
SELECT int4range(1, 5) && int4range(5, 8) AS kesishadi,
int4range(1, 5) -|- int4range(5, 8) AS yonma_yon,
int4range(1, 5) << int4range(5, 8) AS chapda,
int4range(2, 3) <@ int4range(1, 5) AS ichida; kesishadi | yonma_yon | chapda | ichida
-----------+-----------+--------+--------
f | t | t | t
(1 row)| Operator | Ma'nosi |
|---|---|
&& |
Kesishadimi (umumiy nuqta bormi) |
-|- |
Yonma-yon (biri tugagan joyda ikkinchisi boshlanadi) |
<< / >> |
To'liq chapda / o'ngda |
@> / <@ |
O'z ichiga oladi / ichida |
[1,5) va [5,8) — kesishmaydi, lekin yonma-yon. "Bron orasida bo'shliq qolmasin" kabi qoidalarda -|- qo'l keladi.
3.3 Kanonik shakl, cheksizlik va bo'sh oraliq
SELECT '[2026-10-01, 2026-10-31]'::daterange AS oktabr,
'[2026-10-01, 2026-10-31]'::daterange
= '[2026-10-01, 2026-11-01)'::daterange AS teng;
SELECT tstzrange('2026-10-27 09:00+05', NULL) AS cheksiz,
isempty('[5,5)'::int4range) AS bosh; oktabr | teng
-------------------------+------
[2026-10-01,2026-11-01) | t
(1 row)
cheksiz | bosh
-----------------------------+------
["2026-10-27 04:00:00+00",) | t
(1 row)Uch narsa:
- Kanonik shakl. Sana va butun son "sanoqli" — 31-oktabrdan keyingi kun aniq: 1-noyabr. Shuning uchun Postgres
[1, 31]ni o'zi[1, 32)ko'rinishiga keltiradi va ikkalasi teng.tstzrangeda bunday yo'q: vaqtda "keyingi lahza" aniqlanmagan. - Cheksiz chegara.
NULL— "chegarasi yo'q":["… 04:00:00+00",)— "shu paytdan (Toshkentda 09:00) boshlab, tugashi noma'lum". Vergul ortida hech narsa yo'q. Muddatsiz obuna, tugamagan ijara. (Bu blokdaSET TIME ZONEyo'q — vaqt sessiya zonasida, UTC'da chiqdi.) - Bo'sh oraliq.
[5,5)— hech qanday nuqta yo'q (isempty—t). Bo'sh oraliq hech narsa bilan kesishmaydi — buEXCLUDEdan "yashirinib o'tish" yo'li bo'lishi mumkin; kerak bo'lsaCHECK (NOT isempty(vaqt))qo'shing.
3.4 Multirange: bo'sh vaqtni topish
PostgreSQL 14 dan multirange bor — bir nechta oraliqlar to'plami. "A xona 08:00–12:00 orasida qachon bo'sh?" — oldin bu murakkab so'rov edi. Endi:
SET TIME ZONE 'Asia/Tashkent';
SELECT range_agg(vaqt) AS band,
tstzmultirange(tstzrange('2026-10-27 08:00+05',
'2026-10-27 12:00+05'))
- range_agg(vaqt) AS bosh
FROM bron WHERE xona = 'A';
RESET TIME ZONE;SET
band | bosh
-------------------------------------------------------+-----------------------------------------------------------------------------------------------------------
{["2026-10-27 09:00:00+05","2026-10-27 11:00:00+05")} | {["2026-10-27 08:00:00+05","2026-10-27 09:00:00+05"),["2026-10-27 11:00:00+05","2026-10-27 12:00:00+05")}
(1 row)
RESETrange_agg— oraliqlarni bitta multirange'ga yig'adi va yonma-yonlarini birlashtiradi: 09–10 va 10–11 → bitta09:00–11:00.-— ayirish: "kun" dan "band" ni olib tashlasak, bo'sh qoladi: 08–09 va 11–12.
Bitta so'rov, sikl yo'q, ilova kodida oraliq hisob-kitobi yo'q. Shifokor navbati, taksi haydovchisining smenasi, xona jadvali — hammasida aynan shu savol.
Tekshirib ko'ring: B xonaga 09:00–10:00 bron qilish mumkinmi? A xonada shu vaqt band-ku.
Javob
Mumkin. Cheklov xona WITH = va vaqt WITH && — ikkalasi bir vaqtda bajarilsa rad etadi. Xona har xil — = bajarilmaydi, kesishish muhim emas. Shuning uchun btree_gist kerak edi: xona ustuni ham GiST indeksiga kiradi.
4. DOMAIN: qoidasi bilan tur
4.1 Bir marta yozib, hamma joyda ishlatish
DOMAIN — mavjud turga (masalan, text) qoida qo'shib yaratilgan yangi tur. Qoida tur bilan birga yuradi: shu turdagi har ustun, har o'zgaruvchi, har cast tekshiriladi:
CREATE DOMAIN email_manzil AS text
CHECK (VALUE ~ '^[^@[:space:]]+@[^@[:space:]]+$');
CREATE DOMAIN vazifa_matni AS text NOT NULL
CHECK (VALUE = btrim(VALUE)
AND char_length(VALUE) BETWEEN 1 AND 100);
CREATE TABLE f_domen (email email_manzil, matn vazifa_matni);
INSERT INTO f_domen VALUES ('aziz@example.com', 'Non olish');
INSERT INTO f_domen VALUES ('aziz.example.com', 'Non olish');
INSERT INTO f_domen VALUES ('aziz@example.com', ' bo''shliq ');
SELECT 'yomon'::email_manzil;CREATE DOMAIN
CREATE DOMAIN
CREATE TABLE
INSERT 0 1
ERROR: value for domain email_manzil violates check constraint "email_manzil_check"
ERROR: value for domain vazifa_matni violates check constraint "vazifa_matni_check"
ERROR: value for domain email_manzil violates check constraint "email_manzil_check"VALUE — tekshirilayotgan qiymat. Uchala xato bir xil manba: domen qoidasi — jadval cheklovi emas. Hatto oddiy SELECT 'yomon'::email_manzil ham — jadvalsiz — rad etildi. Bu CHECK'dan farqi: CHECK bitta jadvalni himoya qiladi, DOMAIN esa qiymatning o'zini.
vazifa_matni qoidasi — kanon vazifalar.matn CHECK'ining aynan nusxasi (CREATE TABLE va cheklovlar). DOMAIN bo'lganda, bir xil qoida izohlar, shablonlar va boshqa jadvallarda bir xil bo'lardi.
4.2 Tuzoq: domendagi NOT NULL
vazifa_matni da NOT NULL bor. Kutilmagan joyda ishlaydi:
INSERT INTO f_domen (email) VALUES ('b@example.com');ERROR: domain vazifa_matni does not allow null valuesBiz matn ga hech narsa yozmadik — sukut qiymat NULL. Domen uni rad etdi: "vazifa_matni domeni null qiymatga ruxsat bermaydi". Bu ko'pincha kutilgan xulq. Lekin hujjat ogohlantiradi: NOT NULL li domen ustuni ba'zi joylarda baribir NULL bo'lib chiqishi mumkin. Masalan, LEFT JOIN natijasida yoki hech qator qaytarmagan ichki so'rov qiymati yozilganda: INSERT INTO t (d) VALUES ((SELECT d FROM t WHERE false)) — biz sinadik, PG 17.6 bu NULL ni qabul qildi (postgresql.org/docs/18, "CREATE DOMAIN"). Hujjat tavsiyasi: NOT NULL ni domenda emas, ustunda yozing.
4.3 Qachon DOMAIN
| Holat | Tanlov |
|---|---|
| Qoida bitta jadvalda | Oddiy CHECK |
| Bir xil qoida ko'p jadvalda (email, telefon, summa) | DOMAIN |
| Qoida keyin o'zgarishi mumkin | DOMAIN — ALTER DOMAIN bir joyda (3-mashq) |
DOMAIN'ning noqulay tomoni — ORM'lar uni ko'pincha oddiy text deb ko'radi. Prisma va Drizzle darslarida buni eslatamiz.
5. ENUM, composite tur va citext
5.1 ENUM: tartibli ro'yxat
Pul, miqdor va enum'lar darsida vazifalar.ustuvorlik ni ENUM qilgan edik. Ikki muhim xususiyatini eslaymiz va bittasini qo'shamiz:
CREATE TYPE u AS ENUM ('past', 'orta', 'yuqori');
SELECT 'yuqori'::u > 'past'::u AS katta, enum_range(NULL::u);
ALTER TYPE u ADD VALUE 'shoshilinch' AFTER 'yuqori';
SELECT enum_range(NULL::u);
SELECT 'juda'::u;CREATE TYPE
katta | enum_range
-------+--------------------
t | {past,orta,yuqori}
(1 row)
ALTER TYPE
enum_range
--------------------------------
{past,orta,yuqori,shoshilinch}
(1 row)
ERROR: invalid input value for enum u: "juda"
LINE 1: SELECT 'juda'::u;
^- Tartib — e'lon tartibida, alifboda emas:
yuqori > past. - Yangi qiymat —
ALTER TYPE … ADD VALUE,BEFORE/AFTERbilan kerakli joyga. - Ro'yxatda yo'q qiymat —
invalid input value for enum u: "juda".
Qo'shimcha: ENUM'dan qiymatni olib tashlash buyrug'i yo'q (hujjat bo'yicha, "ALTER TYPE"). Qiymat ro'yxati tez-tez o'zgarsa — ENUM o'rniga ma'lumotnoma jadvali va tashqi kalit yaxshiroq.
5.2 Composite tur
Composite tur — bir nechta maydonli tur, jadval qatoriga o'xshash:
CREATE TYPE manzil AS (shahar text, kocha text, uy int);
SELECT ROW('Toshkent', 'Amir Temur', 15)::manzil AS m,
(ROW('Toshkent', 'Amir Temur', 15)::manzil).shahar;CREATE TYPE
m | shahar
----------------------------+----------
(Toshkent,"Amir Temur",15) | Toshkent
(1 row)Maydonga murojaat — (qiymat).maydon, qavs bilan (qavssiz Postgres qiymat ni jadval nomi deb o'ylaydi). Amalda composite tur ustun sifatida kam ishlatiladi — manzil uchun alohida ustunlar yoki jsonb qulayroq. Lekin u har joyda bor: har jadval avtomatik ravishda shu nomli composite tur ham bo'ladi. Funksiyalar qatorni qaytarganda aynan shu tur ishlatiladi (Funksiyalar va PL/pgSQL).
5.3 citext: registrga befarq matn
CREATE EXTENSION IF NOT EXISTS citext;
SELECT 'Aziz@Example.COM'::citext = 'aziz@example.com' AS citext,
'Aziz@Example.COM'::text = 'aziz@example.com' AS text_;
SELECT 'OʻZBEK'::citext = 'oʻzbek' AS ozbek,
'İstanbul'::citext = 'istanbul' AS turkcha;CREATE EXTENSION
citext | text_
--------+-------
t | f
(1 row)
ozbek | turkcha
-------+---------
t | f
(1 row)citext solishtirishdan oldin ikkala tomonni lower() qiladi. Aziz@Example.COM = aziz@example.com. O'zbekcha ʻ bilan ham ishladi.
Halol topilma: oxirgi ustun muhitga bog'liq. Biz f oldik (klasterimiz ICU en-US locale bilan), kanon konteynerida (PostgreSQL 18.6, boshqa locale) xuddi shu so'rov t berdi. Turkcha İ ning kichik harfi qaysi qoidaga ko'ra olinishi bazaning locale sozlamasiga bog'liq. Xulosa: citext — "registrga befarq", lekin "har tilda bir xil" emas.
Kanon foydalanuvchilar.email uchun citext emas, lower(email) bo'yicha noyob indeks tanlagan — kengaytmasiz va qaysi qoida ishlashi aniq. Ikkalasi ham to'g'ri yo'l; muhimi — bitta loyihada bittasi.
Tajriba tugadi — yaratganlarimizni o'chiramiz, baza mashqlar uchun toza qolsin:
DROP TABLE m_vazifa, bron, f_domen;
DROP DOMAIN email_manzil, vazifa_matni;
DROP TYPE manzil, u;
DROP EXTENSION btree_gist, citext;DROP TABLE
DROP DOMAIN
DROP TYPE
DROP EXTENSION6. Ko'p uchraydigan xatolar
1. array_length(x, 1) = 0 bilan bo'sh massivni qidirish. Natija bo'sh — bo'sh massivda array_length NULL. To'g'risi: cardinality(x) = 0.
2. Massiv indeksini 0 dan boshlash. teglar[0] — NULL, xato yo'q. Postgres massivi 1 dan; JSONB massivi 0 dan.
3. Oraliqni ikki ustunda saqlab, kesishishni ilovada tekshirish. Poyga holatida ikkala bron o'tadi. To'g'risi: tstzrange + EXCLUDE USING gist.
4. [] (ikkala chegara kiradi) ishlatish. 09:00–10:00 va 10:00–11:00 kesishib qoladi — ketma-ket bronlar rad etiladi. To'g'risi: [).
5. btree_gist siz EXCLUDE (xona WITH =, …). Postgres text uchun GiST operatorini topa olmaydi va xato beradi. To'g'risi: CREATE EXTENSION btree_gist.
6. Tez-tez o'zgaradigan ro'yxat uchun ENUM. Qiymatni olib tashlab bo'lmaydi. To'g'risi: ma'lumotnoma jadvali.
7. Domenda NOT NULL. Ba'zi holatlarda baribir NULL uchraydi. To'g'risi: NOT NULL — ustunda, domenda faqat CHECK.
7. Mashqlar
Mashqlar kurs/mashqlar/27/02-turlar/ da. Holat 01 bazasida; yangi jadval yaratadiganlari BEGIN … ROLLBACK ichida. Yechimlar tekshirilgan (biz ishga tushirdik, PostgreSQL 17.6).
1-mashq (oson)
vazifalar ning teglari ko'prik jadvalda (vazifa_teglari + teglar). Har vazifaning teglarini massivga yig'ing va #ish hamda #to'lov teglari ikkalasi bor vazifalarni chiqaring: id va teglar massivi (alifbo tartibida). Ishora: array_agg(t.nom ORDER BY t.nom) + GROUP BY (Agregatlar), shart guruhga — HAVING; "hammasi bor" — @>.
Yechim
SELECT v.id, array_agg(t.nom ORDER BY t.nom) AS teglar
FROM vazifalar v
JOIN vazifa_teglari vt ON vt.vazifa_id = v.id
JOIN teglar t ON t.id = vt.teg_id
GROUP BY v.id
HAVING array_agg(t.nom) @> ARRAY['#ish', '#to''lov']
ORDER BY v.id; id | teglar
----+----------------
32 | {#ish,#to'lov}
(1 row)Bitta vazifa — 32 (soliq hisoboti). array_agg ko'prik jadvaldagi qatorlarni massivga yig'di — jadvaldan massivga o'tish. API ko'pincha aynan shunday qiladi: teglar ko'prik jadvalda saqlanadi, javobga esa massiv bo'lib chiqadi.
2-mashq (o'rta)
Shifokor navbati: navbat jadvali (shifokor text, vaqt tstzrange), bir shifokorda kesishadigan navbat bo'lmasin. Karimov shifokorga 09:00–09:30 va 10:00–11:00 navbat yozing, keyin 10:30–11:30 ni yozishga urinib ko'ring. Oxirida Karimovning 09:00–12:00 orasidagi bo'sh vaqtlarini chiqaring. Hammasi BEGIN … ROLLBACK ichida, vaqtlar +05, ko'rinish Asia/Tashkent da. Ishora: «Bron tizimi» va «Multirange» bo'limlari; SET LOCAL TIME ZONE — faqat shu tranzaksiyada.
Yechim
BEGIN;
SET LOCAL TIME ZONE 'Asia/Tashkent';
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE navbat (
shifokor text NOT NULL,
vaqt tstzrange NOT NULL,
EXCLUDE USING gist (shifokor WITH =, vaqt WITH &&)
);
INSERT INTO navbat VALUES
('Karimov', '[2026-10-28 09:00+05, 2026-10-28 09:30+05)'),
('Karimov', '[2026-10-28 10:00+05, 2026-10-28 11:00+05)');
SAVEPOINT s;
INSERT INTO navbat VALUES
('Karimov', '[2026-10-28 10:30+05, 2026-10-28 11:30+05)');
ROLLBACK TO SAVEPOINT s;
SELECT tstzmultirange(tstzrange('2026-10-28 09:00+05',
'2026-10-28 12:00+05'))
- range_agg(vaqt) AS bosh
FROM navbat WHERE shifokor = 'Karimov';
ROLLBACK;BEGIN
SET
CREATE EXTENSION
CREATE TABLE
INSERT 0 2
SAVEPOINT
ERROR: conflicting key value violates exclusion constraint "navbat_shifokor_vaqt_excl"
DETAIL: Key (shifokor, vaqt)=(Karimov, ["2026-10-28 10:30:00+05","2026-10-28 11:30:00+05")) conflicts with existing key (shifokor, vaqt)=(Karimov, ["2026-10-28 10:00:00+05","2026-10-28 11:00:00+05")).
ROLLBACK
bosh
-----------------------------------------------------------------------------------------------------------
{["2026-10-28 09:30:00+05","2026-10-28 10:00:00+05"),["2026-10-28 11:00:00+05","2026-10-28 12:00:00+05")}
(1 row)
ROLLBACKBo'sh: 09:30–10:00 va 11:00–12:00. SAVEPOINT — tranzaksiya ichidagi "saqlash nuqtasi" (Tranzaksiya asoslari): xatodan keyin tranzaksiya faqat ROLLBACK ni qabul qiladi, ROLLBACK TO SAVEPOINT s esa faqat xatoli qismni bekor qilib, davom etishga imkon beradi. Agar sizda btree_gist allaqachon o'rnatilgan bo'lsa, CREATE EXTENSION o'rnida NOTICE: extension "btree_gist" already exists, skipping chiqadi — xato emas.
3-mashq (qiyin)
uz_telefon domenini yarating: +998 va 9 ta raqam. mijozlar (ism text, telefon uz_telefon) jadvaliga ikki to'g'ri raqam yozing. Keyin talab o'zgardi: faqat mobil operator kodlari (+99890…, +99891…, +99893…, +99894…, +99897…, +99899…) ruxsat. Bazada shahar raqami (+998712…) bor. Yangi qoidani domenga qo'shing: avval oddiy usulda (xatoni ko'ring), keyin eski qatorlarni tekshirmasdan, oxirida eski raqamni tuzatib — to'liq tekshiruv bilan. Hammasi BEGIN … ROLLBACK. Ishora: ALTER DOMAIN … ADD CONSTRAINT … NOT VALID va ALTER DOMAIN … VALIDATE CONSTRAINT — jadvaldagi NOT VALID kabi (ALTER TABLE); regex ^\+998(90|91|93|94|97|99)[0-9]{7}$; xatolardan keyin davom etish — 2-mashqdagi SAVEPOINT.
Yechim
BEGIN;
CREATE DOMAIN uz_telefon AS text
CHECK (VALUE ~ '^\+998[0-9]{9}$');
CREATE TABLE mijozlar (ism text, telefon uz_telefon);
INSERT INTO mijozlar VALUES
('Aziz', '+998901234567'), ('Do''kon', '+998712345678');
SAVEPOINT s;
ALTER DOMAIN uz_telefon ADD CONSTRAINT uz_mobil
CHECK (VALUE ~ '^\+998(90|91|93|94|97|99)[0-9]{7}$');
ROLLBACK TO SAVEPOINT s;
ALTER DOMAIN uz_telefon ADD CONSTRAINT uz_mobil
CHECK (VALUE ~ '^\+998(90|91|93|94|97|99)[0-9]{7}$')
NOT VALID;
SAVEPOINT s2;
INSERT INTO mijozlar VALUES ('Yangi', '+998711112233');
ROLLBACK TO SAVEPOINT s2;
SAVEPOINT s3;
ALTER DOMAIN uz_telefon VALIDATE CONSTRAINT uz_mobil;
ROLLBACK TO SAVEPOINT s3;
UPDATE mijozlar SET telefon = '+998991112233'
WHERE ism = 'Do''kon';
ALTER DOMAIN uz_telefon VALIDATE CONSTRAINT uz_mobil;
ROLLBACK;BEGIN
CREATE DOMAIN
CREATE TABLE
INSERT 0 2
SAVEPOINT
ERROR: column "telefon" of table "mijozlar" contains values that violate the new constraint
ROLLBACK
ALTER DOMAIN
SAVEPOINT
ERROR: value for domain uz_telefon violates check constraint "uz_mobil"
ROLLBACK
SAVEPOINT
ERROR: column "telefon" of table "mijozlar" contains values that violate the new constraint
ROLLBACK
UPDATE 1
ALTER DOMAIN
ROLLBACKKetma-ketlik: oddiy ADD CONSTRAINT shu domenli hamma ustunlarni tekshirdi va do'kon raqamini topdi. Xabar: "mijozlar jadvalining telefon ustunida yangi cheklovni buzadigan qiymatlar bor". NOT VALID — eski qatorlar tekshirilmaydi, yangi qator esa darhol tekshiriladi (+99871… rad etildi). VALIDATE eski yomon qatorni topdi; tuzatgandan keyin o'tdi. Domen qoidasi bir joyda o'zgardi — mijozlar ham, shu domenli boshqa har jadval ham himoyalandi. Katta jadvallarda NOT VALID + VALIDATE nega kerakligini keyingi darsda ko'ramiz.
8. Loyiha qadami
Bu darsda vazifalar bazasi o'zgarmaydi — kanon tajribasi alohida jadvallarda o'tkazildi. Har tur uchun kanon qarori:
| Tur | vazifalar da |
Nega |
|---|---|---|
| Massiv | Yo'q, teglar — ko'prik jadval | FK, noyoblik, teg ma'lumoti (26-qism qarori) |
| Range | Yo'q | Vazifada "oraliq" yo'q: muddat — bitta nuqta |
| DOMAIN | Yo'q, CHECK lar |
Har qoida bitta jadvalda — DOMAIN foyda bermaydi |
| ENUM | ustuvorlik (26-qismdan) |
Uch qiymat, deyarli o'zgarmaydi |
| citext | Yo'q, lower(email) indeksi |
Kengaytmasiz, natija locale'ga bog'liq emas |
Bu ham muhim qaror: tur faqat "bor" bo'lgani uchun ishlatilmaydi. Commit yo'q. Keyingi darsdagi cheklovlar ham asosan tajriba — lekin ulardan biri (EXCLUDE … DEFERRABLE) vazifalar da 26-qismdan beri ishlab turibdi; uni o'sha yerda ochamiz.
9. Real ishda
- Bron va jadval tizimlari — mehmonxona, shifokor navbati, o'quv xonalari, avtomobil ijarasi:
tstzrange+EXCLUDE— "ikki marta sotish" bazada imkonsiz. - Narx va tarif tarixi — "shu narx 1-yanvardan 31-martgacha amal qilgan":
daterangeva@>bilan "shu sanadagi narx" bitta so'rov. - Node'da massiv parametr —
pgdrayveri JS massivini Postgres massiviga o'giradi:WHERE id = ANY($1)va[3, 5, 7](node-postgres asoslari). - ORM'lar — Prisma massivlarni (
String[]) Postgres'da qo'llaydi; range, DOMAIN va multirange uchun maxsus tur yo'q — xom SQL kerak bo'ladi. - Intervyuda: "Bron kesishmasligini qanday kafolatlaysiz?" — eng kuchli javob:
EXCLUDE USING gist+[), ilova tekshiruvi emas.
Xulosa
- Massiv: indeks 1 dan, chegaradan tashqari —
NULL; bo'sh massivdaarray_lengthNULL,cardinality0. = ANY— "bormi",@>— "hammasi bormi",&&— "kamida bittasi";unnestyoyadi,array_aggyig'adi.- Range
[)— ketma-ket oraliqlar kesishmaydi;EXCLUDE USING gist (… WITH =, vaqt WITH &&)kesishishni bazada taqiqlaydi. - Multirange (
range_agg, ayirish) — "qachon bo'sh?" savoliga bitta so'rov. - DOMAIN — qoidasi bilan tur, qoida bir joyda o'zgaradi;
NOT NULLni ustunga yozing. - ENUM — tartibli, qiymat qo'shiladi, lekin olib tashlanmaydi;
citextnatijasi locale'ga bog'liq bo'lishi mumkin.
Keyingi dars: Ilg'or constraint'lar va generated ustunlar — EXCLUDE chuqurroq, generated ustunlar (PG 18 da VIRTUAL), katta jadvalga cheklovni to'xtatmasdan qo'shish va tartib ni almashtirishga imkon beradigan kechiktirilgan UNIQUE.
Manbalar
- PostgreSQL 18 hujjati — Arrays va Array Functions: https://www.postgresql.org/docs/18/arrays.html, https://www.postgresql.org/docs/18/functions-array.html
- PostgreSQL 18 hujjati — Range Types (kanonik shakl, multirange, exclusion): https://www.postgresql.org/docs/18/rangetypes.html
- PostgreSQL 18 hujjati — CREATE DOMAIN (
NOT NULLhaqida eslatma): https://www.postgresql.org/docs/18/sql-createdomain.html - PostgreSQL 18 hujjati — ALTER TYPE (
ADD VALUE), citext: https://www.postgresql.org/docs/18/sql-altertype.html, https://www.postgresql.org/docs/18/citext.html - IlmHamroh kanon tajribasi (PostgreSQL 18.6, Docker, 2026-10-24); darsdagi so'rovlar — biz ishga tushirdik (PostgreSQL 17.6, Windows 11), 2026-10-25.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!