# Le SQL de base : SELECT, WHERE par id et un filtrage simple<no value>

## Qu'allons-nous apprendre ?

**`SELECT`** = le **R**ead 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 :

| Endpoint | SQL |
|---|---|
| `GET /students` | `SELECT ... FROM students` |
| `GET /students/7` | `SELECT ... 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

```sql
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

```sql
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érateur | Signification | Exemple |
|---|---|---|
| `=` | Égal (**un seul signe !**, pas `==`) | `WHERE id = 2` |
| `<>` (ou `!=`) | Différent | `WHERE name <> 'Avi'` |
| `>` `<` `>=` `<=` | Comparaison | `WHERE age >= 18` |
| `AND` | Les deux conditions | `WHERE age > 20 AND age < 30` |
| `OR` | Au moins une | `WHERE 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

```sql
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.

{{< box "analogy" "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).
{{< /box >}}

## Syntaxe

```sql
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 :

```text
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) :

```sql
SELECT * FROM students;
```

**Résultat :**

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

```sql
SELECT name, email FROM students;
```

```text
name | email
Dana | dana@mail.com
Avi  | avi@mail.com
Noa  | noa@mail.com
```

```sql
SELECT id, name, age, email FROM students WHERE id = 2;
```

```text
id | name | age | email
 2 | Avi  |  31 | avi@mail.com
```

## Analyse du code ligne par ligne

| Requête | Ce qu'a fait la base |
|---|---|
| `SELECT * FROM students` | Toutes les colonnes, toutes les lignes. |
| `SELECT name, email FROM students` | Toutes les lignes, mais seulement deux colonnes, dans l'ordre demandé. |
| `SELECT id, name, age, email ... WHERE id = 2` | A 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.**

```sql
SELECT name, age FROM students WHERE age > 23;
```

```text
name | age
Dana |  24
Avi  |  31
```

```sql
SELECT name, age FROM students WHERE age > 20 AND age < 30 ORDER BY age;
```

```text
name | age
Noa  |  22
Dana |  24
```

```sql
SELECT name FROM students ORDER BY name;
```

```text
name
Avi
Dana
Noa
```

```sql
SELECT id, name FROM students WHERE id = 99;
```

```text
(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 `=`.**

```sql
SELECT * FROM students WHERE id == 2;
```

```text
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.**

```sql
SELECT * FROM students WHERE name = Avi;
```

```text
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.**

```sql
SELECT name FROM students ORDER BY age WHERE age > 20;
```

```text
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` !)

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## 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 :

```py
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 :**

```text
('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)
```

{{< solution >}}
```python
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()
```

```text
('Dana', 24)
('Avi', 31)
('Noa', 22)
('Tom', 28)
```

`SELECT name, age` — seulement les colonnes nécessaires. `SELECT *` les renvoie toutes.
{{< /solution >}}

### 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 :**

```text
(2, 'Avi', 31, 'Tel Aviv')
None
```

{{< solution >}}
```python
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()
```

```text
(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.
{{< /solution >}}

### 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 :**

```text
[('Dana',), ('Noa',)]
[('Avi', 31), ('Tom', 28), ('Dana', 24), ('Noa', 22)]
```

{{< solution >}}
```python
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()
```

```text
[('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',)`.
{{< /solution >}}

### 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 :**

```text
[]
[('Tom',)]
```

{{< solution >}}
```python
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()
```

```text
[]
[('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`.
{{< /solution >}}

### 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.

{{< solution >}}
```sql
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.
{{< /solution >}}

<!-- exercises:end -->

## 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`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Documentation officielle

- [MySQL — SELECT](https://dev.mysql.com/doc/refman/8.4/en/select.html)
- [MySQL Tutorial — Selecting Particular Rows](https://dev.mysql.com/doc/refman/8.4/en/selecting-rows.html)

<!-- docs:end -->
