Skip to content

Latest commit

 

History

History
1024 lines (784 loc) · 28.8 KB

File metadata and controls

1024 lines (784 loc) · 28.8 KB

🔝 Retour au Sommaire

4.4.2. Types Texte (VARCHAR, TEXT, CHAR)

Introduction

Le texte est l'un des types de données les plus utilisés dans les bases de données. Que ce soit pour stocker des noms, des descriptions, des emails, ou du contenu long, PostgreSQL offre plusieurs types de données textuelles adaptés à différents besoins.

Dans cette section, nous allons explorer :

  • VARCHAR(n) : Texte de longueur variable limitée
  • TEXT : Texte de longueur illimitée
  • CHAR(n) : Texte de longueur fixe
  • Les différences entre ces types et quand les utiliser
  • Les opérations courantes sur le texte

Vue d'Ensemble des Types Texte

PostgreSQL propose trois types principaux pour stocker du texte :

Type Syntaxe Longueur Maximum Stockage Utilisation
VARCHAR(n) VARCHAR(100) n caractères (max 1 Go) Variable Standard : noms, emails, descriptions courtes
TEXT TEXT Illimitée (1 Go) Variable Texte long : articles, commentaires, contenu
CHAR(n) CHAR(10) Exactement n caractères Fixe (complété par espaces) Codes fixes : codes postaux, codes pays

Point important : Dans PostgreSQL, il n'y a presque aucune différence de performance entre VARCHAR, TEXT et CHAR. Le choix dépend surtout de la logique métier et de la validation des données.


1. VARCHAR (Variable Character)

Définition et Syntaxe

VARCHAR(n) stocke une chaîne de caractères de longueur variable avec une limite maximale de n caractères.

-- Syntaxe
VARCHAR(n)  -- ou CHARACTER VARYING(n)

-- Exemples de déclaration
nom VARCHAR(100)      -- Maximum 100 caractères  
email VARCHAR(255)    -- Maximum 255 caractères  
description VARCHAR(500)  -- Maximum 500 caractères  

Comportement de VARCHAR

CREATE TABLE utilisateurs (
    id SERIAL PRIMARY KEY,
    prenom VARCHAR(50),
    nom VARCHAR(50),
    email VARCHAR(255),
    biographie VARCHAR(500)
);

-- Insertion normale
INSERT INTO utilisateurs (prenom, nom, email, biographie)  
VALUES ('Alice', 'Martin', 'alice@example.com', 'Développeuse passionnée par PostgreSQL');  

-- Insertion avec texte court (OK)
INSERT INTO utilisateurs (prenom, nom, email)  
VALUES ('Bob', 'Dupont', 'bob@example.com');  
-- La biographie est NULL

-- Tentative d'insertion avec texte trop long
INSERT INTO utilisateurs (prenom, nom, email)  
VALUES ('Un prénom vraiment beaucoup trop long qui dépasse largement la limite', 'Nom', 'test@example.com');  
-- ERROR: value too long for type character varying(50)

SELECT * FROM utilisateurs;

VARCHAR sans Limite

Vous pouvez omettre la limite, ce qui équivaut à TEXT :

-- VARCHAR sans limite = TEXT
CREATE TABLE test_varchar (
    texte1 VARCHAR,  -- Équivalent à TEXT
    texte2 TEXT
);

-- Ces deux colonnes se comportent identiquement
INSERT INTO test_varchar VALUES ('Texte long...', 'Texte long...');

Recommandation : Spécifiez toujours une limite pour VARCHAR pour :

  • Valider les données à l'insertion
  • Documenter les contraintes métier
  • Faciliter la compréhension du schéma

Cas d'Usage de VARCHAR

1. Noms et Prénoms

CREATE TABLE personnes (
    id SERIAL PRIMARY KEY,
    prenom VARCHAR(100) NOT NULL,
    nom VARCHAR(100) NOT NULL,
    nom_complet VARCHAR(200) GENERATED ALWAYS AS (prenom || ' ' || nom) STORED
);

INSERT INTO personnes (prenom, nom) VALUES ('Marie', 'Curie');  
INSERT INTO personnes (prenom, nom) VALUES ('Albert', 'Einstein');  

SELECT * FROM personnes;

2. Emails et URLs

CREATE TABLE contacts (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,  -- Standard : 255 caractères
    site_web VARCHAR(500),

    -- Validation de l'email
    CONSTRAINT ck_email_format CHECK (email LIKE '%@%.%')
);

INSERT INTO contacts (email, site_web)  
VALUES ('contact@example.com', 'https://www.example.com');  

-- Tentative avec email invalide
INSERT INTO contacts (email) VALUES ('email_invalide');
-- ERROR: violates check constraint "ck_email_format"

3. Codes et Identifiants

CREATE TABLE produits (
    id SERIAL PRIMARY KEY,
    reference VARCHAR(20) UNIQUE NOT NULL,  -- Ex: PROD-2025-001
    nom VARCHAR(200) NOT NULL,
    code_ean VARCHAR(13),  -- Code-barres EAN-13

    CONSTRAINT ck_reference_format CHECK (reference ~ '^PROD-[0-9]{4}-[0-9]{3}$')
);

INSERT INTO produits (reference, nom, code_ean)  
VALUES ('PROD-2025-001', 'Ordinateur portable', '3760123456789');  

4. Descriptions Courtes

CREATE TABLE taches (
    id SERIAL PRIMARY KEY,
    titre VARCHAR(200) NOT NULL,
    description VARCHAR(1000),  -- Description courte
    statut VARCHAR(20) DEFAULT 'A_FAIRE'
        CHECK (statut IN ('A_FAIRE', 'EN_COURS', 'TERMINE'))
);

INSERT INTO taches (titre, description)  
VALUES ('Apprendre PostgreSQL', 'Lire la documentation sur les types de données');  

2. TEXT (Texte Illimité)

Définition et Syntaxe

TEXT stocke une chaîne de caractères de longueur illimitée (jusqu'à environ 1 Go en pratique).

-- Syntaxe
TEXT

-- Exemple de déclaration
contenu TEXT  
commentaire TEXT  
article_complet TEXT  

Comportement de TEXT

CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    titre VARCHAR(300) NOT NULL,
    resume VARCHAR(500),
    contenu TEXT NOT NULL,  -- Texte long sans limite
    auteur VARCHAR(100),
    date_publication TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Insérer un article avec un long contenu
INSERT INTO articles (titre, resume, contenu, auteur)  
VALUES (  
    'Introduction à PostgreSQL',
    'Un guide complet pour débuter avec PostgreSQL',
    'PostgreSQL est un système de gestion de base de données... [contenu très long]...',
    'Alice Martin'
);

-- TEXT peut stocker des textes de plusieurs pages
INSERT INTO articles (titre, contenu, auteur)  
VALUES (  
    'Guide Complet PostgreSQL',
    repeat('PostgreSQL est formidable. ', 10000),  -- Texte répété 10 000 fois
    'Bob Dupont'
);

SELECT
    id,
    titre,
    LENGTH(contenu) AS longueur_caracteres,
    pg_size_pretty(pg_column_size(contenu)) AS taille_stockage
FROM articles;

TEXT vs VARCHAR

Dans PostgreSQL, TEXT et VARCHAR (sans limite) sont presque identiques en termes de performances :

-- Comparaison
CREATE TABLE comparaison_texte (
    id SERIAL PRIMARY KEY,
    colonne_text TEXT,
    colonne_varchar VARCHAR
);

-- Les deux se comportent de la même manière
INSERT INTO comparaison_texte (colonne_text, colonne_varchar)  
VALUES ('Texte identique', 'Texte identique');  

-- Aucune différence de performance ou de stockage
SELECT
    pg_column_size(colonne_text) AS taille_text,
    pg_column_size(colonne_varchar) AS taille_varchar
FROM comparaison_texte;
-- Les deux colonnes ont la même taille

La différence principale : TEXT n'a pas de limite, VARCHAR(n) valide la longueur.

Cas d'Usage de TEXT

1. Contenu de Blog ou CMS

CREATE TABLE posts_blog (
    id SERIAL PRIMARY KEY,
    titre VARCHAR(300) NOT NULL,
    slug VARCHAR(300) UNIQUE NOT NULL,
    contenu TEXT NOT NULL,  -- Article complet
    contenu_html TEXT,      -- Version HTML
    meta_description VARCHAR(160),  -- Pour SEO
    auteur_id INTEGER NOT NULL,
    statut VARCHAR(20) DEFAULT 'brouillon',
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO posts_blog (titre, slug, contenu, auteur_id)  
VALUES (  
    'Mon Premier Article',
    'mon-premier-article',
    E'# Introduction\n\nCeci est mon premier article de blog.\n\n## Section 1\n\nContenu détaillé...',
    1
);

2. Commentaires et Avis

CREATE TABLE commentaires (
    id SERIAL PRIMARY KEY,
    article_id INTEGER NOT NULL,
    auteur VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL,
    commentaire TEXT NOT NULL,  -- Commentaire de longueur variable
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    est_approuve BOOLEAN DEFAULT FALSE
);

INSERT INTO commentaires (article_id, auteur, email, commentaire)  
VALUES (  
    1,
    'Jean Dupont',
    'jean@example.com',
    'Excellent article ! J''ai particulièrement apprécié la section sur les types de données. Les exemples sont clairs et bien expliqués.'
);

3. Logs et Traces

CREATE TABLE logs_application (
    id BIGSERIAL PRIMARY KEY,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    niveau VARCHAR(10),  -- DEBUG, INFO, WARNING, ERROR
    message TEXT,        -- Message de log (peut être long)
    stack_trace TEXT,    -- Trace d'erreur complète
    contexte JSONB       -- Contexte additionnel
);

INSERT INTO logs_application (niveau, message, stack_trace)  
VALUES (  
    'ERROR',
    'Erreur lors de la connexion à la base de données',
    E'Exception in thread "main"...\n  at com.example.Database.connect()\n  at com.example.Main.main()'
);

4. Documents et Notes

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    titre VARCHAR(300) NOT NULL,
    type VARCHAR(50),  -- markdown, html, plain
    contenu TEXT NOT NULL,  -- Document complet
    proprietaire_id INTEGER NOT NULL,
    taille_caracteres INTEGER GENERATED ALWAYS AS (LENGTH(contenu)) STORED,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_modification TIMESTAMP
);

INSERT INTO documents (titre, type, contenu, proprietaire_id)  
VALUES (  
    'Notes de Réunion - 2025-11-19',
    'markdown',
    E'# Réunion d''équipe\n\n## Présents\n- Alice\n- Bob\n\n## Points discutés\n1. Migration PostgreSQL\n2. Nouveaux projets',
    1
);

3. CHAR (Character - Longueur Fixe)

Définition et Syntaxe

CHAR(n) stocke une chaîne de caractères de longueur fixe de exactement n caractères. Si le texte est plus court, PostgreSQL le complète avec des espaces.

-- Syntaxe
CHAR(n)  -- ou CHARACTER(n)

-- Exemples
code_pays CHAR(2)      -- Toujours 2 caractères : 'FR', 'US'  
code_postal CHAR(5)    -- Toujours 5 caractères : '75001'  
oui_non CHAR(1)        -- Un seul caractère : 'O' ou 'N'  

Comportement de CHAR

CREATE TABLE demo_char (
    id SERIAL PRIMARY KEY,
    code_fixe CHAR(5),
    texte_variable VARCHAR(5)
);

-- Insérer des valeurs de différentes longueurs
INSERT INTO demo_char (code_fixe, texte_variable) VALUES ('ABC', 'ABC');  
INSERT INTO demo_char (code_fixe, texte_variable) VALUES ('ABCDE', 'ABCDE');  
INSERT INTO demo_char (code_fixe, texte_variable) VALUES ('A', 'A');  

-- Voir le contenu réel avec LENGTH()
SELECT
    code_fixe,
    texte_variable,
    LENGTH(code_fixe) AS longueur_char,          -- Toujours sans espaces trailing
    LENGTH(texte_variable) AS longueur_varchar,
    pg_column_size(code_fixe) AS taille_char,    -- Stockage avec espaces
    pg_column_size(texte_variable) AS taille_varchar
FROM demo_char;

Résultat :

 code_fixe | texte_variable | longueur_char | longueur_varchar | taille_char | taille_varchar
-----------+----------------+---------------+------------------+-------------+----------------
 ABC       | ABC            |             3 |                3 |           6 |              4
 ABCDE     | ABCDE          |             5 |                5 |           6 |              6
 A         | A              |             1 |                1 |           6 |              2

Observation importante :

  • CHAR(5) stocke toujours 5 caractères (+ 1 octet d'overhead) = 6 octets
  • Les espaces sont ajoutés mais supprimés lors de la récupération
  • VARCHAR stocke seulement les caractères nécessaires

Problème avec les Espaces

Les espaces ajoutés par CHAR peuvent causer des problèmes :

CREATE TABLE test_comparaison (
    code_char CHAR(10),
    code_varchar VARCHAR(10)
);

INSERT INTO test_comparaison VALUES ('ABC', 'ABC');

-- Comparaison
SELECT
    code_char = 'ABC' AS char_egal,           -- true
    code_varchar = 'ABC' AS varchar_egal,     -- true
    code_char = 'ABC      ' AS char_espaces,  -- true (espaces ignorés)
    LENGTH(code_char) AS len_char,            -- 3 (espaces trailing supprimés)
    LENGTH(code_varchar) AS len_varchar       -- 3
FROM test_comparaison;

-- Concaténation révèle le problème
SELECT
    '[' || code_char || ']' AS char_concat,      -- [ABC       ] (espaces visibles)
    '[' || code_varchar || ']' AS varchar_concat  -- [ABC]
FROM test_comparaison;

Cas d'Usage de CHAR

CHAR est rarement recommandé en PostgreSQL car il n'offre aucun avantage de performance sur VARCHAR. Cependant, il peut être utilisé pour :

1. Codes de Longueur Fixe

CREATE TABLE pays (
    code CHAR(2) PRIMARY KEY,  -- ISO 3166-1 alpha-2 : 'FR', 'US', 'JP'
    nom VARCHAR(100) NOT NULL,
    code_alpha3 CHAR(3),       -- ISO 3166-1 alpha-3 : 'FRA', 'USA', 'JPN'
    code_numerique CHAR(3)     -- ISO 3166-1 numeric : '250', '840', '392'
);

INSERT INTO pays (code, nom, code_alpha3, code_numerique)  
VALUES ('FR', 'France', 'FRA', '250');  

INSERT INTO pays (code, nom, code_alpha3, code_numerique)  
VALUES ('US', 'États-Unis', 'USA', '840');  

SELECT * FROM pays;

2. Flags et Indicateurs Binaires

CREATE TABLE utilisateurs_flags (
    id SERIAL PRIMARY KEY,
    nom VARCHAR(100),
    est_actif CHAR(1) CHECK (est_actif IN ('O', 'N')),  -- 'O' = Oui, 'N' = Non
    genre CHAR(1) CHECK (genre IN ('M', 'F', 'A')),     -- M/F/Autre

    -- Mais préférez BOOLEAN pour les flags !
    est_actif_bool BOOLEAN  -- Plus clair et idiomatique
);

-- Insertion
INSERT INTO utilisateurs_flags (nom, est_actif, genre, est_actif_bool)  
VALUES ('Alice', 'O', 'F', TRUE);  

Recommandation : Utilisez BOOLEAN au lieu de CHAR(1) pour les flags.

3. Codes Postaux (avec Précaution)

-- Pour des codes postaux de longueur fixe
CREATE TABLE adresses_usa (
    id SERIAL PRIMARY KEY,
    rue VARCHAR(200),
    ville VARCHAR(100),
    etat CHAR(2),        -- Codes d'état US : 'CA', 'NY', 'TX'
    code_postal CHAR(5)  -- ZIP codes US : '90210', '10001'
);

-- Mais attention : certains codes postaux ont des formats variables
CREATE TABLE adresses_internationales (
    id SERIAL PRIMARY KEY,
    pays CHAR(2),
    code_postal VARCHAR(10)  -- Plus flexible : '75001', 'SW1A 1AA', 'K1A 0B1'
);

Comparaison des Types Texte

Tableau Comparatif

Aspect VARCHAR(n) TEXT CHAR(n)
Longueur Variable, max n Illimitée Fixe, exactement n
Stockage Longueur réelle + overhead Longueur réelle + overhead n caractères + overhead
Performance Identique à TEXT Identique à VARCHAR Identique (pas d'avantage)
Validation Limite la longueur Aucune validation Longueur exacte (complète avec espaces)
Utilisation Standard Texte long Codes fixes (rare)

Test de Performance

-- Créer trois tables identiques avec types différents
CREATE TABLE test_varchar (id SERIAL PRIMARY KEY, texte VARCHAR(100));  
CREATE TABLE test_text (id SERIAL PRIMARY KEY, texte TEXT);  
CREATE TABLE test_char (id SERIAL PRIMARY KEY, texte CHAR(100));  

-- Insérer les mêmes données
INSERT INTO test_varchar (texte) SELECT 'Test ' || i FROM generate_series(1, 10000) i;  
INSERT INTO test_text (texte) SELECT 'Test ' || i FROM generate_series(1, 10000) i;  
INSERT INTO test_char (texte) SELECT 'Test ' || i FROM generate_series(1, 10000) i;  

-- Comparer les tailles
SELECT
    pg_size_pretty(pg_total_relation_size('test_varchar')) AS taille_varchar,
    pg_size_pretty(pg_total_relation_size('test_text')) AS taille_text,
    pg_size_pretty(pg_total_relation_size('test_char')) AS taille_char;

Résultat typique : Les tailles sont très similaires, avec CHAR légèrement plus grand à cause du padding.


Opérations Courantes sur le Texte

Concaténation

-- Opérateur || (recommandé)
SELECT 'Hello' || ' ' || 'World';  -- 'Hello World'

-- Fonction CONCAT (gère NULL automatiquement)
SELECT CONCAT('Hello', ' ', 'World');  -- 'Hello World'  
SELECT CONCAT('Hello', NULL, 'World'); -- 'HelloWorld' (NULL ignoré)  

-- CONCAT_WS (avec séparateur)
SELECT CONCAT_WS(', ', 'Paris', 'France', 'Europe');  -- 'Paris, France, Europe'

-- Exemple pratique
SELECT
    CONCAT(prenom, ' ', nom) AS nom_complet,
    CONCAT(email, ' (', telephone, ')') AS contact
FROM utilisateurs;

Longueur

-- LENGTH : Nombre de caractères
SELECT LENGTH('Hello');  -- 5  
SELECT LENGTH('Bonjour');  -- 7  
SELECT LENGTH('');  -- 0  
SELECT LENGTH(NULL);  -- NULL  

-- CHAR_LENGTH : Alias de LENGTH
SELECT CHAR_LENGTH('Hello');  -- 5

-- OCTET_LENGTH : Taille en octets (important pour UTF-8)
SELECT OCTET_LENGTH('Hello');  -- 5  
SELECT OCTET_LENGTH('Héllo');  -- 6 (é = 2 octets en UTF-8)  
SELECT OCTET_LENGTH('你好');    -- 6 (chaque caractère chinois = 3 octets)  

Casse (Majuscules/Minuscules)

-- UPPER : Convertir en majuscules
SELECT UPPER('hello world');  -- 'HELLO WORLD'  
SELECT UPPER('bonjour');      -- 'BONJOUR'  

-- LOWER : Convertir en minuscules
SELECT LOWER('HELLO WORLD');  -- 'hello world'  
SELECT LOWER('BONJOUR');      -- 'bonjour'  

-- INITCAP : Première lettre en majuscule
SELECT INITCAP('hello world');  -- 'Hello World'  
SELECT INITCAP('alice martin'); -- 'Alice Martin'  

-- Exemple pratique : recherche insensible à la casse
SELECT * FROM utilisateurs  
WHERE LOWER(email) = LOWER('Alice@Example.COM');  

Extraction de Sous-Chaînes

-- SUBSTRING : Extraire une partie
SELECT SUBSTRING('PostgreSQL' FROM 1 FOR 6);  -- 'Postgr'  
SELECT SUBSTRING('PostgreSQL', 1, 6);         -- 'Postgr' (syntaxe alternative)  
SELECT SUBSTRING('PostgreSQL', 7);            -- 'SQL' (du 7e caractère à la fin)  

-- LEFT : Extraire depuis la gauche
SELECT LEFT('PostgreSQL', 4);  -- 'Post'

-- RIGHT : Extraire depuis la droite
SELECT RIGHT('PostgreSQL', 3);  -- 'SQL'

-- Exemple : Extraire l'initiale
SELECT
    prenom,
    LEFT(prenom, 1) || '.' AS initiale
FROM personnes;

Recherche et Remplacement

-- POSITION : Trouver la position d'une sous-chaîne
SELECT POSITION('SQL' IN 'PostgreSQL');  -- 7  
SELECT POSITION('xyz' IN 'PostgreSQL');  -- 0 (pas trouvé)  

-- STRPOS : Alias de POSITION
SELECT STRPOS('PostgreSQL', 'SQL');  -- 7

-- REPLACE : Remplacer du texte
SELECT REPLACE('Hello World', 'World', 'PostgreSQL');  -- 'Hello PostgreSQL'  
SELECT REPLACE('aaa bbb aaa', 'aaa', 'xxx');  -- 'xxx bbb xxx'  

-- Exemple : Nettoyer des données
UPDATE utilisateurs  
SET telephone = REPLACE(REPLACE(REPLACE(telephone, ' ', ''), '-', ''), '.', '')  
WHERE telephone LIKE '%[- .]%';  

Suppression d'Espaces

-- TRIM : Supprimer espaces de début et fin
SELECT TRIM('  Hello  ');  -- 'Hello'

-- LTRIM : Supprimer espaces à gauche
SELECT LTRIM('  Hello  ');  -- 'Hello  '

-- RTRIM : Supprimer espaces à droite
SELECT RTRIM('  Hello  ');  -- '  Hello'

-- TRIM avec caractère spécifique
SELECT TRIM('x' FROM 'xxxHelloxxx');  -- 'Hello'

-- Exemple pratique : Nettoyer les données
UPDATE produits  
SET nom = TRIM(nom)  
WHERE nom != TRIM(nom);  

Remplissage (Padding)

-- LPAD : Remplir à gauche
SELECT LPAD('42', 5, '0');  -- '00042'  
SELECT LPAD('SQL', 10, '*');  -- '*******SQL'  

-- RPAD : Remplir à droite
SELECT RPAD('42', 5, '0');  -- '42000'  
SELECT RPAD('SQL', 10, '*');  -- 'SQL*******'  

-- Exemple : Formater des codes
SELECT
    id,
    LPAD(id::TEXT, 6, '0') AS code_formate
FROM produits;
-- Résultat : 1 → '000001', 42 → '000042'

Répétition

-- REPEAT : Répéter une chaîne
SELECT REPEAT('*', 10);  -- '**********'  
SELECT REPEAT('Hello ', 3);  -- 'Hello Hello Hello '  

-- Exemple : Générer des données de test
INSERT INTO test_table (texte)  
SELECT REPEAT('x', 1000);  -- Insérer 1000 caractères 'x'  

Division en Tableaux

-- STRING_TO_ARRAY : Diviser une chaîne
SELECT STRING_TO_ARRAY('a,b,c,d', ',');  -- {'a','b','c','d'}  
SELECT STRING_TO_ARRAY('one two three', ' ');  -- {'one','two','three'}  

-- ARRAY_TO_STRING : Joindre un tableau
SELECT ARRAY_TO_STRING(ARRAY['a', 'b', 'c'], ',');  -- 'a,b,c'

-- Exemple pratique : Traiter une liste de tags
SELECT
    id,
    titre,
    STRING_TO_ARRAY(tags, ',') AS tags_array
FROM articles  
WHERE tags IS NOT NULL;  

Validation et Contraintes sur le Texte

Contraintes CHECK avec Pattern

CREATE TABLE utilisateurs_valides (
    id SERIAL PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,

    -- Email valide (basique)
    email VARCHAR(255) NOT NULL UNIQUE
        CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),

    -- Téléphone français (format 06 ou 07)
    telephone VARCHAR(20)
        CHECK (telephone ~ '^0[67][0-9]{8}$'),

    -- Code postal français (5 chiffres)
    code_postal VARCHAR(5)
        CHECK (code_postal ~ '^[0-9]{5}$'),

    -- URL valide
    site_web VARCHAR(500)
        CHECK (site_web ~ '^https?://.*')
);

-- Insertion valide
INSERT INTO utilisateurs_valides (nom, email, telephone, code_postal, site_web)  
VALUES ('Alice', 'alice@example.com', '0612345678', '75001', 'https://alice.com');  

-- Insertion invalide (email)
INSERT INTO utilisateurs_valides (nom, email)  
VALUES ('Bob', 'email_invalide');  
-- ERROR: violates check constraint

Contraintes de Longueur

CREATE TABLE produits_contraintes (
    id SERIAL PRIMARY KEY,

    -- Nom : entre 3 et 200 caractères
    nom VARCHAR(200) NOT NULL
        CHECK (LENGTH(TRIM(nom)) >= 3),

    -- Description : entre 10 et 1000 caractères
    description TEXT
        CHECK (LENGTH(description) BETWEEN 10 AND 1000),

    -- Code : exactement 8 caractères
    code VARCHAR(8) UNIQUE
        CHECK (LENGTH(code) = 8)
);

-- Insertion valide
INSERT INTO produits_contraintes (nom, description, code)  
VALUES (  
    'Ordinateur Portable',
    'Ordinateur portable haute performance avec écran 15 pouces',
    'PROD0001'
);

-- Insertion invalide (nom trop court)
INSERT INTO produits_contraintes (nom, description, code)  
VALUES ('PC', 'Description...', 'PROD0002');  
-- ERROR: violates check constraint

Contraintes avec Listes de Valeurs

CREATE TABLE articles_statuts (
    id SERIAL PRIMARY KEY,
    titre VARCHAR(300) NOT NULL,

    -- Statut : seulement certaines valeurs
    statut VARCHAR(20) DEFAULT 'brouillon'
        CHECK (statut IN ('brouillon', 'en_revision', 'publie', 'archive')),

    -- Langue : codes ISO 639-1
    langue CHAR(2) DEFAULT 'fr'
        CHECK (langue IN ('fr', 'en', 'es', 'de', 'it')),

    -- Catégorie
    categorie VARCHAR(50)
        CHECK (categorie IN ('technologie', 'science', 'culture', 'sport'))
);

Bonnes Pratiques avec les Types Texte

1. Choisir le Bon Type

-- ✅ BON : Types appropriés
CREATE TABLE utilisateurs_correct (
    id SERIAL PRIMARY KEY,
    prenom VARCHAR(100) NOT NULL,        -- Nom : limité et validé
    nom VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,  -- Email : standard 255
    biographie TEXT,                     -- Biographie : peut être longue
    code_postal VARCHAR(10)              -- Variable selon pays
);

-- ❌ MAUVAIS : Types inadaptés
CREATE TABLE utilisateurs_mauvais (
    id SERIAL PRIMARY KEY,
    prenom CHAR(50),        -- CHAR pour un nom ? Non !
    email VARCHAR(50),      -- Trop court pour un email
    biographie VARCHAR(100) -- Trop court pour une biographie
);

2. Toujours Utiliser VARCHAR avec Limite

-- ✅ BON : Limite définie
CREATE TABLE produits (
    nom VARCHAR(200) NOT NULL,
    description VARCHAR(500)
);

-- ❓ ACCEPTABLE mais moins bon
CREATE TABLE produits_vague (
    nom VARCHAR,  -- Pas de limite = TEXT déguisé
    description TEXT
);

3. TEXT pour le Contenu Long

-- ✅ BON : TEXT pour contenu variable et long
CREATE TABLE blog_posts (
    titre VARCHAR(300) NOT NULL,
    contenu TEXT NOT NULL,  -- Article complet
    commentaires TEXT       -- Commentaires des lecteurs
);

-- ❌ MAUVAIS : VARCHAR pour contenu long
CREATE TABLE blog_posts_mauvais (
    contenu VARCHAR(5000)  -- Limite arbitraire et insuffisante
);

4. Éviter CHAR Sauf Cas Spécifiques

-- ✅ ACCEPTABLE : Codes de longueur fixe
CREATE TABLE codes (
    code_pays CHAR(2),     -- 'FR', 'US'
    code_devise CHAR(3)    -- 'EUR', 'USD'
);

-- ❌ MAUVAIS : CHAR pour données variables
CREATE TABLE mauvais_usage_char (
    nom CHAR(100),  -- Gaspille de l'espace si nom court
    email CHAR(255) -- Idem
);

-- ✅ MIEUX : VARCHAR pour données variables
CREATE TABLE bon_usage (
    nom VARCHAR(100),
    email VARCHAR(255)
);

5. Valider les Données avec CHECK

-- ✅ BON : Validation des formats
CREATE TABLE contacts_valides (
    email VARCHAR(255) CHECK (email ~* '^[^@]+@[^@]+\.[^@]+$'),
    telephone VARCHAR(20) CHECK (telephone ~ '^[0-9+\- ]+$'),
    code_postal VARCHAR(10) CHECK (code_postal ~ '^[0-9]{5}$')
);

6. Nettoyer les Données à l'Insertion

-- Utiliser TRIM pour nettoyer
CREATE TABLE utilisateurs_propres (
    nom VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL
);

-- Trigger pour nettoyer automatiquement
CREATE OR REPLACE FUNCTION nettoyer_texte()  
RETURNS TRIGGER AS $$  
BEGIN  
    NEW.nom = TRIM(NEW.nom);
    NEW.email = LOWER(TRIM(NEW.email));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tg_nettoyer_utilisateurs  
BEFORE INSERT OR UPDATE ON utilisateurs_propres  
FOR EACH ROW EXECUTE FUNCTION nettoyer_texte();  

-- Ou nettoyer manuellement
INSERT INTO utilisateurs_propres (nom, email)  
VALUES (TRIM('  Alice  '), LOWER(TRIM('  Alice@Example.COM  ')));  

7. Indexer les Colonnes Texte Recherchées

-- Index pour recherches exactes
CREATE INDEX idx_email ON utilisateurs(email);

-- Index pour recherches insensibles à la casse
CREATE INDEX idx_email_lower ON utilisateurs(LOWER(email));

-- Index pour recherches de préfixe
CREATE INDEX idx_nom_prefix ON utilisateurs(nom text_pattern_ops);

-- Index pour recherche full-text (avancé)
CREATE INDEX idx_contenu_fulltext ON articles USING GIN(to_tsvector('french', contenu));

Encodage et Internationalisation

Gestion de l'UTF-8

PostgreSQL utilise UTF-8 par défaut, ce qui permet de stocker n'importe quel caractère Unicode :

-- Caractères accentués
INSERT INTO test_utf8 (texte) VALUES ('Héllo, wörld! Ça marche!');

-- Emojis
INSERT INTO test_utf8 (texte) VALUES ('PostgreSQL 🐘 est génial! 🚀');

-- Caractères asiatiques
INSERT INTO test_utf8 (texte) VALUES ('你好世界');  -- Chinois  
INSERT INTO test_utf8 (texte) VALUES ('こんにちは');  -- Japonais  
INSERT INTO test_utf8 (texte) VALUES ('안녕하세요');  -- Coréen  

-- Tous sont stockés correctement
SELECT * FROM test_utf8;

Collation (Ordre de Tri)

La collation détermine comment le texte est trié et comparé :

-- Voir la collation actuelle
SHOW lc_collate;

-- Tri avec collation française
SELECT nom  
FROM personnes  
ORDER BY nom COLLATE "fr_FR";  

-- Comparaison insensible à la casse
SELECT * FROM utilisateurs  
WHERE email ILIKE '%example.com%';  -- ILIKE = insensible à la casse  

Récapitulatif

Tableau de Décision

Besoin Type Recommandé Exemple
Nom, prénom VARCHAR(100) prenom VARCHAR(100)
Email VARCHAR(255) email VARCHAR(255)
URL VARCHAR(500) site_web VARCHAR(500)
Description courte VARCHAR(500) resume VARCHAR(500)
Article, contenu long TEXT contenu TEXT
Commentaire TEXT commentaire TEXT
Code pays (ISO) CHAR(2) ou VARCHAR(2) pays VARCHAR(2)
Code postal VARCHAR(10) code_postal VARCHAR(10)
Statut/Enum VARCHAR(20) statut VARCHAR(20)

Règles d'Or

  1. VARCHAR pour les données courtes avec limite connue
  2. TEXT pour le contenu long sans limite prévisible
  3. Éviter CHAR sauf pour codes de longueur vraiment fixe
  4. Toujours valider avec CHECK quand possible
  5. Utiliser TRIM pour nettoyer les données
  6. Indexer les colonnes recherchées fréquemment

Fonctions Essentielles

-- Manipulation
CONCAT(), LENGTH(), TRIM(), UPPER(), LOWER()

-- Extraction
SUBSTRING(), LEFT(), RIGHT()

-- Recherche/Remplacement
POSITION(), REPLACE()

-- Validation
LIKE, SIMILAR TO, ~ (regex)

Conclusion

Les types texte PostgreSQL sont simples mais puissants :

  • VARCHAR(n) est le choix standard pour la plupart des cas
  • TEXT est parfait pour le contenu long et variable
  • CHAR(n) est rarement nécessaire (sauf codes fixes)

Point clé : Dans PostgreSQL, VARCHAR et TEXT ont les mêmes performances. Choisissez en fonction de la logique métier et de la validation souhaitée, pas pour des raisons de performance.

Dans la prochaine section, nous explorerons les types temporels (DATE, TIMESTAMP, INTERVAL) qui sont essentiels pour gérer les dates et heures dans vos applications.


⏭️ Temporels (DATE, TIMESTAMP, TIMESTAMPTZ, INTERVAL)