Le SQL de base : SELECT, WHERE par id et un filtrage simple

Objectifs de la leçon
  • Récupérer toutes les lignes ou des colonnes choisies avec SELECT
  • Récupérer un enregistrement par son id avec WHERE
  • Écrire des conditions de filtrage simples avec =, <>, >, < et AND
  • Trier les résultats avec ORDER BY, et comprendre ce qui se passe quand aucune ligne n’a été trouvée

Qu’allons-nous apprendre ?

SELECT = le Read du CRUD : lire des données dans une table.

  • Toutes les lignes : SELECT * FROM students.
  • Des colonnes choisies : SELECT name, email FROM students.
  • Une ligne par id : SELECT ... WHERE id = 2.
  • Un filtrage simple avec WHERE et des conditions.
  • Le tri avec ORDER BY.

Pourquoi en avons-nous besoin ?

Les deux requêtes les plus importantes de toute API :

EndpointSQL
GET /studentsSELECT ... FROM students
GET /students/7SELECT ... FROM students WHERE id = 7

Chaque fois que vous ouvrez une liste dans une application (produits, messages, commandes), une requête SELECT s’exécute en coulisses.

Explication à partir des bases

SELECT … FROM

SELECT name, age FROM students;

« Sélectionne les colonnes name et age depuis la table students ». Le résultat est une nouvelle table (temporaire), avec les lignes et les colonnes demandées. La base de données ne change pas : SELECT ne fait que lire, exactement comme GET.

  • * (un astérisque) = toutes les colonnes.
  • L’ordre dans la liste fixe l’ordre des colonnes dans le résultat.

WHERE — filtrer les lignes

SELECT name, age FROM students WHERE age > 23;

WHERE = « seulement les lignes pour lesquelles la condition est vraie ». La base vérifie la condition sur chaque ligne, et ne renvoie que celles qui correspondent.

OpérateurSignificationExemple
=Égal (un seul signe !, pas ==)WHERE id = 2
<> (ou !=)DifférentWHERE name <> 'Avi'
> < >= <=ComparaisonWHERE age >= 18
ANDLes deux conditionsWHERE age > 20 AND age < 30
ORAu moins uneWHERE name = 'Dana' OR name = 'Noa'

WHERE id — le plus important

id est une Primary Key, unique, donc WHERE id = 2 renvoie au plus une ligne. C’est la récupération de GET /students/{student_id}. Et la base la trouve très vite, même parmi des millions de lignes (car une Primary Key est indexée automatiquement).

ORDER BY — le tri

SELECT name, age FROM students ORDER BY age;
SELECT name, age FROM students ORDER BY age DESC;

Par défaut, l’ordre est croissant (ASC). DESC = décroissant. Sans ORDER BY, l’ordre n’est pas garanti, donc si l’ordre compte, demandez-le explicitement.

Analogie : un bibliothécaire

SELECT name, age FROM students = « apporte-moi le nom et l’âge de tous les étudiants ». WHERE age > 23 = « seulement ceux de plus de 23 ans ». ORDER BY age = « rangés par âge ». WHERE id = 2 = « le dossier numéro 2 ». Vous dites quoi, et comment le trouver, c’est l’affaire du bibliothécaire (le SQL déclaratif).

Syntaxe

SELECT * FROM table_name;                                 -- all columns, all rows
SELECT col_a, col_b FROM table_name;                      -- chosen columns
SELECT col_a, col_b FROM table_name WHERE id = 3;         -- one row by primary key
SELECT col_a FROM table_name WHERE col_b > 10 AND col_c = 'x';
SELECT col_a FROM table_name ORDER BY col_b DESC;

-- au début d’un texte en SQL = un commentaire (comme # en Python).

Premier exemple de code

La table de la leçon précédente :

id | name | age | email
---+------+-----+--------------
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
 3 | Noa  |  22 | noa@mail.com

Exécutez dans Database Client (dans un fichier .sql : sélectionner → Run) :

SELECT * FROM students;

Résultat :

id | name | age | email
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
 3 | Noa  |  22 | noa@mail.com
SELECT name, email FROM students;
name | email
Dana | dana@mail.com
Avi  | avi@mail.com
Noa  | noa@mail.com
SELECT id, name, age, email FROM students WHERE id = 2;
id | name | age | email
 2 | Avi  |  31 | avi@mail.com

Analyse du code ligne par ligne

RequêteCe qu’a fait la base
SELECT * FROM studentsToutes les colonnes, toutes les lignes.
SELECT name, email FROM studentsToutes les lignes, mais seulement deux colonnes, dans l’ordre demandé.
SELECT id, name, age, email ... WHERE id = 2A parcouru les lignes (en pratique, est allée directement à l’id 2 grâce à la Primary Key), et n’a renvoyé que la ligne où id = 2.

Remarquez : dans la troisième requête, nous avons écrit explicitement la liste des colonnes, et non *. C’est l’habitude que nous prendrons dans le code : si une colonne est ajoutée à la table à l’avenir (peut-être avec une information sensible, comme un mot de passe), elle ne « fuira » pas par erreur dans les réponses de l’API.

Un autre exemple

Le filtrage, les conditions combinées, le tri, et un résultat vide.

SELECT name, age FROM students WHERE age > 23;
name | age
Dana |  24
Avi  |  31
SELECT name, age FROM students WHERE age > 20 AND age < 30 ORDER BY age;
name | age
Noa  |  22
Dana |  24
SELECT name FROM students ORDER BY name;
name
Avi
Dana
Noa
SELECT id, name FROM students WHERE id = 99;
(no rows)

Analyse :

  • age > 23 — Dana (24) et Avi (31). Noa (22) n’a pas passé la condition.
  • AND + ORDER BY — d’abord le filtrage (seulement entre 20 et 30), puis le tri de ce qui reste. L’ordre dans la requête est fixe : SELECT ... FROM ... WHERE ... ORDER BY ....
  • ORDER BY name — un tri alphabétique du texte.
  • WHERE id = 99 — il n’y a pas de telle ligne. Ce n’est pas une erreur ! Le résultat est simplement vide : 0 ligne. En Python, fetchone() renverra None, et fetchall() renverra une liste vide. Dans l’API, cela deviendra un 404.

Erreur courante

Erreur 1 : == au lieu de =.

SELECT * FROM students WHERE id == 2;
1064 (42000): You have an error in your SQL syntax; ... near '== 2' at line 1

En SQL, la comparaison est un seul =. (Et c’est l’inverse de Python, où = est l’affectation !)

Erreur 2 : une chaîne sans guillemets.

SELECT * FROM students WHERE name = Avi;
1054 (42S22): Unknown column 'Avi' in 'where clause'

Sans guillemets, SQL pense que Avi est le nom d’une colonne (exactement comme print(Hello) en Python cherche une variable). → WHERE name = 'Avi'.

Erreur 3 : un mauvais ordre des parties.

SELECT name FROM students ORDER BY age WHERE age > 20;
1064 (42000): You have an error in your SQL syntax; ... near 'WHERE age > 20' at line 1

L’ordre : SELECT → FROM → WHERE → ORDER BY.

Erreur 4 : traiter un résultat vide comme une erreur, ou l’inverse. Un WHERE id = 99 qui n’a rien trouvé renvoie 0 ligne, sans exception. C’est notre code qui doit vérifier (« introuvable ») et renvoyer 404. Si vous ne vérifiez pas, None[0] plantera.

Erreur 5 : supposer que la comparaison de texte est sensible à la casse. Dans MySQL, par défaut, WHERE name = 'avi' trouve bien Avi — la comparaison ne distingue pas les majuscules des minuscules. C’est pratique pour la recherche, mais cela a un prix : avec UNIQUE sur email, la base refusera d’enregistrer Dana@mail.com si dana@mail.com existe déjà. (Et c’est différent de Python, où "avi" == "Avi" vaut False !)

Exercices

Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.

Exercice 1: Un SELECT de base

Dans tous les exercices de cette leçon, créez d’abord la table et les données :

import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL, age INT NOT NULL, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

Écrivez une requête qui renvoie le nom et l’âge de tous les étudiants, et affichez chaque ligne.

Résultat attendu :

('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)
Afficher la solution
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL, age INT NOT NULL, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name, age FROM students")
for row in cursor.fetchall():
    print(row)
connection.close()
('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)

SELECT name, age — seulement les colonnes nécessaires. SELECT * les renvoie toutes.

Exercice 2: WHERE par id

Avec les mêmes données, récupérez l’étudiant dont l’id vaut 2, et l’étudiant dont l’id vaut 99.

Résultat attendu :

(2, 'Avi', 31, 'Tel Aviv')
None
Afficher la solution
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL, age INT NOT NULL, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT * FROM students WHERE id = 2")
print(cursor.fetchone())
cursor.execute("SELECT * FROM students WHERE id = 99")
print(cursor.fetchone())
connection.close()
(2, 'Avi', 31, 'Tel Aviv')
None

Un résultat vide n’est pas une erreur : fetchone() renvoie None quand il n’y a pas de telle ligne.

Exercice 3: Filtrer et trier

Avec les mêmes données : récupérez les noms des étudiants de Haïfa de moins de 30 ans, et tous les étudiants triés par âge du plus âgé au plus jeune.

Résultat attendu :

[('Dana',), ('Noa',)]
[('Avi', 31), ('Tom', 28), ('Dana', 24), ('Noa', 22)]
Afficher la solution
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL, age INT NOT NULL, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name FROM students WHERE city = 'Haifa' AND age < 30")
print(cursor.fetchall())
cursor.execute("SELECT name, age FROM students ORDER BY age DESC")
print(cursor.fetchall())
connection.close()
[('Dana',), ('Noa',)]
[('Avi', 31), ('Tom', 28), ('Dana', 24), ('Noa', 22)]

Chaque ligne est un tuple — même quand il y a une seule colonne : ('Dana',).

Exercice 4: NULL

Avec les mêmes données : quel étudiant n’a pas de ville ? Écrivez deux requêtes — l’une avec city = NULL et l’autre avec city IS NULL — et expliquez la différence.

Résultat attendu :

[]
[('Tom',)]
Afficher la solution
import mysql.connector

connection = mysql.connector.connect(
    host="localhost", user="root", password="kodkod", database="school")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL, age INT NOT NULL, city VARCHAR(50))""")
cursor.execute("""
INSERT INTO students (name, age, city) VALUES
    ('Dana', 24, 'Haifa'), ('Avi', 31, 'Tel Aviv'),
    ('Noa', 22, 'Haifa'), ('Tom', 28, NULL)""")
connection.commit()

cursor.execute("SELECT name FROM students WHERE city = NULL")
print(cursor.fetchall())
cursor.execute("SELECT name FROM students WHERE city IS NULL")
print(cursor.fetchall())
connection.close()
[]
[('Tom',)]

NULL signifie « aucune valeur », donc city = NULL n’est jamais vrai — pas même pour Tom. On vérifie une valeur manquante uniquement avec IS NULL.

Exercice 5: Des requêtes sur des produits

Écrivez des requêtes SQL sur la table products (name, price, stock), et exécutez-les dans Database Client :

  1. Les noms des produits dont le stock est inférieur à 5.
  2. Tous les produits triés par prix, du moins cher au plus cher — le nom et le prix.
  3. Tous les produits triés par prix, du plus cher au moins cher.
Afficher la solution
SELECT name FROM products
WHERE stock < 5;

SELECT name, price FROM products
ORDER BY price;

SELECT name, price FROM products
ORDER BY price DESC;
  • ORDER BY trie du plus petit au plus grand par défaut. DESC inverse l’ordre.

Résumé

  • SELECT cols FROM table — la lecture. * = toutes les colonnes, mais dans le code il vaut mieux les énumérer explicitement.
  • WHERE condition — seulement les lignes qui correspondent. = (un seul signe), <>, >, <, AND, OR. Les chaînes entre '...'.
  • WHERE id = 2 — au plus une ligne, par la Primary Key. C’est GET /students/{id}.
  • ORDER BY col [DESC] — le tri. Sans ORDER BY, l’ordre n’est pas garanti.
  • L’ordre des parties : SELECT → FROM → WHERE → ORDER BY.
  • Un résultat vide n’est pas une erreur. Le code le vérifie (None / []) et renvoie 404. Pour NULL, utilisez IS NULL.

Documentation officielle