Bu gün bizim məqsədimiz belə bir funksiya yazmaqdır ki:
- Müştərinin balansını yoxlayır. Məbləği silməzdən əvvəl kifayət qədər vəsait olub-olmadığını yoxlamaq lazımdır.
- Balansdan vəsaiti silir. Əgər balansda kifayət qədər pul varsa, silinir.
- Uğurlu və uğursuz əməliyyatları log-layır. Bütün hərəkətlər sonradan analiz üçün log cədvəlinə yazılır.
Bu, sadəcə darıxdırıcı çıxma funksiyası deyil. Burada biz iç-içə transaction-lardan istifadə edəcəyik ki, nəsə düz getməsə (məsələn, balansda pul çatmırsa və ya log yazarkən səhv çıxsa) dəyişiklikləri geri qaytaraq. SAVEPOINT nöqtələrinin faydasını görəcəyik və prosedurları səhvlərə davamlı etməyi öyrənəcəyik.
İlkin cədvəlləri yaradırıq
Funksiya yazmağa başlamazdan əvvəl gəlin bazanı hazırlayaq. Bizə üç cədvəl lazım olacaq:
clients— müştərilərin məlumatları və balansı üçün.payments— uğurlu transaction-ların yazılması üçün.logs— bütün ödəniş cəhdlərinin (uğurlu və uğursuz) saxlanması üçün.
-- Müştərilər cədvəli
CREATE TABLE clients (
client_id SERIAL PRIMARY KEY,
full_name TEXT NOT NULL,
balance NUMERIC(10, 2) NOT NULL DEFAULT 0
);
-- Uğurlu ödənişlər cədvəli
CREATE TABLE payments (
payment_id SERIAL PRIMARY KEY,
client_id INT NOT NULL REFERENCES clients(client_id),
amount NUMERIC(10, 2) NOT NULL,
payment_date TIMESTAMP DEFAULT NOW()
);
-- Log cədvəli
CREATE TABLE logs (
log_id SERIAL PRIMARY KEY,
client_id INT NOT NULL REFERENCES clients(client_id),
message TEXT NOT NULL,
log_date TIMESTAMP DEFAULT NOW()
);
clients cədvəlini test dataları ilə dolduraq
INSERT INTO clients (full_name, balance)
VALUES
('Otto Song', 100.00),
('Maria Chi', 50.00),
('Anna Vel', 0.00);
İndi bizdə üç müştəri var: Otto-nun hesabında 100, Maria-da — 50, Anna-da isə — 0 var.
Biznes-məntiqin reallaşdırılması: PROSEDUR vs FUNKSIYA
Qısa olaraq:
- "Hamısı və ya heç nə" tipli biznes əməliyyatları üçün funksiya kifayətdir.
- Transaction-lara mərhələli nəzarət, qismən commit, rollback, error log-lama üçün — prosedur (
CREATE PROCEDURE) istifadə et.
Niyə funksiya yox? Çünki PostgreSQL 17-də funksiya daxilində nə COMMIT, nə SAVEPOINT, nə də ROLLBACK istifadə edə bilməzsən. Bütün dəyişikliklər xarici transaction daxilində atomik olur.
Yalnız prosedur (CREATE PROCEDURE ... LANGUAGE plpgsql) SAVEPOINT, COMMIT, ROLLBACK istifadə etməyə imkan verir — amma vacib məhdudiyyətlərlə:
- Prosedur daxilində
SAVEPOINT,COMMIT,RELEASE SAVEPOINTicazəlidir. ROLLBACK TO SAVEPOINTPL/pgSQL-də qadağandır (səhv çıxacaq), bunun əvəzinəBEGIN ... EXCEPTION ... ENDbloklarından istifadə olunur, hansı ki "virtual savepoint" yaradır.
Kodun bir hissəsini geri qaytarmaq üçün əsas texnika:
BEGIN
-- sənin kodun
EXCEPTION
WHEN OTHERS THEN
-- Bu blokda səhv olsa, DAXİLİNDƏKİ bütün dəyişikliklər geri qaytarılır!
-- Log-da məlumat saxlamaq olar:
INSERT INTO logs (...) VALUES (...);
END;
Qismən rollback və log-lama ilə ödəniş proseduru yaradırıq
CREATE OR REPLACE PROCEDURE process_payment(
in_client_id INT,
in_payment_amount NUMERIC
)
LANGUAGE plpgsql
AS $$
DECLARE
current_balance NUMERIC;
BEGIN
-- Müştərinin balansını alırıq
SELECT balance INTO current_balance
FROM clients
WHERE client_id = in_client_id;
IF NOT FOUND THEN
INSERT INTO logs (client_id, message)
VALUES (in_client_id, 'Müştəri tapılmadı, əməliyyat rədd edildi');
RAISE EXCEPTION 'ID-si % olan müştəri tapılmadı', in_client_id;
END IF;
-- Vəsaitin kifayətliliyini yoxlayırıq
IF current_balance < in_payment_amount THEN
INSERT INTO logs (client_id, message)
VALUES (in_client_id, 'Silinmək üçün kifayət qədər vəsait yoxdur ' || in_payment_amount || ' azn.');
-- Proseduru bitiririk
RETURN;
END IF;
-- Atomik dəyişikliklər üçün blok; səhv olsa — rollback (virtual savepoint)
BEGIN
-- Balansı silirik
UPDATE clients
SET balance = balance - in_payment_amount
WHERE client_id = in_client_id;
-- Uğurlu ödənişi əlavə edirik
INSERT INTO payments (client_id, amount)
VALUES (in_client_id, in_payment_amount);
-- Uğuru log-layırıq
INSERT INTO logs (client_id, message)
VALUES (in_client_id, 'Uğurlu silinmə ' || in_payment_amount || ' azn.');
EXCEPTION
WHEN OTHERS THEN
-- Bu blokdakı bütün dəyişikliklər ləğv olunur
INSERT INTO logs (client_id, message)
VALUES (in_client_id, 'Ödəniş zamanı səhv: ' || SQLERRM);
-- (açıq ROLLBACK TO SAVEPOINT lazım deyil — qadağandır və lazım deyil)
END;
END;
$$;
Qısa olaraq nə baş verir:
- Əgər pul azdır/müştəri yoxdur — log-layırıq və çıxırıq.
- Bütün kritik kod
BEGIN ... EXCEPTION ... ENDblokuna qoyulub. - Bu blokda istənilən səhv olsa — bütün dəyişikliklər avtomatik geri qaytarılır; səhv log-a yazılır.
- Birbaşa
SAVEPOINTvəROLLBACK TO SAVEPOINTistifadə olunmur — belə də olmalıdır, PL/pgSQL-də yalnız EXCEPTION blokları ilə işləyir.
Prosedurun çağırılması
Vacibdir: proseduru CALL ... komandası ilə çağırmaq lazımdır, üstəlik bazaya xarici bağlantı autocommit rejimində və ya açıq böyük transaction xaricində olmalıdır!
CALL process_payment(1, 30.00); -- Uğurlu ödəniş
CALL process_payment(2, 100.00); -- Kifayət qədər vəsait yoxdur
CALL process_payment(999, 50.00); -- Müştəri yoxdur
Nəticələrin yoxlanması
- Müştərinin balansı yalnız ödəniş keçərsə dəyişir.
- payments cədvəli — yalnız uğurlu silinmədə qeyd olur.
- logs — bütün cəhdlərin (və səhvlərin) tarixi.
SELECT * FROM clients;
SELECT * FROM payments;
SELECT * FROM logs;
Real tətbiq
Transaction-ların işlənməsi üçün prosedurlar — fintech, e-commerce və hətta oyun platformalarında əsas hissələrdən biridir. Təsəvvür elə, internet mağazası var, hansı ki, hədiyyə sertifikatlarının balansını nəzərə almalı və alış zamanı onları silməlidir, ya da bank sistemi minlərlə əməliyyatı saniyədə emal edir.
Bu biliklər sənə praktikada lazım olacaq, müştərilərinin məlumatlarını qorumağa və ödənişlərin işlənməsində fəlakətli səhvlərdən qaçmağa kömək edəcək.
GO TO FULL VERSION