Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. Teglar jadvali va conflict target
- 2.1 Jadval
- 2.2 Conflict target nima
- 3. ON CONFLICT DO NOTHING
- 3.1 Oddiy INSERT va DO NOTHING
- 3.2 DO NOTHING ning kamchiligi: id yo'q
- 4. ON CONFLICT DO UPDATE va EXCLUDED
- 4.1 EXCLUDED — "qo'shilmoqchi bo'lgan qator"
- 4.2 Nega #yangi ning id si 37?
- 4.3 Hisoblagich: eski qiymatga qo'shish
- 4.4 DO UPDATE ... WHERE: shart bilan yangilash
- 4.5 Bitta buyruqda bir kalit ikki marta
- 5. MERGE: ikki jadvalni solishtirish
- 5.1 Qachon UPSERT yetmaydi
- 5.2 Birinchi MERGE
- 5.3 WHEN NOT MATCHED BY SOURCE (PG 17+)
- 6. Matndan teglar: SQL qadami
- 6.1 Teglarni ajratish funksiyasi
- 6.2 Mavjud vazifalardan teglar
- 6.3 Halol eslatma
- 7. Ko'p uchraydigan xatolar
- 7.1 there is no unique or exclusion constraint matching the ON CONFLICT specification
- 7.2 ON CONFLICT DO UPDATE command cannot affect row a second time
- 7.3 column reference "..." is ambiguous
- 7.4 "DO NOTHING ishladi, lekin id kelmadi"
- 7.5 MERGE boshqalarning qatorlarini o'zgartirdi
- 8. Mashqlar
- 1-mashq (oson): bor tegni qayta qo'shish
- 2-mashq (o'rta): id'lar har doim
- 3-mashq (qiyin): MERGE bilan bajarilganlarni sinxronlash
- 4-mashq: SQL qadami — teglar (holat 23)
- 9. Real ishda
- Xulosa
- Manbalar
PostgreSQL UPSERT va MERGE: ON CONFLICT, EXCLUDED va poyga holatisiz yozish
Qisqacha: UPSERT — "bor bo'lsa yangila, yo'q bo'lsa qo'sh" amali. PostgreSQL'da u
INSERT ... ON CONFLICT (ustunlar) DO NOTHINGyokiDO UPDATE SET ... = EXCLUDED....bilan yoziladi va bitta buyruqda, poyga holatisiz (race condition) ishlaydi. Shart:ON CONFLICTdagi ustunlarda UNIQUE cheklov bo'lishi kerak. Bir nechta jadvalni solishtirib, qo'shish, yangilash va o'chirishni birga qilish uchun —MERGE(PG 15+), PG 17 dan esaMERGE ... RETURNING merge_action().
Bu darsda
- "Avval tekshir, keyin qo'sh" usuli nega ikki parallel so'rovda buzilishini ko'rasiz.
ON CONFLICT DO NOTHINGvaDO UPDATEbilan UPSERT yozasiz,EXCLUDEDnima ekanini tushuntirasiz.- Conflict target,
DO UPDATE ... WHEREva "bir buyruqda ikki marta" xatosini bilasiz. MERGEbilan import jadvalini asosiy jadvalga sinxronlaysiz.vazifalarbazasigateglarjadvalini qo'shasiz (holat 23).
Oldin bilishingiz kerak: INSERT va RETURNING, UPDATE va DELETE xavfsiz, CREATE TABLE va cheklovlar, Ifodalar va o'rnatilgan funksiyalar.
1. Nega bu kerak?
Oldingi darsda ifodalar va funksiyalar bilan ma'lumotni SQL ichida qayta ishladik. Endi vazifalar ga yangi imkoniyat qo'shamiz: teglar. Frontendda (22-qism) vazifa matnidagi #uy, #to'lov so'zlari teg bo'lardi. Endi teglar bazada alohida jadvalda turadi.
Aziz "Kranni almashtirish #uy" deb yozdi. Server #uy tegini teglar jadvaliga qo'shishi kerak. Lekin Azizda #uy allaqachon bor bo'lishi mumkin. Birinchi xayolga keladigan yo'l:
SELECT— Azizda#uybormi?- Yo'q bo'lsa —
INSERT.
Bitta foydalanuvchi, bitta so'rov bo'lsa — ishlaydi. Endi tasavvur qiling: Aziz bir vaqtda telefondan ham, noutbukdan ham #sport tegli vazifa yozdi. Ikki so'rov deyarli bir paytda keldi. Biz buni ikki psql oynasida sinadik (PostgreSQL 17.6, o'z kompyuterimizda). Har oyna — bazaga alohida ulanish, ya'ni alohida seans. Vaqt bo'yicha:
| Qadam | A seans (telefon) | B seans (noutbuk) | Natija |
|---|---|---|---|
| 1 | BEGIN; INSERT … '#sport' |
— | A qo'shdi, lekin COMMIT yo'q |
| 2 | — | SELECT count(*) … '#sport' |
0 — A ning qatori B ga ko'rinmaydi |
| 3 | — | INSERT … '#sport' |
B A tugashini kutib qoladi |
| 4 | COMMIT; (~1.3 s dan keyin) |
— | A ning tegi saqlandi |
| 5 | — | — | B xato oladi: duplicate key |
B seansidagi buyruqlar (ikkinchi seans kerakligi uchun bitta oynada takrorlanmaydi):
SELECT count(*) FROM teglar WHERE egasi_id = 1 AND nom = '#sport';
INSERT INTO teglar (egasi_id, nom) VALUES (1, '#sport');B seansida:
count
-------
0
(1 row)
ERROR: duplicate key value violates unique constraint "teglar_egasi_id_nom_key"
DETAIL: Key (egasi_id, nom)=(1, #sport) already exists.B "teg yo'q" deb ko'rdi — A hali COMMIT qilmagani uchun uning qatori B ga ko'rinmaydi. B INSERT qildi, A tugashini ~1.3 soniya kutdi va xato oldi. Foydalanuvchi "Xato yuz berdi" degan oynani ko'radi. Bu — poyga holati (race condition): natija qaysi so'rov birinchi "yetib kelgani"ga bog'liq. Xuddi ikki kassir bir paytda omborga qarab "oxirgi qop un bor" deb, ikkalasi ham uni sotib yuborgandek.
sequenceDiagram
participant A as Telefon (A)
participant DB as PostgreSQL
participant B as Noutbuk (B)
A->>DB: INSERT #sport (COMMIT yo'q)
B->>DB: SELECT #sport bormi?
DB-->>B: 0 ta
B->>DB: INSERT #sport
Note over B,DB: A tugashini kutadi
A->>DB: COMMIT
DB-->>B: ERROR duplicate keyDiagrammada asosiy narsa — B ning SELECT i va INSERT i orasidagi "teshik". Tekshiruv bilan yozuv ikki alohida qadam bo'lsa, ular orasiga boshqa so'rov kirib qoladi. Yechim — tekshiruv va yozuvni bitta buyruqqa birlashtirish. Bu darsning mavzusi aynan shu.
2. Teglar jadvali va conflict target
2.1 Jadval
vazifalar bazasining 23-holatida ikki yangi jadval bor. Bu yerda teglar ning asosiy qismi:
CREATE TABLE teglar (
id bigint GENERATED ALWAYS AS IDENTITY,
egasi_id bigint NOT NULL,
nom text NOT NULL,
CONSTRAINT teglar_pkey PRIMARY KEY (id),
CONSTRAINT teglar_egasi_id_fkey FOREIGN KEY (egasi_id)
REFERENCES foydalanuvchilar (id) ON DELETE CASCADE,
-- teg kichik harfda saqlanadi, # bilan boshlanadi
CONSTRAINT teglar_nom_check CHECK (nom ~ '^#'
AND nom = lower(nom) AND char_length(nom) <= 50),
-- ON CONFLICT (egasi_id, nom) shu cheklovga tayanadi
CONSTRAINT teglar_egasi_id_nom_key UNIQUE (egasi_id, nom)
);Teg har foydalanuvchiniki: Azizning #uy i — Dilnozaniki emas. Shuning uchun noyoblik bitta ustunda emas, juftlikda: UNIQUE (egasi_id, nom). Aziz ikkita #uy yarata olmaydi, lekin Aziz va Dilnoza har biri o'z #uy iga ega bo'la oladi.
Ikkinchi jadval — vazifa_teglari, qaysi vazifada qaysi teg borligini bog'laydi. U haqida Bog'lanishlar: 1:1, 1:N, N:M darsida batafsil gaplashamiz. Hozircha bilish yetarli: bitta vazifada bir nechta teg, bitta tegda bir nechta vazifa bo'lishi mumkin.
2.2 Conflict target nima
UPSERT'ning kaliti — conflict target (to'qnashuv nishoni): "qaysi noyoblik buzilsa, bu to'qnashuv deb hisoblansin". ON CONFLICT (egasi_id, nom) — "agar (egasi_id, nom) juftligi band bo'lsa". Postgres bu ustunlarga mos UNIQUE cheklov yoki noyob indeksni qidiradi va to'qnashuvni o'sha indeks orqali aniqlaydi.
Indeks muhim: u bir vaqtda ikki seans bir xil qiymatni yozayotganini "ko'radi". SELECT esa hali tasdiqlanmagan qatorni ko'rmaydi — biz buni yuqorida ko'rdik. Indeks qanday ishlashini Indeks nima: B-tree ichidan darsida o'rganamiz.
Mos indeks bo'lmasa — xato:
INSERT INTO teglar (egasi_id, nom) VALUES (1, '#uy')
ON CONFLICT (nom) DO NOTHING;ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specificationTarjimasi: "ON CONFLICT ko'rsatmasiga mos noyob yoki istisno cheklovi yo'q". Bizda UNIQUE (nom) yo'q — faqat (egasi_id, nom) bor. Postgres "nom bo'yicha to'qnashuv"ni aniqlay olmaydi.
Ustunlar o'rniga cheklov nomini ham yozish mumkin:
INSERT INTO teglar (egasi_id, nom) VALUES (1, '#uy')
ON CONFLICT ON CONSTRAINT teglar_egasi_id_nom_key DO NOTHING;INSERT 0 0INSERT 0 0 — hech narsa qo'shilmadi (ikkinchi raqam — qo'shilgan qatorlar soni). Ustunlar bilan yozish odatda afzal: cheklov nomi o'zgarsa ham so'rov buzilmaydi.
Tekshirib ko'ring:
foydalanuvchilarjadvalida email uchunUNIQUE INDEX ... (lower(email))bor (ifoda ustidagi indeks).ON CONFLICT (email)ishlaydimi?
Javob
Yo'q — email ustunining o'zida noyob indeks yo'q, indeks lower(email) ifodasida. Conflict target indeksga aynan mos bo'lishi kerak: ON CONFLICT (lower(email)). Postgres ifodali indeksni ham conflict target sifatida qabul qiladi, faqat ifoda indeksdagi bilan bir xil yozilishi shart.
3. ON CONFLICT DO NOTHING
3.1 Oddiy INSERT va DO NOTHING
Avval oddiy INSERT — Azizda #uy allaqachon bor:
INSERT INTO teglar (egasi_id, nom) VALUES (1, '#uy');ERROR: duplicate key value violates unique constraint "teglar_egasi_id_nom_key"
DETAIL: Key (egasi_id, nom)=(1, #uy) already exists.Tarjimasi: "takror kalit qiymati teglar_egasi_id_nom_key noyoblik cheklovini buzadi. Tafsilot: (1, #uy) kaliti allaqachon bor". Bu xatoni CREATE TABLE va cheklovlar darsida ko'rgansiz.
Endi xuddi shu buyruq ON CONFLICT ... DO NOTHING bilan:
INSERT INTO teglar (egasi_id, nom) VALUES (1, '#uy')
ON CONFLICT (egasi_id, nom) DO NOTHING
RETURNING id; id
----
(0 rows)
INSERT 0 0Xato yo'q. Teg bor edi — Postgres jim o'tib ketdi. RETURNING bo'sh: hech qanday qator qo'shilmadi va o'zgarmadi, qaytaradigan narsa yo'q.
Poyga holati ham shu bilan yo'qoladi. Birinchi bo'limdagi tajribani B seansida ON CONFLICT DO NOTHING bilan takrorladik (PostgreSQL 17.6). B yana A ning COMMIT ini kutdi, lekin xato o'rniga INSERT 0 0 oldi. Bazada #sport — bitta.
3.2 DO NOTHING ning kamchiligi: id yo'q
Server tegni bog'lash uchun uning id sini bilishi kerak. DO NOTHING esa bor tegning id sini qaytarmaydi. Ikki yo'l bor:
- UPSERT'dan keyin alohida
SELECT id FROM teglar WHERE ...(ikkinchi so'rov, lekin endi xavfsiz — teg albatta bor). DO UPDATEni "hech narsani o'zgartirmaydigan" qilib yozish — keyingi bo'limda.
Tekshirib ko'ring:
ON CONFLICT DO NOTHINGni conflict target'siz yozsa bo'ladimi:INSERT ... ON CONFLICT DO NOTHING?
Javob
Ha, DO NOTHING uchun target ixtiyoriy: unda istalgan noyoblik cheklovi buzilsa ham qator jim tashlab yuboriladi. Bu qulay, lekin xavfli: siz kutmagan cheklov (masalan boshqa UNIQUE ustun) buzilsa ham xato chiqmaydi va muammo yashirinadi. DO UPDATE da esa target majburiy.
4. ON CONFLICT DO UPDATE va EXCLUDED
4.1 EXCLUDED — "qo'shilmoqchi bo'lgan qator"
DO UPDATE — to'qnashuv bo'lsa, mavjud qatorni yangilash. Yangilashda ikki qator kerak bo'ladi: jadvaldagi eski qator va biz qo'shmoqchi bo'lgan yangi qator. Yangisi — maxsus EXCLUDED (chetlatilgan) nomi bilan. "Chetlatilgan", chunki u jadvalga kira olmay, eshik oldida qoldi.
Tegning id sini doim olish uchun naqsh (sinov BEGIN ... ROLLBACK ichida — bazani o'zgartirmaslik uchun):
BEGIN;
INSERT INTO teglar (egasi_id, nom)
VALUES (1, '#uy'), (1, '#yangi')
ON CONFLICT (egasi_id, nom) DO UPDATE SET nom = EXCLUDED.nom
RETURNING id, nom, xmax <> 0 AS yangilandi;
ROLLBACK;BEGIN
id | nom | yangilandi
----+--------+------------
8 | #uy | t
37 | #yangi | f
(2 rows)
INSERT 0 2
ROLLBACKSET nom = EXCLUDED.nom — nomni o'ziga tenglash, qiymat o'zgarmaydi. Lekin endi bu qator "yangilangan" hisoblanadi va RETURNING uni qaytaradi. Natijada ikkala tegning id si bor: #uy — eski (8), #yangi — yangi (37).
xmax <> 0 — kichik hiyla. xmax — har qatorda bor yashirin tizim ustuni. Yangilangan qatorda u noldan farqli, yangi qo'shilganda — nol. Uning to'liq ma'nosini MVCC: Postgres ichida versiyalar darsida o'rganamiz. Hozir bilish shart emas — faqat "yangimi yoki borimi" ni ajratish uchun ishlatiladi.
Diqqat: "o'zgarmaydigan"
DO UPDATEbepul emas. U baribir qatorning yangi versiyasini yozadi va trigger'lar ishlaydi. Tez-tez chaqiriladigan joydaDO NOTHING+ alohidaSELECTarzonroq bo'lishi mumkin.
4.2 Nega #yangi ning id si 37?
Bazada 32 ta teg bor edi, keyingi yangi teg 33 olishi kerak edi. Lekin shu darsdagi oldingi uch buyruqni eslang: ON CONSTRAINT ... DO NOTHING, xato bergan oddiy INSERT va DO NOTHING ... RETURNING. Ularning birortasi ham qator qo'shmadi, lekin har biri raqam oldi: 33, 34, 35. Shu buyruqdagi #uy ham 36 ni oldi. #yangi ga 37 qoldi.
Qoida: Postgres raqamni qatorni yozishdan oldin oladi, to'qnashuv esa keyin aniqlanadi. Olingan raqam qaytarilmaydi. Primary key tanlovi darsida ROLLBACK id'da "teshik" qoldirishini ko'rgansiz — bu ham xuddi shunday. Id'lar ketma-ket bo'lishiga hech qachon tayanmang.
4.3 Hisoblagich: eski qiymatga qo'shish
UPSERT'ning eng mashhur ishlatilishi — hisoblagich (counter). Masalan vazifa necha marta ko'rilganini sanash. Sinov uchun vaqtinchalik jadval (TEMP TABLE — seans tugaganda o'zi o'chadi):
CREATE TEMP TABLE korishlar (
vazifa_id bigint PRIMARY KEY,
soni int NOT NULL,
oxirgi timestamptz
);
INSERT INTO korishlar VALUES (1, 1, '2026-10-20 10:00+05')
ON CONFLICT (vazifa_id) DO UPDATE
SET soni = korishlar.soni + 1, oxirgi = EXCLUDED.oxirgi;
INSERT INTO korishlar VALUES (1, 1, '2026-10-20 11:00+05')
ON CONFLICT (vazifa_id) DO UPDATE
SET soni = korishlar.soni + 1, oxirgi = EXCLUDED.oxirgi;
SELECT * FROM korishlar;CREATE TABLE
INSERT 0 1
INSERT 0 1
vazifa_id | soni | oxirgi
-----------+------+------------------------
1 | 2 | 2026-10-20 06:00:00+00
(1 row)Birinchi buyruq qatorni qo'shdi (soni = 1), ikkinchisi to'qnashdi va soni ni oshirdi. korishlar.soni — jadvaldagi eski qiymat, EXCLUDED.oxirgi — yangi qatordan kelgan vaqt. Vaqt UTC'da ko'rinyapti: 11:00 Toshkent = 06:00 UTC (Ma'lumot turlari darsidagi timestamptz).
Bu buyruq parallel so'rovlarda ham to'g'ri ishlaydi. Ikki seans bir vaqtda soni + 1 qilsa, ikkinchisi birinchisi tugashini kutadi va yangilangan qiymatga qo'shadi — natija 2 emas, 3 bo'ladi.
4.4 DO UPDATE ... WHERE: shart bilan yangilash
Ba'zan eski ma'lumot yangisini bosib ketmasligi kerak. Masalan telefon internetsiz edi va eski "ko'rish" hodisasini kechikib yubordi. WHERE bilan faqat yangiroq vaqt yozilsin:
CREATE TEMP TABLE korishlar (
vazifa_id bigint PRIMARY KEY,
soni int NOT NULL,
oxirgi timestamptz
);
INSERT INTO korishlar VALUES (1, 1, '2026-10-20 11:00+05');
-- eskiroq vaqt keldi: yangilanmasin
INSERT INTO korishlar VALUES (1, 1, '2026-10-20 09:00+05')
ON CONFLICT (vazifa_id) DO UPDATE
SET oxirgi = EXCLUDED.oxirgi
WHERE korishlar.oxirgi < EXCLUDED.oxirgi;
SELECT * FROM korishlar;CREATE TABLE
INSERT 0 1
INSERT 0 0
vazifa_id | soni | oxirgi
-----------+------+------------------------
1 | 1 | 2026-10-20 06:00:00+00
(1 row)INSERT 0 0 — to'qnashuv bo'ldi, lekin WHERE sharti yolg'on chiqdi va qator o'zgarmadi. Vaqt 11:00 (06:00 UTC) bo'lib qoldi. Biz buni o'zimiz ishga tushirdik (PostgreSQL 17.6).
4.5 Bitta buyruqda bir kalit ikki marta
INSERT INTO teglar (egasi_id, nom)
VALUES (1, '#ikki'), (1, '#ikki')
ON CONFLICT (egasi_id, nom) DO UPDATE SET nom = EXCLUDED.nom;ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.Tarjimasi: "ON CONFLICT DO UPDATE buyrug'i bitta qatorga ikkinchi marta ta'sir qila olmaydi. Maslahat: bitta buyruqda qo'shilayotgan qatorlarda cheklangan qiymatlar takrorlanmasin". Postgres qaysi qiymat "oxirgi" ekanini o'zi tanlamaydi — tanlovni sizga qoldiradi.
Bu ko'pincha matndan teg ajratganda chiqadi: "#uy ... #uy" matnida teg ikki marta. Yechim — kiritishdan oldin takrorni olib tashlash (DISTINCT):
BEGIN;
INSERT INTO teglar (egasi_id, nom)
SELECT DISTINCT 1, nom FROM (VALUES ('#ikki'), ('#ikki')) AS t(nom)
ON CONFLICT (egasi_id, nom) DO UPDATE SET nom = EXCLUDED.nom
RETURNING id, nom;
ROLLBACK;BEGIN
id | nom
----+-------
40 | #ikki
(1 row)
INSERT 0 1
ROLLBACKDO NOTHING da bu xato chiqmaydi — ikkinchi nusxa jim tashlab yuboriladi.
Tekshirib ko'ring: hisoblagich misolida
SET soni = soni + 1deb (jadval nomisiz) yozsa nima bo'ladi?
Javob
soni ikki ma'noli: jadvaldagi korishlar.soni mi yoki EXCLUDED.soni mi? SET ning chap tomoni har doim jadval ustuni. O'ng tomonida esa Postgres ERROR: column reference "soni" is ambiguous deydi. Shuning uchun o'ng tomonda doim korishlar.soni yoki EXCLUDED.soni deb aniq yozing.
5. MERGE: ikki jadvalni solishtirish
5.1 Qachon UPSERT yetmaydi
Dilnoza boshqa ilovadan ro'yxatini import qilmoqchi. Import jadvalida uchta qator bor. Kerak:
- matni bir xil vazifa bor va holati farq qilsa — yangilash;
- bunday vazifa yo'q bo'lsa — qo'shish;
- importda yo'q, lekin bazada bor vazifa — o'chirish (to'liq sinxron).
ON CONFLICT faqat qo'shish yoki yangilashni biladi, o'chirishni emas. Bundan tashqari unga UNIQUE cheklov kerak, vazifalar.matn esa noyob emas. Bu ish — MERGE (birlashtirish) uchun. U SQL standartida bor va Postgres'da 15-versiyadan beri ishlaydi.
flowchart TD
M["MERGE: manba qatori"] --> Q{"ON sharti bo'yicha<br/>juft bormi?"}
Q -->|ha| U["WHEN MATCHED<br/>UPDATE yoki DELETE"]
Q -->|yo'q| I["WHEN NOT MATCHED<br/>INSERT"]
T["Jadvalda juftsiz qolgan<br/>qator"] --> D["WHEN NOT MATCHED<br/>BY SOURCE (PG 17+)"]Diagrammaga qarang: har manba qatori uchun Postgres juft qidiradi va uch tarmoqdan biriga yuboradi. Jadvalda juftsiz qolgan qatorlar uchun alohida tarmoq bor.
5.2 Birinchi MERGE
Kanon tajribasi (PostgreSQL 18.6), BEGIN ... ROLLBACK ichida:
BEGIN;
CREATE TEMP TABLE import (matn text, bajarildi boolean);
INSERT INTO import VALUES
('Bozordan meva olish #bozor', false),
('Yangi vazifa importdan #bozor', false),
('Rezyume yangilash #ish', true);
MERGE INTO vazifalar v
USING import i ON v.egasi_id = 2 AND v.matn = i.matn
WHEN MATCHED AND v.bajarildi <> i.bajarildi THEN
UPDATE SET bajarildi = i.bajarildi,
bajarilgan_vaqt = CASE WHEN i.bajarildi
THEN '2026-10-20 12:00+05'::timestamptz END
WHEN NOT MATCHED THEN
INSERT (egasi_id, matn, bajarildi)
VALUES (2, i.matn, i.bajarildi)
RETURNING merge_action(), v.id, v.matn, v.bajarildi;
ROLLBACK;BEGIN
CREATE TABLE
INSERT 0 3
merge_action | id | matn | bajarildi
--------------+----+-------------------------------+-----------
UPDATE | 16 | Bozordan meva olish #bozor | f
INSERT | 48 | Yangi vazifa importdan #bozor | f
UPDATE | 36 | Rezyume yangilash #ish | t
(3 rows)
MERGE 3
ROLLBACKQatorma-qator o'qiymiz:
| Qism | Ma'nosi |
|---|---|
MERGE INTO vazifalar v |
nishon — o'zgaradigan jadval |
USING import i ON ... |
manba va juftlik sharti |
WHEN MATCHED AND ... THEN UPDATE |
juft bor va holat farqli — yangilash |
WHEN NOT MATCHED THEN INSERT |
juft yo'q — qo'shish |
RETURNING merge_action() — PG 17 dan: har qatorda qaysi amal bajarilgani (INSERT, UPDATE, DELETE). bajarilgan_vaqt dagi CASE Ifodalar darsidan: bajarildi yolg'on bo'lsa, ELSE yo'q — NULL. Bu bajarildi = (bajarilgan_vaqt IS NOT NULL) cheklovini saqlaydi.
5.3 WHEN NOT MATCHED BY SOURCE (PG 17+)
To'liq sinxron uchun importda yo'q vazifalarni o'chiramiz. Biz o'zimiz sinadik (PostgreSQL 17.6), oxirida ROLLBACK — bazani buzmaslik uchun. Natijada merge_action ustuniga qarang — endi DELETE qatorlari ham bor:
BEGIN;
CREATE TEMP TABLE import (matn text, bajarildi boolean);
INSERT INTO import VALUES
('Bozordan meva olish #bozor', false),
('Yangi vazifa importdan #bozor', false),
('Rezyume yangilash #ish', true);
MERGE INTO vazifalar v
USING import i ON v.egasi_id = 2 AND v.matn = i.matn
WHEN MATCHED AND v.bajarildi <> i.bajarildi THEN
UPDATE SET bajarildi = i.bajarildi,
bajarilgan_vaqt = CASE WHEN i.bajarildi
THEN '2026-10-20 12:00+05'::timestamptz END
WHEN NOT MATCHED THEN
INSERT (egasi_id, matn, bajarildi)
VALUES (2, i.matn, i.bajarildi)
WHEN NOT MATCHED BY SOURCE AND v.egasi_id = 2 THEN
DELETE
RETURNING merge_action(), v.id, v.matn;
ROLLBACK;BEGIN
CREATE TABLE
INSERT 0 3
merge_action | id | matn
--------------+----+-----------------------------------------------
UPDATE | 16 | Bozordan meva olish #bozor
INSERT | 49 | Yangi vazifa importdan #bozor
UPDATE | 36 | Rezyume yangilash #ish
DELETE | 26 | Dugonamning tug'ilgan kuniga sovg'a #oila
DELETE | 29 | Gaz to'lovi #to'lov
DELETE | 14 | IELTS writing task 2 mashqi #ingliz
DELETE | 5 | Ingliz tili: 20 ta yangi so'z #o'qish #ingliz
(7 rows)
MERGE 7
ROLLBACKDilnozaning importda yo'q to'rt vazifasi o'chdi. Yangi vazifa id si — 49: 48 ni oldingi, ROLLBACK qilingan MERGE olgan edi. Muhim: AND v.egasi_id = 2 sharti. Usiz boshqa foydalanuvchilarning hamma vazifalari ham "manbada juftsiz" hisoblanib o'chib ketardi. ON sharti juftlikni aniqlaydi, lekin nishon jadvalini cheklamaydi.
Diqqat:
MERGEUPSERT'ning o'rnini bosmaydi. PostgreSQL hujjatiga ko'ra, ikki seans bir vaqtda bir xil qatorniMERGEbilan qo'shsa, ikkinchisi noyoblik xatosini oladi —MERGEON CONFLICTkabi qayta urinmaydi. Parallel "bor bo'lsa yangila" uchun —INSERT ... ON CONFLICT.MERGE— import, sinxronlash, fon vazifalari uchun.
Tekshirib ko'ring:
MERGEdagiON v.egasi_id = 2 AND v.matn = i.matnshartiniON v.matn = i.matnga almashtirsak,Bozordan meva olish #bozorqatori uchun nima o'zgarishi mumkin?
Javob
Boshqa foydalanuvchida ham shu matnli vazifa bo'lsa, u ham "juft" bo'lib qoladi va yangilanadi — Dilnozaning importi begona vazifani o'zgartiradi. Bitta manba qatoriga ikki nishon qatori mos kelsa, Postgres buni ruxsat beradi (har biri bir marta yangilanadi). Teskarisi — bitta nishon qatoriga ikki manba qatori — xato beradi. Shuning uchun ON shartida egasini doim ko'rsating.
6. Matndan teglar: SQL qadami
6.1 Teglarni ajratish funksiyasi
Teglar vazifa matnidan olinadi. Frontendda buni JavaScript regex qilardi (22-qism, TEG). Bazada xuddi shu qoida — PostgreSQL regex bilan yozilgan funksiya:
CREATE FUNCTION teglarni_ajrat(matn text) RETURNS SETOF text
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$
SELECT DISTINCT lower(m[1])
FROM regexp_matches(matn, '(?<![[:alnum:]_#])(#[[:alnum:]_]+(?:[''‘’][[:alnum:]_]+)*)', 'g') AS m
$$;O'z funksiyalarimizni (trigger funksiyalari bilan) Denormalizatsiya va hisoblangan maydonlar darsida yozamiz. Hozir uni "matn berasan — teglar ro'yxatini olasan" qutisi deb qabul qiling. Regex'ning ma'nosi: # dan oldin harf, raqam, _ yoki # bo'lmasin. # dan keyin harflar keladi, apostrof (') faqat harflar orasida bo'ladi. DISTINCT takrorni olib tashlaydi — «Bitta buyruqda bir kalit ikki marta» xatosiga yo'l qo'ymaydi.
Sinab ko'ramiz:
SELECT * FROM teglarni_ajrat(
'Oshxonadagi jo''mrakni almashtirish #ta''mir #UY Non#uy ##x #o'''
); teglarni_ajrat
----------------
#uy
#o
#ta'mir
(3 rows)#UY kichik harfga o'tdi. Non#uy teg emas — # dan oldin harf bor. ##x ham teg emas. #o' — #o bo'ldi: oxiridagi apostrofdan keyin harf yo'q. SQL satrida apostrof ikkilanadi ('') — SQL tili darsidan.
6.2 Mavjud vazifalardan teglar
teglar jadvali bo'sh yaratiladi. Mavjud 40 dan ortiq vazifaning teglarini bir buyruqda to'ldiramiz — INSERT ... SELECT va ON CONFLICT DO NOTHING:
INSERT INTO teglar (egasi_id, nom)
SELECT DISTINCT v.egasi_id, t.nom
FROM vazifalar v CROSS JOIN LATERAL teglarni_ajrat(v.matn) AS t(nom)
ORDER BY 1, 2
ON CONFLICT (egasi_id, nom) DO NOTHING;CROSS JOIN LATERAL — "har vazifa uchun uning teglarini yonma-yon qo'y". Uni JOIN'lar va Subquery va LATERAL darslarida o'rganamiz. Hozir muhimi — ON CONFLICT DO NOTHING. Faylni qayta ishga tushirsangiz ham, teglar takrorlanmaydi. Natija: 32 teg va 46 bog'lanish.
Natijani tekshiramiz — Azizning teglari va har biri nechta vazifada:
SELECT t.nom, count(vt.vazifa_id) AS vazifalar
FROM teglar t LEFT JOIN vazifa_teglari vt ON vt.teg_id = t.id
WHERE t.egasi_id = 1
GROUP BY t.nom ORDER BY 2 DESC, 1; nom | vazifalar
------------+-----------
#o'qish | 3
#bozor | 1
#docker | 1
#oila | 1
#portfolio | 1
#ta'mir | 1
#to'lov | 1
#uy | 1
(8 rows)6.3 Halol eslatma
Teglar hozircha bir martalik to'ldirildi. Aziz ertaga yangi #sport tegli vazifa yozsa, teglar o'zi yangilanmaydi. Buni avtomatlashtirish — trigger bilan, Denormalizatsiya va hisoblangan maydonlar darsida. Shungacha teglar qo'lda (yoki server tomonidan UPSERT bilan) yoziladi.
7. Ko'p uchraydigan xatolar
7.1 there is no unique or exclusion constraint matching the ON CONFLICT specification
Conflict target'dagi ustunlarga aynan mos UNIQUE cheklov yo'q. Tekshiring: ustunlar to'plami bir xilmi ((egasi_id, nom) va (nom) — boshqa-boshqa), ifodali indeksda ifoda bir xilmi. Kerak bo'lsa avval cheklovni qo'shing.
7.2 ON CONFLICT DO UPDATE command cannot affect row a second time
Bitta INSERT da bir kalit ikki marta. Manbani DISTINCT (yoki DISTINCT ON va tartib) bilan tozalang.
7.3 column reference "..." is ambiguous
DO UPDATE SET ning o'ng tomonida ustun nomi jadval nomisiz yozilgan. jadval.ustun yoki EXCLUDED.ustun deb aniq yozing.
7.4 "DO NOTHING ishladi, lekin id kelmadi"
Bu xato emas — DO NOTHING o'zgarmagan qatorni qaytarmaydi. DO UPDATE SET x = EXCLUDED.x naqshi yoki keyingi SELECT kerak.
7.5 MERGE boshqalarning qatorlarini o'zgartirdi
WHEN NOT MATCHED BY SOURCE yoki keng ON sharti. O'chiradigan MERGE ni avval BEGIN ichida, RETURNING merge_action() bilan ishga tushirib ko'ring, keyin COMMIT.
8. Mashqlar
Mashq fayllari kurs/mashqlar/26/23-upsert/ da. Hammasi vazifalar bazasining 23-holatida bajariladi.
1-mashq (oson): bor tegni qayta qo'shish
Madinaga (egasi_id = 4) #oila va #sayohat teglarini qo'shing. #oila unda allaqachon bor — xato chiqmasin. RETURNING nom bilan faqat haqiqatan qo'shilgan teg chiqsin. Yordam: ON CONFLICT ... DO NOTHING — «ON CONFLICT DO NOTHING» bo'limi.
Yechim
INSERT INTO teglar (egasi_id, nom)
VALUES (4, '#oila'), (4, '#sayohat')
ON CONFLICT (egasi_id, nom) DO NOTHING
RETURNING nom; nom
----------
#sayohat
(1 row)
INSERT 0 1#oila to'qnashdi va jim tashlandi — RETURNING faqat yangi qatorni qaytardi. INSERT 0 1 — bitta qator qo'shildi.
2-mashq (o'rta): id'lar har doim
Gulnoraga (egasi_id = 6) #bozor (bor) va #retsept (yo'q) teglarini yozing. Natijada ikkala tegning nom i va "yangimi" belgisi chiqsin. Yordam: DO UPDATE SET nom = EXCLUDED.nom va xmax = 0 — yangi qator belgisi.
Yechim
INSERT INTO teglar (egasi_id, nom)
VALUES (6, '#bozor'), (6, '#retsept')
ON CONFLICT (egasi_id, nom) DO UPDATE SET nom = EXCLUDED.nom
RETURNING nom, xmax = 0 AS yangi; nom | yangi
----------+-------
#bozor | f
#retsept | t
(2 rows)
INSERT 0 2INSERT 0 2 — Postgres yangilangan qatorni ham "ta'sir qilingan" deb sanaydi. xmax = 0 yangi qatorni ajratadi.
3-mashq (qiyin): MERGE bilan bajarilganlarni sinxronlash
Bekzod (egasi_id = 7) boshqa ilovada ikki vazifasini bajardi: Universitet kontrakti #to'lov va Yangi kitob o'qish #o'qish (ikkinchisi bazada yo'q). Vaqtinchalik bajarilganlar (matn text) jadvalini yarating. MERGE bilan: bor vazifa bajarilmagan bo'lsa — bajarildi = true va bajarilgan_vaqt = '2026-10-21 09:00+05'; yo'q bo'lsa — shu holatda qo'shilsin. Hammasi BEGIN ... ROLLBACK ichida, RETURNING merge_action(), v.matn bilan. Yordam: CHECK (bajarildi = (bajarilgan_vaqt IS NOT NULL)) — ikkala ustunni birga yozing.
Yechim
BEGIN;
CREATE TEMP TABLE bajarilganlar (matn text);
INSERT INTO bajarilganlar VALUES
('Universitet kontrakti #to''lov'),
('Yangi kitob o''qish #o''qish');
MERGE INTO vazifalar v
USING bajarilganlar b ON v.egasi_id = 7 AND v.matn = b.matn
WHEN MATCHED AND NOT v.bajarildi THEN
UPDATE SET bajarildi = true,
bajarilgan_vaqt = '2026-10-21 09:00+05'
WHEN NOT MATCHED THEN
INSERT (egasi_id, matn, bajarildi, bajarilgan_vaqt)
VALUES (7, b.matn, true, '2026-10-21 09:00+05')
RETURNING merge_action(), v.matn;
ROLLBACK;BEGIN
CREATE TABLE
INSERT 0 2
merge_action | matn
--------------+-------------------------------
UPDATE | Universitet kontrakti #to'lov
INSERT | Yangi kitob o'qish #o'qish
(2 rows)
MERGE 2
ROLLBACKWHEN MATCHED AND NOT v.bajarildi — allaqachon bajarilgan vazifaga tegilmaydi (vaqti o'zgarmaydi). Yangi qatorda bajarilgan_vaqt ni ham berdik — aks holda vazifalar_bajarilgan_vaqt_check buzilardi. Diqqat: yangi vazifaning #o'qish tegi teglar ga o'zi tushmaydi («Halol eslatma»).
4-mashq: SQL qadami — teglar (holat 23)
- Loyihangizning
sql/papkasiga006-teglar.sqlfaylini yarating:teglar,vazifa_teglari(tarkibiy PK(vazifa_id, teg_id), ikkala FKON DELETE CASCADE,teg_idindeksi),teglarni_ajrat()va ikkiINSERT ... ON CONFLICT DO NOTHING. - Bazani noldan quring (001–006) — xatosiz o'tsin.
- Tekshiring:
SELECT count(*) FROM teglar→ 32,vazifa_teglari→ 46. - Commit:
db: teglar (N:M) — matndagi #teglardan, ON CONFLICT bilan (006).
Yechim
Fayl tuzilmasi — «Teglar jadvali» va «Matndan teglar» bo'limlaridagi bloklar. vazifa_teglari ning to'liq ko'rinishi:
CREATE TABLE vazifa_teglari (
vazifa_id bigint NOT NULL,
teg_id bigint NOT NULL,
CONSTRAINT vazifa_teglari_pkey PRIMARY KEY (vazifa_id, teg_id),
CONSTRAINT vazifa_teglari_vazifa_id_fkey FOREIGN KEY (vazifa_id)
REFERENCES vazifalar (id) ON DELETE CASCADE,
CONSTRAINT vazifa_teglari_teg_id_fkey FOREIGN KEY (teg_id)
REFERENCES teglar (id) ON DELETE CASCADE
);
CREATE INDEX vazifa_teglari_teg_id_idx ON vazifa_teglari (teg_id);
INSERT INTO vazifa_teglari (vazifa_id, teg_id)
SELECT v.id, tg.id
FROM vazifalar v
CROSS JOIN LATERAL teglarni_ajrat(v.matn) AS t(nom)
JOIN teglar tg ON tg.egasi_id = v.egasi_id AND tg.nom = t.nom
ON CONFLICT DO NOTHING;Kanon holatida faylni ikki marta ishga tushirish ham xatosiz — ON CONFLICT DO NOTHING takroriy ishga tushirishni xavfsiz qiladi. Fayl qo'llangandan keyin o'zgartirilmaydi: keyingi o'zgarish — yangi raqamli fayl (007-...).
9. Real ishda
- Teglar, "like"lar, ko'rishlar soni — deyarli har ilovada UPSERT bor. "Avval tekshir, keyin qo'sh" kod review'da xato deb belgilanadi.
- Idempotent API: to'lov yoki buyurtmada
idempotency_keyustuni UNIQUE bo'ladi. Takroriy so'rovON CONFLICT DO NOTHINGbilan ikkinchi marta bajarilmaydi (25 va 28-qismlarda). - ORM'lar: Prisma'dagi
upsertva Drizzle'dagionConflictDoUpdateichida aynan shu SQL turadi (27-qism). SQL'ni bilsangiz, ORM nima qilayotganini tushunasiz. - MERGE — ma'lumot omborlari (ETL), import va kechki sinxron vazifalarida. Boshqa bazalardan (SQL Server, Oracle) ko'chib kelgan dasturchilar uni yaxshi biladi.
- Intervyu savoli: "UPSERT'ni qanday yozasiz va nega SELECT + INSERT yomon?" — javob: poyga holati va UNIQUE indeks orqali atomik tekshiruv.
Xulosa
- "Tekshir, keyin yoz" ikki so'rovda poyga holatini beradi —
SELECThali tasdiqlanmagan qatorni ko'rmaydi. INSERT ... ON CONFLICT (target) DO NOTHING | DO UPDATE— bitta atomik buyruq; target'ga UNIQUE indeks kerak.EXCLUDED— kira olmagan yangi qator;DO UPDATE ... WHERE— shart bilan yangilash.- UPSERT identity raqamlarini sarflaydi — id'lardagi teshiklar normal.
MERGE— qo'shish, yangilash va o'chirish birga; PG 17 daRETURNING merge_action()vaNOT MATCHED BY SOURCE. Parallel UPSERT uchun emas.vazifalarholat 23:teglar(32),vazifa_teglari(46),teglarni_ajrat().
Keyingi dars: JOIN'lar — teglar, vazifalar va foydalanuvchilarni bitta natijada birlashtiramiz.
Manbalar
- PostgreSQL 18 hujjati — INSERT, ON CONFLICT: https://www.postgresql.org/docs/18/sql-insert.html
- PostgreSQL 18 hujjati — MERGE: https://www.postgresql.org/docs/18/sql-merge.html
- PostgreSQL 17 reliz eslatmalari (MERGE RETURNING, WHEN NOT MATCHED BY SOURCE): https://www.postgresql.org/docs/17/release-17.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!