Real loyiha — to'liq DB schema loyihalash va yozish
Bu kursda o'rgangan barcha narsani bir loyihada birlashtirish vaqti. E-commerce platformasi — foydalanuvchilar, mahsulotlar, buyurtmalar. Boshlashdan yakunigacha: loyihalash, schema, indekslar, Node.js integratsiya.
Loyiha talablari
✅ Foydalanuvchilar ro'yxatdan o'tishi va kirishi
✅ Mahsulotlar katalogi (kategoriyalar bilan)
✅ Savatcha va buyurtmalar
✅ Foydalanuvchi faqat o'z buyurtmalarini ko'radi (RLS)
✅ Qidiruv (pg_trgm)
✅ Softdelete
Schema
-- Extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- Kategoriyalar (daraxtsimon)
CREATE TABLE kategoriyalar (
id SERIAL PRIMARY KEY,
nomi TEXT NOT NULL,
ota_id INT REFERENCES kategoriyalar(id),
yaratilgan TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Foydalanuvchilar
CREATE TABLE foydalanuvchilar (
id BIGSERIAL PRIMARY KEY,
ism TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
parol_hash TEXT NOT NULL,
rol TEXT NOT NULL DEFAULT 'user' CHECK (rol IN ('user', 'admin')),
faol BOOLEAN NOT NULL DEFAULT TRUE,
yaratilgan TIMESTAMPTZ NOT NULL DEFAULT NOW(),
yangilangan TIMESTAMPTZ NOT NULL DEFAULT NOW(),
o_chirilgan TIMESTAMPTZ
);
-- Mahsulotlar
CREATE TABLE mahsulotlar (
id BIGSERIAL PRIMARY KEY,
nomi TEXT NOT NULL,
tavsif TEXT,
narxi NUMERIC(12, 2) NOT NULL CHECK (narxi >= 0),
qoldiq INT NOT NULL DEFAULT 0 CHECK (qoldiq >= 0),
kategoriya_id INT REFERENCES kategoriyalar(id),
rasm_url TEXT,
faol BOOLEAN NOT NULL DEFAULT TRUE,
yaratilgan TIMESTAMPTZ NOT NULL DEFAULT NOW(),
yangilangan TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Manzillar
CREATE TABLE manzillar (
id BIGSERIAL PRIMARY KEY,
foydalanuvchi_id BIGINT NOT NULL REFERENCES foydalanuvchilar(id),
shahar TEXT NOT NULL,
ko_cha TEXT NOT NULL,
uy TEXT NOT NULL,
asosiy BOOLEAN NOT NULL DEFAULT FALSE,
yaratilgan TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Buyurtmalar
CREATE TABLE buyurtmalar (
id BIGSERIAL PRIMARY KEY,
foydalanuvchi_id BIGINT NOT NULL REFERENCES foydalanuvchilar(id),
manzil_id BIGINT REFERENCES manzillar(id),
holat TEXT NOT NULL DEFAULT 'yangi'
CHECK (holat IN ('yangi', 'tasdiqlangan', 'yetkazilmoqda', 'yetkazildi', 'bekor')),
jami_summa NUMERIC(12, 2) NOT NULL,
yaratilgan TIMESTAMPTZ NOT NULL DEFAULT NOW(),
yangilangan TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Buyurtma elementlari
CREATE TABLE buyurtma_elementlari (
id BIGSERIAL PRIMARY KEY,
buyurtma_id BIGINT NOT NULL REFERENCES buyurtmalar(id) ON DELETE CASCADE,
mahsulot_id BIGINT NOT NULL REFERENCES mahsulotlar(id),
soni INT NOT NULL CHECK (soni > 0),
narxi NUMERIC(12, 2) NOT NULL, -- o'sha vaqtdagi narx
UNIQUE (buyurtma_id, mahsulot_id)
);
Indekslar
-- Qidiruv uchun
CREATE INDEX idx_mahsulotlar_nomi_trgm ON mahsulotlar USING GIN (nomi gin_trgm_ops);
CREATE INDEX idx_mahsulotlar_kategoriya ON mahsulotlar (kategoriya_id) WHERE faol = TRUE;
-- Buyurtmalar
CREATE INDEX idx_buyurtmalar_foydalanuvchi ON buyurtmalar (foydalanuvchi_id);
CREATE INDEX idx_buyurtmalar_holat_vaqt ON buyurtmalar (holat, yaratilgan DESC);
-- Foydalanuvchilar
CREATE INDEX idx_foydalanuvchilar_email ON foydalanuvchilar (email);
Row Level Security
ALTER TABLE buyurtmalar ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_buyurtmalari ON buyurtmalar
FOR ALL
USING (
foydalanuvchi_id = current_setting('app.user_id')::BIGINT
OR current_setting('app.user_role') = 'admin'
);
Node.js — asosiy so'rovlar
// Mahsulot qidirish
async function mahsulotQidirish(q, limit = 20, offset = 0) {
const { rows } = await pool.query(`
SELECT m.*, k.nomi AS kategoriya
FROM mahsulotlar m
LEFT JOIN kategoriyalar k ON m.kategoriya_id = k.id
WHERE m.faol = TRUE
AND (m.nomi ILIKE $1 OR m.tavsif ILIKE $1)
ORDER BY similarity(m.nomi, $2) DESC, m.id
LIMIT $3 OFFSET $4
`, [`%${q}%`, q, limit, offset]);
return rows;
}
// Buyurtma yaratish (transaction)
async function buyurtmaYaratish(userId, manzilId, elementlar) {
return withTransaction(async (client) => {
// Mahsulotlar va qoldiqlarni tekshirish
for (const el of elementlar) {
const { rows } = await client.query(
'SELECT narxi, qoldiq FROM mahsulotlar WHERE id = $1 AND faol = TRUE FOR UPDATE',
[el.mahsulot_id]
);
if (!rows[0]) throw new Error(`Mahsulot ${el.mahsulot_id} topilmadi`);
if (rows[0].qoldiq < el.soni) throw new Error(`Mahsulot ${el.mahsulot_id} qoldig'i yetarli emas`);
el.narxi = rows[0].narxi;
}
// Jami hisoblash
const jami = elementlar.reduce((s, el) => s + el.narxi * el.soni, 0);
// Buyurtma yaratish
const { rows: [buyurtma] } = await client.query(`
INSERT INTO buyurtmalar (foydalanuvchi_id, manzil_id, jami_summa)
VALUES ($1, $2, $3) RETURNING *
`, [userId, manzilId, jami]);
// Elementlar qo'shish va qoldiq kamaytirish
for (const el of elementlar) {
await client.query(`
INSERT INTO buyurtma_elementlari (buyurtma_id, mahsulot_id, soni, narxi)
VALUES ($1, $2, $3, $4)
`, [buyurtma.id, el.mahsulot_id, el.soni, el.narxi]);
await client.query(
'UPDATE mahsulotlar SET qoldiq = qoldiq - $1 WHERE id = $2',
[el.soni, el.mahsulot_id]
);
}
return buyurtma;
});
}
Trigger — yangilangan vaqtni avtomatik yangilash
CREATE OR REPLACE FUNCTION yangilangan_vaqt()
RETURNS TRIGGER AS $
BEGIN
NEW.yangilangan = NOW();
RETURN NEW;
END;
$ LANGUAGE plpgsql;
CREATE TRIGGER tr_foydalanuvchilar_yangilangan
BEFORE UPDATE ON foydalanuvchilar
FOR EACH ROW EXECUTE FUNCTION yangilangan_vaqt();
CREATE TRIGGER tr_mahsulotlar_yangilangan
BEFORE UPDATE ON mahsulotlar
FOR EACH ROW EXECUTE FUNCTION yangilangan_vaqt();
Xulosa
Bu kursda qamrab olgan asosiy tushunchalar:
✅ DDL/DML — CREATE, INSERT, SELECT, UPDATE, DELETE
✅ JOIN — INNER, LEFT, RIGHT, FULL, SELF
✅ Ilg'or SQL — Subquery, CTE, Window Functions
✅ JSONB, Array — maxsus turlar
✅ Full-text Search — tsvector, pg_trgm
✅ Index — B-tree, GIN, GiST, Composite, Partial
✅ Transaction — ACID, Isolation Levels, Locking
✅ MVCC — qanday ishlaydi
✅ Normalizatsiya — 1NF, 2NF, 3NF
✅ Xavfsizlik — GRANT, RLS, SQL Injection himoyasi
✅ Backup — pg_dump, pg_restore
✅ Extensions — PostGIS, pg_trgm, TimescaleDB
✅ Node.js — pg, Pool, Transaction
PostgreSQL — kuchli, ishonchli, va kengaytiriladigan database. Bu kurs — asoslar. Amalda ishlash — eng yaxshi o'rgatuvchi.