Aller au contenu

Section 1 : SQLite3 en ligne de commande

Cette section vous apprendra à utiliser l'interface en ligne de commande sqlite3 pour créer et manipuler des bases de données. Ces compétences sont essentielles pour :

  • Déboguer les bases de données de vos applications Android
  • Tester vos requêtes SQL avant de les intégrer dans Room
  • Analyser les données stockées sur les appareils
  • Prototyper rapidement des schémas de base de données

Lien avec Android

Les commandes que vous apprendrez ici peuvent être utilisées via ADB pour inspecter les bases de données de vos applications Android en cours de développement.

1.1 Installation de sqlite3

  1. Téléchargez les binaires depuis sqlite.org/download.html
  2. Recherchez "Precompiled Binaries for Windows"
  3. Téléchargez sqlite-tools-win32-x86-*.zip
  4. Extrayez le fichier et ajoutez le répertoire au PATH

Ajouter au PATH sous Windows

  1. Copiez le chemin du dossier contenant sqlite3.exe
  2. Recherchez "Variables d'environnement" dans le menu Démarrer
  3. Modifiez la variable Path
  4. Ajoutez le chemin copié
sudo apt update
sudo apt install sqlite3

SQLite3 est généralement préinstallé sur macOS. Sinon :

brew install sqlite3

Vérification de l'installation

sqlite3 --version

Résultat attendu

Vous devriez voir quelque chose comme :

3.44.4 2025-02-19 00:18:53 ...

Bonus : sqlite3 avec Android

Déboguer une base Android

Une fois que vous développerez des applications Android, vous pourrez utiliser sqlite3 pour inspecter vos bases de données directement depuis l'appareil :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
```bash
# 1. Lister les bases de données d'une app
adb shell ls /data/data/com.example.app/databases/

# 2. Extraire la base pour analyse locale
adb pull /data/data/com.example.app/databases/ma_base.db

# 3. Ouvrir la base extraite avec sqlite3 (lance le mode interactif)
sqlite3 ma_base.db
```

**Note** : L'accès à `/data/data/` nécessite un appareil rooté ou un émulateur. La commande `sqlite3` ouvre une session interactive où vous pouvez exécuter des requêtes SQL et des commandes système (`.tables`, `.schema`, etc.).

Commandes système sqlite3

Les commandes système commencent par un point (.) :

Commande Description
.help Affiche l'aide complète
.databases Liste les bases de données attachées
.tables Liste toutes les tables
.schema Affiche le schéma de toutes les tables
.schema TABLE Affiche le schéma d'une table spécifique
.quit ou .exit Quitte sqlite3
.mode MODE Change le mode d'affichage (column, csv, etc.)
.headers on/off Affiche/masque les en-têtes de colonnes
.output FILE Redirige la sortie vers un fichier
Astuce : Configuration d'affichage

Pour une meilleure lisibilité, utilisez :

.mode column
.headers on

1.3 Création de tables

Exercice 1 : Créer une base de données pour une application mobile de gestion de tâches

Nous allons créer une base de données, todo.db, typique d'une application Android de gestion de tâches (To-Do app), similaire à ce que vous implémenterez avec Room dans les prochains TPs.

Table Tasks (Tâches)

CREATE TABLE Tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    description TEXT,
    priority TEXT CHECK(priority IN ('LOW', 'MEDIUM', 'HIGH')) DEFAULT 'MEDIUM',
    is_completed INTEGER DEFAULT 0,  -- 0 = false, 1 = true (SQLite n'a pas de booléen)
    due_date DATETIME,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Mise à jour automatique de updated_at

Dans les exemples ci-dessus, nous mettons à jour updated_at manuellement. Pour une approche plus robuste, vous pouvez créer un trigger SQLite qui met à jour automatiquement ce champ :

1
2
3
4
5
6
CREATE TRIGGER update_tasks_updated_at
BEFORE UPDATE ON Tasks
FOR EACH ROW
BEGIN
    SET NEW.updated_at = CURRENT_TIMESTAMP;
END;

Avec ce trigger :

1
2
3
4
5
-- Maintenant, updated_at sera mis à jour automatiquement
UPDATE Tasks 
SET is_completed = 1
WHERE id = 1;
-- Pas besoin de spécifier updated_at !

Équivalent Room

Room ne supporte pas directement les triggers, mais vous pouvez obtenir le même comportement avec @PreUpdate :

@Entity(tableName = "tasks")
data class Task(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    // ...
    val updatedAt: Long = System.currentTimeMillis()
)

// Dans le DAO
@Update
suspend fun updateTask(task: Task) {
    // Copier avec nouveau timestamp
    val updated = task.copy(updatedAt = System.currentTimeMillis())
    _updateTask(updated)
}

Explications - Pattern Android

  • INTEGER PRIMARY KEY AUTOINCREMENT : Identifiant unique (comme dans Room)
  • TEXT NOT NULL : Champ obligatoire
  • CHECK(priority IN (...)) : Validation des valeurs (équivalent à un enum)
  • INTEGER pour les booléens : Convention SQLite/Android (0=false, 1=true)
  • Timestamps avec DEFAULT CURRENT_TIMESTAMP

Table Categories

1
2
3
4
5
6
7
CREATE TABLE Categories (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT UNIQUE NOT NULL,
    color TEXT DEFAULT '#000000',  -- Code couleur hexadécimal
    icon TEXT,  -- Nom de l'icône (ex: "work", "home", "shopping")
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Design mobile

Stockez les couleurs en hexadécimal (#RRGGBB) et les icônes par nom pour faciliter l'affichage dans Android

Table TaskCategory (Relation N:M)

1
2
3
4
5
6
7
CREATE TABLE TaskCategory (
    task_id INTEGER NOT NULL,
    category_id INTEGER NOT NULL,
    PRIMARY KEY (task_id, category_id),
    FOREIGN KEY (task_id) REFERENCES Tasks(id) ON DELETE CASCADE,
    FOREIGN KEY (category_id) REFERENCES Categories(id) ON DELETE CASCADE
);

Relation plusieurs-à-plusieurs

  • Table de jointure (junction table) comme dans Room
  • PRIMARY KEY composite sur les deux colonnes
  • ON DELETE CASCADE : Suppression en cascade (automatique dans Room)

Vérifier la structure

1
2
3
4
5
6
7
8
-- Afficher toutes les tables
.tables

-- Afficher le schéma d'une table
.schema Tasks

-- Afficher le schéma d'une table avec PRAGMA
PRAGMA table_info(Tasks);
Comprendre la sortie de PRAGMA table_info

Le résultat de PRAGMA table_info(Tasks) affiche les détails de chaque colonne dans l'ordre suivant :

cid | name | type | notnull | dflt_value | pk
Position Signification Description
0 cid Column ID : Position de la colonne (commence à 0)
1 name Nom de la colonne
2 type Type de données (INTEGER, TEXT, etc.)
3 notnull 0 = NULL autorisé, 1 = NOT NULL obligatoire
4 dflt_value Valeur par défaut (ou vide si aucune)
5 pk Clé primaire : 1 si PRIMARY KEY, 0 sinon

Exemple avec la colonne title :

1|title|TEXT|1||0
  • Position 1 dans la table
  • Nom : title
  • Type : TEXT
  • NOT NULL obligatoire (1)
  • Pas de valeur par défaut (vide)
  • Pas une clé primaire (0)

Exemple avec la colonne id :

0|id|INTEGER|0||1
  • Position 0 (première colonne)
  • Type INTEGER
  • NULL autorisé (0) mais ignoré car c'est une PRIMARY KEY
  • Clé primaire (1)

1.4 Insertion de données

Exercice 2 : Insérer des données de test

Insérer des catégories

1
2
3
4
5
6
7
8
-- Catégories typiques d'une app de tâches
INSERT INTO Categories (name, color, icon) 
VALUES 
    ('Travail', '#FF5722', 'work'),
    ('Personnel', '#4CAF50', 'home'),
    ('Shopping', '#2196F3', 'shopping_cart'),
    ('Sport', '#FF9800', 'fitness_center'),
    ('Études', '#9C27B0', 'school');

Insérer des tâches

1
2
3
4
5
6
7
8
-- Tâches avec différentes priorités
INSERT INTO Tasks (title, description, priority, is_completed, due_date) 
VALUES 
    ('Finir le TP SQLite', 'Compléter tous les exercices', 'HIGH', 0, '2025-01-15 23:59:00'),
    ('Faire les courses', 'Acheter du pain et du lait', 'MEDIUM', 0, '2025-01-10 18:00:00'),
    ('Séance de sport', 'Course à pied 5km', 'LOW', 1, '2025-01-08 07:00:00'),
    ('Préparer présentation', 'Slides pour la réunion', 'HIGH', 0, '2025-01-12 09:00:00'),
    ('Appeler le dentiste', 'Prendre RDV', 'MEDIUM', 0, NULL);

Lier les tâches aux catégories

1
2
3
4
5
6
7
8
-- Associer les tâches à leurs catégories
INSERT INTO TaskCategory (task_id, category_id) 
VALUES 
    (1, 5),  -- TP SQLite → Études
    (2, 3),  -- Courses → Shopping
    (3, 4),  -- Sport → Sport
    (4, 1),  -- Présentation → Travail
    (5, 2);  -- Dentiste → Personnel

1.5 Requêtes SELECT

Exercice 3 : Interroger les données

Configuration de l'affichage

1
2
3
.mode column
.headers on
.width 5 30 10 15

Requêtes simples (typiques dans une app mobile)

-- Toutes les tâches non terminées (écran principal de l'app)
SELECT id, title, priority, due_date 
FROM Tasks 
WHERE is_completed = 0
ORDER BY due_date ASC;

-- Tâches à haute priorité
SELECT * FROM Tasks 
WHERE priority = 'HIGH' AND is_completed = 0;

-- Recherche de tâches (fonction de recherche dans l'app)
SELECT * FROM Tasks 
WHERE title LIKE '%TP%' OR description LIKE '%TP%';

-- Compter les tâches par statut (statistiques)
SELECT 
    CASE is_completed
        WHEN 0 THEN 'En cours'
        WHEN 1 THEN 'Terminées'
    END as statut,
    COUNT(*) as nombre
FROM Tasks
GROUP BY is_completed;
Résultat attendu
1
2
3
4
statut     nombre
---------- ------
En cours   4
Terminées  1

1.6 Jointures

Exercice 4 : Requêtes avec jointures (pattern courant dans Room)

Liste des tâches avec leurs catégories

-- Requête typique pour afficher les tâches avec leur catégorie
SELECT 
    t.id,
    t.title,
    t.priority,
    c.name as category,
    c.color,
    t.due_date,
    t.is_completed
FROM Tasks t
LEFT JOIN TaskCategory tc ON t.id = tc.task_id
LEFT JOIN Categories c ON tc.category_id = c.id
ORDER BY t.due_date ASC;

Équivalent Room

Cette requête correspond à une relation @Relation dans Room avec @Embedded

Statistiques par catégorie

-- Dashboard de l'app : nombre de tâches par catégorie
SELECT 
    c.name as category,
    c.color,
    COUNT(tc.task_id) as total_tasks,
    SUM(CASE WHEN t.is_completed = 1 THEN 1 ELSE 0 END) as completed,
    SUM(CASE WHEN t.is_completed = 0 THEN 1 ELSE 0 END) as pending
FROM Categories c
LEFT JOIN TaskCategory tc ON c.id = tc.category_id
LEFT JOIN Tasks t ON tc.task_id = t.id
GROUP BY c.id
ORDER BY total_tasks DESC;

Tâches en retard (fonctionnalité de notification)

SELECT 
    t.id,
    t.title,
    c.name as category,
    t.due_date,
    CAST((julianday('now') - julianday(t.due_date)) AS INTEGER) as days_overdue
FROM Tasks t
LEFT JOIN TaskCategory tc ON t.id = tc.task_id
LEFT JOIN Categories c ON tc.category_id = c.id
WHERE t.is_completed = 0 
  AND t.due_date < datetime('now')
ORDER BY days_overdue DESC;

Cas d'usage Android

Cette requête serait utilisée pour :

  • Afficher des notifications de rappel
  • Mettre en évidence les tâches en retard dans l'UI
  • Calculer des statistiques de productivité

1.7 Mise à jour et suppression

Exercice 5 : Modifier les données (opérations typiques dans une app)

Marquer une tâche comme terminée

-- Action utilisateur : cocher une tâche
UPDATE Tasks 
SET is_completed = 1,
    updated_at = CURRENT_TIMESTAMP
WHERE id = 1;

-- Vérifier la modification
SELECT id, title, is_completed, updated_at 
FROM Tasks 
WHERE id = 1;

Modifier la priorité d'une tâche

1
2
3
4
UPDATE Tasks 
SET priority = 'HIGH',
    updated_at = CURRENT_TIMESTAMP
WHERE id = 2;

Supprimer une tâche terminée

-- Supprimer une tâche (l'association dans TaskCategory sera supprimée en CASCADE)
DELETE FROM Tasks WHERE id = 3;

Attention : CASCADE

1
2
3
-- Supprimer une catégorie supprime toutes ses associations
DELETE FROM Categories WHERE id = 5;
-- Les entrées dans TaskCategory sont automatiquement supprimées

Pattern Android

Dans Room, ces opérations seraient gérées par des DAO :

1
2
3
4
5
@Update
suspend fun updateTask(task: Task)

@Delete
suspend fun deleteTask(task: Task)

1.8 Transactions

Exercice 6 : Utiliser les transactions (critiques pour Android)

Les transactions garantissent que plusieurs opérations s'exécutent ensemble ou pas du tout. Essentielles pour maintenir la cohérence des données dans une app mobile.

-- Commencer une transaction
BEGIN TRANSACTION;

-- Créer une nouvelle tâche
INSERT INTO Tasks (title, description, priority, due_date) 
VALUES ('Préparer démo app', 'Finaliser la présentation du projet', 'HIGH', '2025-01-20 14:00:00');

-- Récupérer l'ID de la tâche créée
SELECT last_insert_rowid() as task_id;

-- Associer la tâche à une catégorie
INSERT INTO TaskCategory (task_id, category_id) 
VALUES (last_insert_rowid(), 1);

-- Valider la transaction
COMMIT;

En cas d'erreur

Si quelque chose ne va pas, annulez avec :

ROLLBACK;

Transactions dans Room

Room gère automatiquement les transactions :

1
2
3
4
5
@Transaction
suspend fun insertTaskWithCategory(task: Task, categoryId: Long) {
    val taskId = taskDao.insert(task)
    taskCategoryDao.insert(TaskCategory(taskId, categoryId))
}

Les annotations @Insert, @Update, @Delete utilisent des transactions par défaut.

1.9 Export de données

Exercice 7 : Exporter les données

Export en CSV (pour analyse ou backup)

1
2
3
4
.mode csv
.output tasks_backup.csv
SELECT * FROM Tasks;
.output stdout

Export SQL complet (migration ou partage)

1
2
3
4
5
6
7
8
9
-- Exporter le schéma et les données
.output tasks_database.sql
.dump
.output stdout

-- Exporter une table spécifique
.output categories.sql
.dump Categories
.output stdout

Cas d'usage Android

  • Backup utilisateur : Exporter les données avant réinitialisation
  • Debug : Analyser la base extraite d'un appareil
  • Migration : Transférer des données entre versions d'app

Bonus : Extraire une base d'un appareil Android (pour plus tard)

À utiliser dans les prochains TPs

Cette section vous sera utile lorsque vous développerez vos propres applications Android avec Room. Pour l'instant, vous pouvez la lire pour comprendre le workflow, mais vous l'appliquerez concrètement dans les TPs suivants.

Installation d'ADB (Android Debug Bridge)

ADB est l'outil officiel pour communiquer avec des appareils Android depuis votre ordinateur.

  1. Téléchargez Android Platform Tools
  2. Extrayez le fichier ZIP dans C:\platform-tools
  3. Ajoutez C:\platform-tools au PATH (voir instructions sqlite3)
  4. Vérifiez : adb --version
1
2
3
4
5
sudo apt update
sudo apt install android-tools-adb android-tools-fastboot

# Vérifier l'installation
adb --version
1
2
3
4
brew install android-platform-tools

# Vérifier l'installation
adb --version

Extraire la base de données d'une app

Une fois que vous aurez créé votre propre application Android :

# 1. Connecter votre appareil/émulateur
adb devices

# 2. Lister les bases de données de votre app
adb shell ls /data/data/VOTRE_PACKAGE/databases/

# 3. Extraire la base (pour émulateur ou appareil rooté)
adb pull /data/data/VOTRE_PACKAGE/databases/votre_base.db

# 4. Ou via run-as (pour votre propre app en développement)
adb shell run-as VOTRE_PACKAGE cat databases/votre_base.db > ma_base_extraite.db

# 5. Analyser avec sqlite3
sqlite3 ma_base_extraite.db
Exemple concret (pour vos futurs TPs)

Imaginez que vous développez une app de tâches avec le package com.votregroupe.todoapp :

1
2
3
4
5
6
7
# Extraire la base
adb pull /data/data/com.votregroupe.todoapp/databases/tasks.db

# Analyser
sqlite3 tasks.db
.tables
SELECT * FROM Tasks WHERE is_completed = 0;

Félicitations !

Vous avez terminé la section 1 ! Vous savez maintenant créer et manipuler des bases SQLite adaptées aux applications mobiles.


Prochaines étapes

  • Section 2 : DB Browser


    Continuez avec l'outil graphique

    Continuer