Mini-cours de SQLite

Partie I

Passons à la pratique et créons notre première base de données.

Installer SQLite

... sous Windows

Télécharger l'exécutable et la documentation sur la page download du site officiel sqlite.org :
https://sqlite.org/download.html

Par exemple, sous Windows, sqlite-tools-win32-x86-3130000.zip ( juillet 2016 ) = A bundle of command-line tools for managing SQLite database files, including the command-line shell program (="sqlite.exe"), the "sqldiff.exe" program, and the "sqlite3_analyzer.exe" program

Par exemple, sqlite-doc-3130000.zip (juillet 2016) = Documentation as a bundle of static HTML files.

Décompressez les deux fichiers.

La version 3.13 de l'exécutable - utilisée dans ce mini-cours-, pour Windows, se trouve ici. La signature SHA-1 de l'exécutable téléchargable ici est 39472632a0b8cb3c596b03a50203c8b92541470b. Cet exécutable se présente sous une interface console et contient un moteur de gestion de base de données.
La documentation en anglais, issue du site officiel, pour utiliser cet exécutable se trouve ici

... sous Ubuntu

https://doc.ubuntu-fr.org/sqlite

  1. Press “Ctrl-Alt-T” to open a terminal window in Ubuntu.
  2. Type "sudo apt-get install sqlite3"
Exemple
  Lecture des listes de paquets... Fait
  Construction de l'arbre des dépendances
  Lecture des informations d'état... Fait
  Les paquets suivants ont été installés automatiquement et ne sont plus nécessaires :
    libfprint-2-tod1 libllvm10 libllvm11
  Veuillez utiliser « sudo apt autoremove » pour les supprimer.
  Paquets suggérés :
    sqlite3-doc
  Les NOUVEAUX paquets suivants seront installés :
    sqlite3
  0 mis à jour, 1 nouvellement installés, 0 à enlever et 3 non mis à jour.
  Il est nécessaire de prendre 860 ko dans les archives.
  Après cette opération, 2.803 ko d'espace disque supplémentaires seront utilisés.
  Réception de :1 http://be.archive.ubuntu.com/ubuntu focal-updates/main amd64 sqlite3 amd64 3.31.1-4ubuntu0.2 [860 kB]
  860 ko réceptionnés en 0s (2.923 ko/s)
  Sélection du paquet sqlite3 précédemment désélectionné.
  (Lecture de la base de données... 197675 fichiers et répertoires déjà installés.)
  Préparation du dépaquetage de .../sqlite3_3.31.1-4ubuntu0.2_amd64.deb ...
  Dépaquetage de sqlite3 (3.31.1-4ubuntu0.2) ...
  Paramétrage de sqlite3 (3.31.1-4ubuntu0.2) ...
  Traitement des actions différées (« triggers ») pour man-db (2.9.1-1) ...

Lancer et fermer SQLite

Avant de démarrer un moteur, une voiture, ... lancer un programme, il faut savoir comment l'arrêter. Pour quitter le programme, il faudra taper, après le prompt sqlite>, .quit ou .exit, puis taper sur la touche Enter.

Le prompt est un terme informatique anglais que l'on peut traduire en français par invite de commande et qui indique que le programme est prêt à recevoir une saisie au clavier. Il se trouve toujours au début d'une ligne, est composé généralement du nom du programme et se termine souvent par le caractère > ( dans l'illustration ci-dessous, il est encadré en jaune ).

... sous Windows

Sous Windows, pour lancer le gestionnaire de bases de données SQLite, il suffit de double-cliquer sur l'exécutable sqlite3.exe. Au premier lancement, peut s'afficher le message suivant, émis par votre système d'exploitation :

Ce message anxiogène est affiché car, probablement, l'éditeur n'a pas compilé son programme via un compilateur Microsoft, ni payé une licence à Microsoft pour que ce message anxiogène n'apparaisse pas. Microsoft veille à la sécurité de ses utilisateurs et aussi, et surtout, à son chiffre d'affaire.

Si tel est le cas, décochez pour ne plus être ennuyé ultérieurement par l'affichage de ce message. S'affiche la console suivante :

Sous Windows, une console est une fenêtre d'invite de commande. Sous Linux, on parlera de terminal. On peut aussi l'appeler Interface en ligne de commande. La communication entre le programme et l'utilisateur se fait en mode texte.

La console ou le terminal peut être lancé dans le dossier contenant la base de donnée. (Recommandé)

Le texte affiché et les commandes à taper dans la console/terminal sont identiques sous Windows et sous Ubuntu.

... sous Ubuntu

Dans un terminal, lancer la commande suivante avant de taper les commandes propres à SQLite :
sqlite3 (ne pas oublier le 3)


Pour fermer le programme, il suffit de taper .quit :

Pour fermer la fenêtre, on peut cliquer sur la croix de fermeture. Cela entraînera la fermeture du programme. Mais, il est préférable de fermer le programme. Cela entraînera la fermeture de la fenêtre.


Pour obtenir la liste des dot-commands, il suffit de taper .help

dot signifie point. Les dot-commands sont les commandes qui commencent par point.

Il suffit de déplacer l'ascenseur droit de la fenêtre, pour lire toutes les dot-commands.

Les dot-commands ne sont pas des instructions SQL et ne se terminent pas par un point-virgule. Ces commandes sont propres à SQLite. La commande .exit permet de quitter ce programme.


Avant de créer notre première table, il nous faut connaître les types de données de SQLite et les contraintes prévues par le SQL.

Les types de données de SQLite

SQLite ne connaît que 5 types de données :

  1. NULL = pas de donnée ...
  2. INTEGER = un nombre entier
  3. REAL = un nombre décimal ( avec point décimal )
  4. TEXT = une chaîne de caractères
  5. BLOB = une suite d'octets

Une colonne ne peut pas être déclarée de type NULL. Cela signifierait que la colonne ne peut contenir que des " pas de données ". Cela ne présente aucun intérêt.

Avec SQLite, les booléens doivent être codés comme en C : false ( = 0 ), true ( = 1 )

Avec SQLite, les dates doivent être codées soit comme une chaîne de caractères ayant le format suivant : "YYYY-MM-DD HH:MM:SS.SSS", soit comme un nombre entier positif de secondes depuis le 1er janvier 1970 à 0 heure, 0 minute, 0 seconde.

Compatibilité

Pour assurer une compatibilité avec les autres bases de données ( utilisant d'autres noms de type de données ), voici les règles appliquées :

Si le nom de type de données contient la chaîne "INT" - quelque soit la casse utilisée -, pour SQLite, le type sera INTEGER.

Si le nom de type de données contient la chaîne "CHAR", "CLOB" ou "TEXT" - quelque soit la casse utilisée -, pour SQLite, le type sera TEXT.

Les valeurs numériques indiquées entre parenthèses pour un type tel que varchar(15) seront ignorées par SQLite.

Si le nom de type de données n'est pas indiqué ou contient la chaîne "BLOB" - quelque soit la casse utilisée -, pour SQLite, le type sera BLOB.

Si le nom de type de données contient la chaîne "REAL", "FLOA", or "DOUB" - quelque soit la casse utilisée -, pour SQLite, le type sera REAL.

SQL : les contraintes

Les contraintes sont importantes car elles autorisent la base de données à refuser l'insertion, la modification ou la suppression d'un enregistrement si on tente de violer une de ces contraintes. Ces contraintes sont fixées par le DB administrator. La validité des données n'est pas soumise à la qualité de l'application qui alimente la base de données. Le DB administrator est placé au dessus du programmer

Principales contraintes :

PRIMARY KEY

Cette contrainte impose que, dans cette colonne, toute donnée existe et soit unique. En pratique, ce champ est quasi toujours de type INTEGER.

id_fiche INTEGER PRIMARY KEY,

Pour SQLITE, lorsqu'un champ est de type INTEGER et a comme contrainte primary key, il est aussi d'office autoincrement. Ceci permet au moteur de la base de données de choisir lui-même le nombre entier positif de la colonne, lors de l'insertion d'une nouvelle ligne. Ce nombre sera le dernier nombre attribué + 1. Soit 1 si la table est vide.

AUTOINCREMENT
  1. The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and disk I/O overhead and should be avoided if not strictly needed. It is usually not needed.
  2. In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT ROWID tables) which is always a 64-bit signed integer.
  3. On an INSERT, if the ROWID or INTEGER PRIMARY KEY column is not explicitly given a value, then it will be filled automatically with an unused integer, usually one more than the largest ROWID currently in use. This is true regardless of whether or not the AUTOINCREMENT keyword is used.
  4. If the AUTOINCREMENT keyword appears after INTEGER PRIMARY KEY, that changes the automatic ROWID assignment algorithm to prevent the reuse of ROWIDs over the lifetime of the database. In other words, the purpose of AUTOINCREMENT is to prevent the reuse of ROWIDs from previously deleted rows.

UNIQUE

NOT NULL

Cette contrainte interdit l'absence de données dans cette colonne.

Un champ sur lequel est appliqué la contrainte PRIMARY KEY ou UNIQUE est alors d'office NOT NULL

A unique constraint is satisfied if and only if

The following statement from SQL92 : A unique constraint is satisfied if and only if no two rows in a table have the same non-null values in the unique columns.

That statement is ambiguous, having at least two possible interpretations :

  1. A unique constraint is satisfied if and only if no two rows in a table have the same values and have non-null values in the unique columns.
  2. A unique constraint is satisfied if and only if no two rows in a table have the same values in the subset of unique columns that are NOT NULL.

SQLite follows interpretation (1), as does PostgreSQL, MySQL, Oracle, and Firebird. It is true that Informix and Microsoft SQL Server use interpretation (2), however we the SQLite developers hold that interpretation (1) is the most natural reading of the requirement and we also want to maximize compatibility with other SQL database engines, and most other database engines also go with (1), so that is what SQLite does.

FOREIGN KEY

Cette contrainte est difficile à comprendre, mais elle est très importante. Il faudra la connaître. Vous serez probablement amené à relire cette section plusieurs fois.

Cette contrainte permet à deux tables ( principale et secondaire ) d'être cohérentes.

La table principale est généralement celle qui contient plusieurs colonnes sur lesquelles on effectue des tris.

Cette contrainte est écrite à la fin de la déclaration des colonnes de la table principale, comme ceci :

Syntaxe :

FOREIGN KEY(colonne1) REFERENCES table2(colonne2)

Exemple :

CREATE TABLE contacts(
id_contact    INTEGER PRIMARY KEY,
nom           TEXT NOT NULL,
prenom        TEXT,
rue           TEXT,
num_localite  INTEGER,
dateAnni      TEXT,
FOREIGN KEY(num_localite) REFERENCES localites(id_localite)
);

Il est logique de créer la table externe (ici, localites) avant de créer la table interne (ici, contacts)

Les deux colonnes doivent être du même type de données.
En pratique, colonne1 et colonne2 sont de type INTEGER.

Pour comprendre, prenons un exemple.

La table principale " contacts " contient un colonne nommée " num_localite " qui contient le nombre entier positif. Par exemple, 17 correspondant à BRUXELLES dans la table " localites "

La table secondaire " localites " contient deux colonnes. La première se nomme id_localite est de type INTEGER, a la contrainte PRIMARY KEY. La seconde colonne se nomme "nom_localite". Dans cette table, 17 correspond à BRUXELLES.

Utilité de cette contrainte

Sans cette contrainte FOREIGN KEY sur la colonne num_localite de la table principale, lors de l'insertion d'une nouvelle ligne dans cette table principale, on pourrait mettre dans la colonne num_localite un nombre qui ne correspond à aucune localité.

Sans cette contrainte, on pourrait modifier une ligne de la table principale en remplaçant le nombre actuel de la colonne num_localite par un nombre qui n'existe pas dans la table secondaire.

Notez que cette contrainte n'interdit pas de supprimer des lignes dans la table principale, ni d'ajouter des lignes dans la table secondaire.

Mais, cette contrainte interdit de supprimer ou modifier une ligne de la table secondaire, si elle contient un nombre utilisé dans la table principale.

Bref, la contrainte FOREIGN KEY garantit la cohérence des données entre les deux tables.

Une table principale peut être reliée à plusieurs tables secondaires.
Une table secondaire peut être reliée à plusieurs tables principales.

SQL : CREATE TABLE ... ;

L'instruction CREATE TABLE ... ; est la première instruction SQL majeure.

CREATE TABLE localites(id smallint, nom varchar(25));

Ci-dessus, une instruction SQL qui crée une table nommée localites, qui contient deux colonnes. La première se nommera id et la seconde se nommera nom. Le type de données de la première colonne est smallint, celui de la seconde colonne est varchar(25)

Toutefois, compte tenu des règles de compatibilité - énoncées ci-dessus - cette instruction est traduite en :

CREATE TABLE localites(id INTEGER, nom TEXT);

Remarquez que, comme dans la plupart des langages de programmation, l'instruction se termine par un point-virgule.

Après le prompt sqlite>, tapez l'instruction ci-dessus ( ou faites un copier/coller ) puis tapez sur la touche Enter.

Félicitations ! Vous venez de créer votre première table. Pour le vérifier, tapez, après l'invite de commande, .tables ( puis tapez sur la touche Enter ), pour lister les tables contenues dans la base de données.

Voici le résultat :

Le programme affiche : localites

Syntaxe simplifiée pour créer une table :

CREATE TABLE nom_table(nom_colonne1 type_de_données, nom_colonne2 type_de_données, ...);

Syntaxe complète pour créer une table :

Ajoutons une autre table.

CREATE TABLE contacts(nom varchar(15),
prenom varchar(15),
rue varchar(40),
codePostal varchar(4),
localite smallint,
dateAnniversaire varchar(10));

Il n'est pas obligatoire d'écrire l'instruction sur une seule ligne.
Mais, il est obligatoire de terminer cette instruction par un point-virgule.

Comme d'habitude, pour éviter tout problème, le nom d'une table ou d'un champ ( colonne ) ne contiendra aucun caractère accentué, spécial ou espace. De plus, ce nom ne sera pas un nom réservé pour ce type de base de données ( ni pour d'autres types de base de données, au cas où cette base de données serait exportée )

Dans le code source d'un programme, pour distinguer plus rapidement dans une requête SQL les noms, ceux des tables et colonnes sont écrits en minuscules; et, ceux des noms réservés en majuscules.
Notez qu'en ligne de commande, pour écrire plus vite, les mots réservés sont tapés en minuscules.

Table names that begin with "sqlite_" are reserved for internal use.
Voir cet extrait de la documentation officielle.

Pour constater la création de la seconde table, tapez, après l'invite de commande, .tables

SQL : DROP TABLE ... ;

L'instruction DROP TABLE ... ; est la seconde instruction SQL majeure.

Syntaxe pour supprimer une table :

DROP TABLE nom_table;

Pour supprimer une table, il n'existe pas de syntaxe complexe.

Attention. La table et toutes ses données seront supprimées sans avertissement !

Supprimons la table localites et vérifions sa suppression en tapant .tables

Vous commencez à bien utiliser SQLite. Mais, nous n'allons pas enregistrer ces tables. Vous pouvez taper .exit pour quitter SQLite. Demain, nous créerons de "vraies" tables ( avec des contraintes ), nous les remplirons et les enregistrerons.

SQL : ALTER TABLE ... ;

L'instruction ALTER TABLE ... ; est la troisième instruction SQL majeure. Dans le cas de SQLITE, cette instruction est limitée à :

Après la création d'une table, il n'est pas possible d'ajouter une contrainte (UNIQUE, NOT NULL, ...)

Corriger une table

SQLite has limited ALTER TABLE support that you can use to add a column to the end of a table or to change the name of a table. If you want to make more complex changes in the structure of a table, you will have to recreate the table. You can save existing data to a temporary table, drop the old table, create the new table, then copy the data back in from the temporary table.

Exemple : ajout d'un NOT NULL

NB : Les requêtes SQL doivent tenir en une ligne.

1. Créer une table temporaire

CREATE TABLE conteneurs2 (id_conteneur INTEGER PRIMARY KEY, nomConteneur TEXT NOT NULL UNIQUE, isRempli TEXT NOT NULL DEFAULT "N", emplacement TEXT NOT NULL );

2. Remplir la table temporaire

NSERT INTO conteneurs2 ( id_conteneur, nomConteneur, isRempli, emplacement ) SELECT id_conteneur, nomConteneur, isRempli, emplacement FROM conteneurs;

3. Vérifier qu'elle a bien été remplie

SELECT * FROM conteneurs2;

4. Supprimer la table source (et pas la table temporaire !)

DROP TABLE conteneurs;

5. Renommer la table temporaire par le nom de la table d'origine

ALTER TABLE conteneurs2 RENAME TO conteneurs;

Exemple : ajout d'une contrainte d'unicité

SQLITE ne permet pas de modifier la structure de la base de données, de manière complexe. Ne peut pas être exécutée, la requête suivante :

ALTER TABLE fiches ADD CONSTRAINT position UNIQUE (num_fard,numPosition);

Une nouvelle table devra être créé par une requête SQL contenant :

CREATE TABLE fiches2 (
id_fiche INTEGER PRIMARY KEY,
num_fard INTEGER,
numPosition INTEGER NOT NULL,
description TEXT NOT NULL DEFAULT "",
num_typDoc INTEGER,
num_producteur INTEGER,
num_destinataire INTEGER,
dateProduct NOT NULL DEFAULT CURRENT_DATE,
motCles TEXT NOT NULL DEFAULT "",
dateModifFich NOT NULL DEFAULT CURRENT_TIMESTAMP,
dateDelete NOT NULL DEFAULT "2999-12-31",
UNIQUE (num_fard,numPosition),
FOREIGN KEY(num_fard) REFERENCES conteneurs(id_conteneur),
FOREIGN KEY(num_typDoc) REFERENCES typDoc(id_typDoc),
FOREIGN KEY(num_producteur) REFERENCES personnes(id_personne),
FOREIGN KEY(num_destinataire) REFERENCES personnes(id_personne)
);

Puis, réinjecter les données de la l'ancienne table,

INSERT INTO fiches2
 (
 id_fiche,
 num_fard,
 numPosition,
 num_typDoc,
 num_producteur,
 num_destinataire,
 dateProduct,
 dateModifFich,
 dateDelete,
 motCles,
 description
 )
 SELECT
 id_fiche,
 num_fard,
 numPosition,
 num_typDoc,
 num_producteur,
 num_destinataire,
 dateProduct,
 dateModifFich,
 dateDelete,
 motCles,
 description
 FROM
 fiches
;

C'est l'occasion de modifier l'ordre des champs. Toutefois, les champs (dans l'INSERT et dans le SELECT) doivent correspondre en nombre, en type et être dans le même ordre.

Si l'ancienne table contient des enregistrements qui ne respectent la nouvelle contrainte, une erreur se produit.

Error: UNIQUE constraint failed: fiches2.id_fiche

Il convient alors de modifier (ou de supprimer) les enregistrements causant cette erreur, dans l'ancienne table.

Puis, de vérifier ce transfert (via un SELECT id_fiche FROM fiches2 WHERE id_fiche>850;), puis de supprimer l'ancienne table (DROP TABLE fiches;) et, enfin renommer la nouvelle table du nom de l'ancienne ( ALTER TABLE fiches2 RENAME TO fiches;).