-- Creează tabela cu timestamp nullable
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
deleted_at TIMESTAMP WITH TIME ZONE DEFAULT NULL
);
-- Creăm un index parțial care ignoră rândurile șterse soft
CREATE UNIQUE INDEX users_active_email_idx
ON users (email)
WHERE deleted_at IS NULL;
-- Procesul de anonimizare GDPR (Rulează după perioada de grație)
UPDATE users
SET
email = 'deleted_' || id || '@yourdomain.internal',
name = 'Anonymous User',
deleted_at = NOW() -- marcăm și ca soft deleted dacă nu era deja
WHERE id = 1234;Am văzut prea des schema asta: se pornește un proiect nou, iar cineva zice „punem is_deleted boolean pe toate tabelele, să fim siguri că nu pierdem date”. Sună bine la cafea, dar e o decizie care te va costa scump când baza de date crește sau când primești prima solicitare oficială de ștergere a datelor pe GDPR.
La un CRM pentru un client din zona medicală, cu vreo 18.000 de pacienți activi, am făcut greșeala să aplic soft delete orbește. Ne-am trezit blocați în două probleme majore: indexurile de unicitate sparte și imposibilitatea legală de a păstra datele pacienților care cereau „dreptul de a fi uitat”.
Capcana indexurilor unice și a flag-ului boolean
Dacă ai o tabelă users cu un câmp email care trebuie să fie unic și folosești un flag boolean pentru soft delete, ai o problemă de logică.
Când utilizatorul popescu@gmail.com își șterge contul, setezi is_deleted = true. Ce se întâmplă când Popescu vrea să își facă un cont nou, o lună mai târziu, cu același email? Baza de date va arunca o eroare de constrângere de unicitate (Unique Constraint Violation). Popescu nu se poate înregistra, iar suportul tehnic te sună panicat.
Soluția elegantă în PostgreSQL sau MySQL este să renunți la booleeni și să folosești un timestamp nullable (deleted_at). În Postgres, poți crea un index parțial. Acesta garantează unicitatea doar pentru înregistrările active.
GDPR și conflictul cu soft delete
Din punct de vedere legal, soft delete nu înseamnă ștergere. Dacă un utilizator cere să fie șters conform GDPR, iar tu doar îi pui un flag în baza de date, ești pasibil de amendă. Datele lui personale (nume, prenume, telefon, IP) sunt încă în baza ta de date și pot fi accesate la o simplă interogare SQL.
Pe de altă parte, dacă faci hard delete (adică DELETE FROM users WHERE id = X), riști să distrugi integritatea referențială. Ce faci cu facturile, comenzile sau istoricul plăților asociate acelui user? Nu le poți șterge pe acelea, din motive fiscale.
Soluția de compromis: Anonimizarea
Pentru a împăca și capra și varza, am trecut la o strategie hibridă de anonimizare (scrubbing).
- Când utilizatorul apasă „Șterge contul” în UI, facem un soft delete rapid (setăm
deleted_at). Îi dăm o perioadă de grație de 14 zile în care își poate recupera datele. - După 14 zile, un worker de fundal rulează un proces de anonimizare. În loc să ștergem rândul physical, suprascriem datele cu caracter personal (PII) cu valori generate aleatoriu sau hash-uri, dar păstrăm ID-ul utilizatorului intact pentru ca cheile străine din tabelele de tranzacții să nu rămână orfane.
Merge excelent pentru păstrarea statisticilor financiare corecte, dar e nasol dacă ai un model de date foarte fragmentat, unde trebuie să vânezi PII-uri în zece tabele asociate.
Tu cum gestionezi ștergerile în proiectele mari? Mergi pe anonimizare sau te chinui cu cascade hard deletes?