🔝 Retour au Sommaire
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
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.
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 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;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
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;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"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'); 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'); 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 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;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 tailleLa différence principale : TEXT n'a pas de limite, VARCHAR(n) valide la longueur.
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
);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.'
);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()'
);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
);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' 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
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;CHAR est rarement recommandé en PostgreSQL car il n'offre aucun avantage de performance sur VARCHAR. Cependant, il peut être utilisé pour :
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;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.
-- 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'
);| 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) |
-- 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é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;-- 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) -- 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'); -- 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;-- 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 '%[- .]%'; -- 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); -- 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'-- 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' -- 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; 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 constraintCREATE 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 constraintCREATE 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'))
);-- ✅ 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
);-- ✅ 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
);-- ✅ 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
);-- ✅ 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)
);-- ✅ 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}$')
);-- 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 '))); -- 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));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;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 | Besoin | Type Recommandé | Exemple |
|---|---|---|
| Nom, prénom | VARCHAR(100) |
prenom VARCHAR(100) |
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) |
- VARCHAR pour les données courtes avec limite connue
- TEXT pour le contenu long sans limite prévisible
- Éviter CHAR sauf pour codes de longueur vraiment fixe
- Toujours valider avec CHECK quand possible
- Utiliser TRIM pour nettoyer les données
- Indexer les colonnes recherchées fréquemment
-- Manipulation
CONCAT(), LENGTH(), TRIM(), UPPER(), LOWER()
-- Extraction
SUBSTRING(), LEFT(), RIGHT()
-- Recherche/Remplacement
POSITION(), REPLACE()
-- Validation
LIKE, SIMILAR TO, ~ (regex)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.