20-bo‘lim
Amaliy loyiha - kitob do'koni tahlili
Sxema va cheklovlardan boshlab upsert, oyna funksiyalari, CTE, JSONB, indeks va trigger bilan tugaydigan to'liq amaliy loyiha.
Ushbu bo‘lim mundarijasi
- Loyihaning tuzilmasi
- 1-vazifa: umumiy ko'rsatkichlar
- 2-vazifa: eng ko'p sotilgan kitoblar
- 3-vazifa: oylik dinamika
- 4-vazifa: shahar bo'yicha reyting
- 5-vazifa: teglar va JSONB
- 6-vazifa: yangi kitobni xavfsiz qo'shish
- 7-vazifa: narx tarixini yozish
- 8-vazifa: indeks va reja
- 9-vazifa: ko'rinish
- 10-vazifa: buyurtmani xavfsiz qabul qilish
- Keyingi qadamlar
- Xulosa
Yigirma bobda o'rgangan hamma narsani bitta ishchi loyihada birlashtiramiz. Loyiha - Namangandagi kitob do'koni uchun sotuv tahlili.
CREATE TABLE muallif (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ism text NOT NULL UNIQUE
);
CREATE TABLE kitob (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
isbn text NOT NULL UNIQUE,
nom text NOT NULL,
muallif_id int NOT NULL REFERENCES muallif(id),
narx numeric(10,2) NOT NULL CHECK (narx > 0),
teglar text[] NOT NULL DEFAULT '{}',
xossa jsonb NOT NULL DEFAULT '{}'
);
CREATE TABLE mijoz (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
ism text NOT NULL,
shahar text NOT NULL
);
CREATE TABLE buyurtma (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
mijoz_id int NOT NULL REFERENCES mijoz(id),
sana date NOT NULL
);
CREATE TABLE buyurtma_qator (
buyurtma_id int NOT NULL REFERENCES buyurtma(id) ON DELETE CASCADE,
kitob_id int NOT NULL REFERENCES kitob(id),
soni int NOT NULL CHECK (soni > 0),
narx numeric(10,2) NOT NULL CHECK (narx > 0),
PRIMARY KEY (buyurtma_id, kitob_id)
);
INSERT INTO muallif (ism) VALUES
('Abdulla Qodiriy'), ('Cho''lpon'), ('Oybek'), ('Said Ahmad');
INSERT INTO kitob (isbn, nom, muallif_id, narx, teglar, xossa) VALUES
('978-1', 'Otkan kunlar', 1, 85000, '{roman,tarixiy}',
'{"yil":1926,"sahifa":420}'),
('978-2', 'Mehrobdan chayon',1, 72000, '{roman,tarixiy}',
'{"yil":1929,"sahifa":380}'),
('978-3', 'Kecha va kunduz', 2, 68000, '{roman}',
'{"yil":1936,"sahifa":310}'),
('978-4', 'Navoiy', 3, 95000, '{roman,tarixiy}',
'{"yil":1944,"sahifa":520}'),
('978-5', 'Ufq', 4, 64000, '{roman,zamonaviy}',
'{"yil":1964,"sahifa":290}');
INSERT INTO mijoz (ism, shahar) VALUES
('Husanboy', 'Namangan'),
('Malika', 'Namangan'),
('Nodira', 'Toshkent'),
('Aziza', 'Samarqand'),
('Kamola', 'Namangan');
INSERT INTO buyurtma (mijoz_id, sana) VALUES
(1, '2026-01-12'), (2, '2026-01-18'), (1, '2026-02-03'),
(3, '2026-02-14'), (4, '2026-02-27'), (5, '2026-03-05'),
(2, '2026-03-11'), (1, '2026-03-22');
INSERT INTO buyurtma_qator (buyurtma_id, kitob_id, soni, narx) VALUES
(1, 1, 1, 85000), (1, 3, 2, 68000),
(2, 4, 1, 95000),
(3, 2, 1, 72000), (3, 5, 1, 64000),
(4, 1, 3, 85000),
(5, 4, 1, 95000), (5, 2, 2, 72000),
(6, 5, 1, 64000),
(7, 1, 1, 85000), (7, 4, 1, 95000),
(8, 3, 1, 68000);
Loyihaning tuzilmasi #
narx ikki joyda saqlanadikitob.narx - bugungi narx. buyurtma_qator.narx -
sotilgan paytdagi narx.
Bu takrorlash emas, balki ataylab: kitob narxi ertaga oshsa, o'tgan oydagi hisobot o'zgarib ketmasligi kerak.
Umumiy qoida: o'tmishdagi hodisa yozuvi o'sha paytdagi qiymatni o'zida saqlashi kerak - boshqa jadvalga havola qilmasligi kerak.
1-vazifa: umumiy ko'rsatkichlar #
Avval do'kon haqida umumiy tasavvur hosil qilamiz:
SELECT count(DISTINCT b.id) AS buyurtmalar,
count(DISTINCT b.mijoz_id) AS mijozlar,
sum(q.soni) AS sotilgan_nusxa,
sum(q.soni * q.narx) AS tushum,
round(avg(q.soni * q.narx), 0) AS ortacha_qator
FROM buyurtma b
JOIN buyurtma_qator q ON q.buyurtma_id = b.id;
buyurtmalar | mijozlar | sotilgan_nusxa | tushum | ortacha_qator
-------------+----------+----------------+------------+---------------
8 | 5 | 16 | 1258000.00 | 104833
(1 row)
2-vazifa: eng ko'p sotilgan kitoblar #
JOIN va agregat bilan:
SELECT k.nom,
m.ism AS muallif,
sum(q.soni) AS nusxa,
sum(q.soni * q.narx) AS tushum
FROM buyurtma_qator q
JOIN kitob k ON k.id = q.kitob_id
JOIN muallif m ON m.id = k.muallif_id
GROUP BY k.nom, m.ism
ORDER BY nusxa DESC, k.nom;
nom | muallif | nusxa | tushum
------------------+-----------------+-------+-----------
Otkan kunlar | Abdulla Qodiriy | 5 | 425000.00
Kecha va kunduz | Cho'lpon | 3 | 204000.00
Mehrobdan chayon | Abdulla Qodiriy | 3 | 216000.00
Navoiy | Oybek | 3 | 285000.00
Ufq | Said Ahmad | 2 | 128000.00
(5 rows)
3-vazifa: oylik dinamika #
date_trunc va oyna funksiyasi bilan o'sishni ko'ramiz:
WITH oylik AS (
SELECT date_trunc('month', b.sana)::date AS oy,
sum(q.soni * q.narx) AS tushum
FROM buyurtma b
JOIN buyurtma_qator q ON q.buyurtma_id = b.id
GROUP BY 1
)
SELECT oy,
tushum,
lag(tushum) OVER (ORDER BY oy) AS otgan_oy,
tushum - lag(tushum) OVER (ORDER BY oy) AS farq,
sum(tushum) OVER (ORDER BY oy) AS jamlangan
FROM oylik
ORDER BY oy;
oy | tushum | otgan_oy | farq | jamlangan
------------+-----------+-----------+------------+------------
2026-01-01 | 316000.00 | | | 316000.00
2026-02-01 | 630000.00 | 316000.00 | 314000.00 | 946000.00
2026-03-01 | 312000.00 | 630000.00 | -318000.00 | 1258000.00
(3 rows)
lag o'tgan oyni yoniga qo'yadi, sum(...) OVER (ORDER BY)
esa yig'ilib boruvchi summani beradi.
4-vazifa: shahar bo'yicha reyting #
Har shaharda eng ko'p xarid qilgan mijozni topamiz:
WITH mijoz_summa AS (
SELECT m.shahar,
m.ism,
sum(q.soni * q.narx) AS summa
FROM mijoz m
JOIN buyurtma b ON b.mijoz_id = m.id
JOIN buyurtma_qator q ON q.buyurtma_id = b.id
GROUP BY m.shahar, m.ism
),
reyting AS (
SELECT shahar, ism, summa,
row_number() OVER (PARTITION BY shahar
ORDER BY summa DESC) AS orin
FROM mijoz_summa
)
SELECT shahar, ism, summa
FROM reyting
WHERE orin = 1
ORDER BY summa DESC;
shahar | ism | summa
-----------+----------+-----------
Namangan | Husanboy | 425000.00
Toshkent | Nodira | 255000.00
Samarqand | Aziza | 239000.00
(3 rows)
"Har guruhdagi eng yaxshi N ta" - amalda eng ko'p uchraydigan talablardan biri.
Yechim har doim ikki qadamdan iborat:
row_number() OVER (PARTITION BY guruh ORDER BY ...)- Tashqi so'rovda
WHERE orin <= N
WHERE ni to'g'ridan-to'g'ri oyna funksiyasi ustiga yozib
bo'lmaydi - shuning uchun CTE yoki ichki so'rov kerak.
Uchta funksiyani ajratib oling:
| Funksiya | Teng qiymatlarda |
|---|---|
row_number() | 1, 2, 3, 4 - takrorlanmaydi |
rank() | 1, 1, 3, 4 - o'rin tashlaydi |
dense_rank() | 1, 1, 2, 3 - tashlamaydi |
5-vazifa: teglar va JSONB #
Massiv va JSONB birgalikda:
SELECT k.nom,
k.teglar,
(k.xossa->>'yil')::int AS yil,
(k.xossa->>'sahifa')::int AS sahifa
FROM kitob k
WHERE k.teglar @> ARRAY['tarixiy']
AND (k.xossa->>'sahifa')::int > 350
ORDER BY yil;
nom | teglar | yil | sahifa
------------------+-----------------+------+--------
Otkan kunlar | {roman,tarixiy} | 1926 | 420
Mehrobdan chayon | {roman,tarixiy} | 1929 | 380
Navoiy | {roman,tarixiy} | 1944 | 520
(3 rows)
Teg bo'yicha nechta kitob borligini sanash:
SELECT t.teg, count(*) AS kitoblar
FROM kitob k, unnest(k.teglar) AS t(teg)
GROUP BY t.teg
ORDER BY kitoblar DESC, t.teg;
teg | kitoblar
-----------+----------
roman | 5
tarixiy | 3
zamonaviy | 1
(3 rows)
6-vazifa: yangi kitobni xavfsiz qo'shish #
INSERT ... ON CONFLICT bilan - isbn takrorlansa yangilaymiz:
INSERT INTO kitob (isbn, nom, muallif_id, narx, teglar, xossa)
VALUES ('978-1', 'Otkan kunlar', 1, 92000,
'{roman,tarixiy,klassika}', '{"yil":1926,"sahifa":420}')
ON CONFLICT (isbn) DO UPDATE
SET narx = EXCLUDED.narx,
teglar = EXCLUDED.teglar
RETURNING isbn, nom, narx, teglar;
isbn | nom | narx | teglar
-------+--------------+----------+--------------------------
978-1 | Otkan kunlar | 92000.00 | {roman,tarixiy,klassika}
(1 row)
INSERT 0 1
Kitob ikki marta qo'shilmadi - narxi va teglari yangilandi.
7-vazifa: narx tarixini yozish #
Trigger bilan avtomatik audit:
CREATE TABLE narx_tarixi (
id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kitob_id int NOT NULL,
eski numeric(10,2) NOT NULL,
yangi numeric(10,2) NOT NULL
);
CREATE FUNCTION narxni_yoz() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
IF NEW.narx IS DISTINCT FROM OLD.narx THEN
INSERT INTO narx_tarixi (kitob_id, eski, yangi)
VALUES (OLD.id, OLD.narx, NEW.narx);
END IF;
RETURN NULL;
END;
$$;
CREATE TRIGGER kitob_narx_audit
AFTER UPDATE ON kitob
FOR EACH ROW EXECUTE FUNCTION narxni_yoz();
UPDATE kitob SET narx = narx * 1.10 WHERE teglar @> ARRAY['tarixiy'];
SELECT kitob_id, eski, yangi FROM narx_tarixi ORDER BY kitob_id;
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
UPDATE 3
kitob_id | eski | yangi
----------+----------+-----------
1 | 85000.00 | 93500.00
2 | 72000.00 | 79200.00
4 | 95000.00 | 104500.00
(3 rows)
8-vazifa: indeks va reja #
Tez-tez ishlatiladigan so'rovlar uchun indeks qo'yamiz:
CREATE INDEX buyurtma_sana_idx ON buyurtma (sana);
CREATE INDEX qator_kitob_idx ON buyurtma_qator (kitob_id);
CREATE INDEX kitob_teglar_idx ON kitob USING gin (teglar);
CREATE INDEX kitob_xossa_idx ON kitob USING gin (xossa);
SELECT indexname
FROM pg_indexes
WHERE tablename IN ('kitob', 'buyurtma', 'buyurtma_qator')
ORDER BY indexname;
CREATE INDEX
CREATE INDEX
CREATE INDEX
CREATE INDEX
indexname
---------------------
buyurtma_pkey
buyurtma_qator_pkey
buyurtma_sana_idx
kitob_isbn_key
kitob_pkey
kitob_teglar_idx
kitob_xossa_idx
qator_kitob_idx
(8 rows)
| Indeks | Nima uchun |
|---|---|
buyurtma (sana) | Oylik hisobotlar sana bo'yicha filtrlaydi |
buyurtma_qator (kitob_id) | JOIN va "bu kitob qancha sotilgan" |
gin (teglar) | teglar @> ARRAY[...] |
gin (xossa) | xossa @> '{...}' |
Yuqoridagi indekslarni qo'yib, EXPLAIN ni ishga tushirsangiz
baribir Seq Scan ko'rasiz.
Sabab: bu darslikdagi jadvallarda atigi bir necha o'nlab qator bor. Baza ularni bitta sahifada o'qiy oladi - indeksga borib kelish qimmatroq bo'lardi.
Bu rejalashtiruvchining to'g'ri qarori, xato emas.
Shuning uchun indeks foydasini haqiqiy hajmda o'lchang: kamida o'n minglab qator kerak. 15-bobdagi misolda 50 000 qator aynan shuning uchun ishlatilgan.
9-vazifa: ko'rinish #
Tez-tez kerak bo'ladigan so'rovni VIEW ga aylantiramiz:
CREATE VIEW kitob_sotuvi AS
SELECT k.id,
k.nom,
m.ism AS muallif,
coalesce(sum(q.soni), 0) AS nusxa,
coalesce(sum(q.soni * q.narx), 0) AS tushum
FROM kitob k
JOIN muallif m ON m.id = k.muallif_id
LEFT JOIN buyurtma_qator q ON q.kitob_id = k.id
GROUP BY k.id, k.nom, m.ism;
SELECT nom, muallif, nusxa, tushum
FROM kitob_sotuvi
WHERE nusxa > 2
ORDER BY tushum DESC;
CREATE VIEW
nom | muallif | nusxa | tushum
------------------+-----------------+-------+-----------
Otkan kunlar | Abdulla Qodiriy | 5 | 425000.00
Navoiy | Oybek | 3 | 285000.00
Mehrobdan chayon | Abdulla Qodiriy | 3 | 216000.00
Kecha va kunduz | Cho'lpon | 3 | 204000.00
(4 rows)
LEFT JOIN va coalesce juftligi muhim: hech sotilmagan
kitob ham ro'yxatda 0 bilan turadi, NULL bilan emas.
10-vazifa: buyurtmani xavfsiz qabul qilish #
Hammasi bitta tranzaksiyada:
BEGIN;
INSERT INTO buyurtma (mijoz_id, sana)
VALUES (5, '2026-03-28')
RETURNING id AS yangi_buyurtma;
INSERT INTO buyurtma_qator (buyurtma_id, kitob_id, soni, narx)
SELECT currval(pg_get_serial_sequence('buyurtma', 'id')),
k.id, 2, k.narx
FROM kitob k WHERE k.isbn = '978-3';
COMMIT;
SELECT b.id, m.ism, b.sana, q.soni, q.narx
FROM buyurtma b
JOIN mijoz m ON m.id = b.mijoz_id
JOIN buyurtma_qator q ON q.buyurtma_id = b.id
WHERE b.sana = '2026-03-28';
BEGIN
yangi_buyurtma
----------------
9
(1 row)
INSERT 0 1
INSERT 0 1
COMMIT
id | ism | sana | soni | narx
----+--------+------------+------+----------
9 | Kamola | 2026-03-28 | 2 | 68000.00
(1 row)
Buyurtma sarlavhasi va qatorlari birga saqlandi. Ikkinchi
INSERT xato bergan bo'lsa, birinchisi ham bekor bo'lar edi
- ya'ni qatorsiz "osilib qolgan" buyurtma paydo bo'lmaydi.
Keyingi qadamlar #
Loyihani o'zingiz kengaytirib ko'ring:
| Yo'nalish | Nima qilish |
|---|---|
| Qidiruv | pg_trgm bilan kitob nomidan qidirish |
| Bo'laklash | buyurtma ni yil bo'yicha bo'lash |
| Xavfsizlik | hisobot_rol yaratib, faqat VIEW ga ruxsat berish |
| Tezlik | 100 000 qator yaratib, EXPLAIN ANALYZE bilan o'lchash |
| Ishonchlilik | pg_dump bilan nusxa olib, boshqa bazaga tiklash |
- Sxemani o'zingiz noldan yozing va ma'lumot kiriting.
- Umumiy ko'rsatkichlar so'rovini tuzing.
- Eng ko'p sotilgan uchta kitobni chiqaring.
- Oylik tushumni
lagbilan solishtiring. - Har shahardagi eng faol mijozni toping.
- Teg va JSONB bo'yicha filtr yozing.
ON CONFLICTbilan kitobni qayta qo'shib ko'ring.- Narx o'zgarishini yozadigan trigger tuzing.
- Kerakli indekslarni qo'ying va nima uchun kerakligini yozing.
- Buyurtmani bitta tranzaksiyada qabul qiling va sinab ko'ring.
Xulosa #
- Sxema cheklovlardan boshlanadi:
NOT NULL,CHECK,UNIQUE, tashqi kalit. - Hodisa yozuvi o'sha paytdagi qiymatni o'zida saqlaydi -
buyurtma_qator.narxkabi. JOINva agregatlar hisobotning asosini tashkil qiladi.date_truncvalagbirga oylik dinamikani beradi.- "Har guruhdagi eng yaxshi N ta" -
row_number()va tashqiWHEREjuftligi. - Massiv va JSONB bir jadvalda birga ishlay oladi.
ON CONFLICTtakroriy yozuvni xatosiz yangilaydi.- Trigger narx tarixini avtomatik yozib boradi.
- Indeks foydasi faqat katta jadvalda ko'rinadi - kichigida
Seq Scannormal. - Bog'liq yozuvlarni har doim bitta tranzaksiyada saqlang.
Darslik shu yerda tugadi. Endi qo'lingizda PostgreSQL bilan haqiqiy loyiha qurish uchun yetarli bilim bor - eng yaxshi davomi o'z bazangizni yozib ko'rishdir.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.