[go: up one dir, main page]

Aller au contenu

MySQL/Requêtes

Un livre de Wikilivres.

La syntaxe de sélection est la suivante (chaque clause fera l'objet d'un paragraphe explicatif ensuite) :

 SELECT *
 FROM nom_table
 WHERE condition
 GROUP BY champ1, champ2
 HAVING groupe condition
 ORDER BY champ
 LIMIT limite, taille;

Liste de champs

[modifier | modifier le wikicode]

Il faut spécifier les données à récupérer avec SELECT :

 SELECT DATABASE();      -- renvoie le nom de la base courante
 SELECT CURRENT_USER();  -- l'utilisateur courant
 SELECT 1+1; -- 2

L'étoile permet d'obtenir tous les champs d'une table :

 SELECT * FROM `wiki1_page`;

Mais il est plutôt conseillé de nommer chaque champ (projection) pour accélérer la requête.

Noms des tables

[modifier | modifier le wikicode]

Pour récupérer les champs d'une table ou d'une vue, il faut la placer dans la clause FROM :

 USE wiki1;
 SELECT page_id FROM `wiki1_page`;  -- renvoie les valeurs du champ "page_id" de la table "wiki1_page".

 SELECT `wiki1`.`wiki1_page`.`page_id`;     -- idem

Autres exemples :

 SELECT MAX(page_id) FROM `wiki1_page`; -- le nombre le plus élevé
 SELECT page_id*2 FROM `wiki1_page`;    -- le double de chaque identifiant

Cette clause permet de filtrer les enregistrements. Prenons pas exemple celui ou ceux dont le champ identifiant est égal à 42 :

 SELECT * FROM `wiki1_page` WHERE `page_id`=42;

Ou bien ceux qui ne sont pas nuls :

 SELECT * FROM `wiki1_page` WHERE page_id IS NOT NULL;

Logo

Il est impossible d'utiliser le résultat d'une fonction calculée dans le SELECT dans le WHERE, car ce résultat n'est trouvé qu'à la fin de l'exécution, donc WHERE ne peut pas s'en servir au moment prévu.

Pour ce faire il convient d'utiliser HAVING (voir-ensuite)

Quand plusieurs enregistrements sont identiques dans le résultat, qu'ils ont les mêmes valeurs dans leurs champs sélectionnés, ils peuvent être groupés en une seule ligne.

Par exemple, en regroupant les enregistrements de la table utilisateurs sur le champ de date d'inscription au wiki, on peut obtenir pour chacune le nombre d'édition maximum, minimum et leurs moyennes :

 SELECT user_registration, MAX(user_editcount), MIN(user_editcount), AVG(user_editcount)
 FROM wiki1_user
 GROUP BY `user_registration`;

Idem mais classé par nom et prénom d'utilisateur :

 SELECT user_registration, user_real_name, MAX(user_editcount), MIN(user_editcount), AVG(user_editcount) 
 FROM wiki1_user
 GROUP BY `user_registration`, `user_real_name`;

Cette instruction permet donc de réaliser des transpositions lignes en colonnes. Par exemple pour afficher les utilisateurs connus comme ayant le même e-mail :

 SELECT user_email,
        MAX(CASE WHEN user_id = 1 THEN user_name ELSE NULL END) AS User1,
        MAX(CASE WHEN user_id = 2 THEN user_name ELSE NULL END) AS User2,
        MAX(CASE WHEN user_id = 3 THEN user_name ELSE NULL END) AS User3
 FROM wiki1_user
 GROUP BY `user_email`;

Logo

Si on place le MAX dans le CASE la transposition ne s'effectue pas.

Voir aussi GROUP_CONCAT() pour transposer les colonnes en lignes.

Ajouter cette clause à un GROUP BY permet d'ajouter le résultat d'une super-agrégation reprenant l'ensemble des résultats[1].

HAVING déclare un filtre valable uniquement pour les enregistrements de la clause GROUP BY, ce qui le distingue de WHERE qui lui opère avant GROUP BY.

HAVING n'est pas optimisé et ne peut pas utiliser les index.

Voici un exemple d'erreur d'optimisation classique : l'ordonnancement des opérations ne filtre le gros des résultats (valeur admin) qu'à la fin de la requête (utilisant plus de mémoire, donc plus de temps qu'avec un WHERE) :

 SELECT MAX(user_editcount), MIN(user_editcount), AVG(user_editcount)
 FROM wiki1_user
 GROUP BY user_real_name
 HAVING user_real_name = 'admin';

Par contre, cet exemple ne peut pas être optimisé car le HAVING utilise le résultat du MAX() calculé après le GROUP BY :

 SELECT MAX(user_editcount), MIN(user_editcount), AVG(user_editcount)
 FROM wiki1_user
 GROUP BY user_real_name
 HAVING MAX(user_editcount) > 500;

Il est possible de classer les résultat, par ordre croissant ou décroissant, des nombres ou des lettres.

 SELECT * FROM `wiki1_page` ORDER BY `page_id`;

Par défaut, l'ordre est ASCENDING (croissant). Pour le décroissant il faut donc préciser DESCENDING :

 SELECT * FROM `wiki1_page` ORDER BY `page_id` ASC;   -- ASC est facultatif
 SELECT * FROM `wiki1_page` ORDER BY `page_id` DESC;  -- ordre inversé

Les valeurs NULL sont considérées comme inférieures aux autres.

Il est également possible de nommer la colonne à classer par son numéro :

 SELECT `page_title`, `page_id` FROM `wiki1_page` ORDER BY 1;  -- nom
 SELECT `page_title`, `page_id` FROM `wiki1_page` ORDER BY 2;  -- id
 SELECT `page_title`, `page_id` FROM `wiki1_page` ORDER BY 1 DESC;

Les expressions SQL sont autorisées :

 SELECT `page_title` FROM `wiki1_page` ORDER BY REVERSE(`page_title`)

La fonction RAND() classe de façon aléatoire :

 SELECT `page_title` FROM `wiki1_page` ORDER BY RAND()

Quand un GROUP BY est spécifié, les résultats sont classés selon les champs qui y sont nommés, sauf avant un ORDER BY. Donc l'ordre décroissant peut aussi être précisé depuis le GROUP BY :

 SELECT user_registration, user_real_name, MAX(user_editcount)
 FROM wiki1_user
 GROUP BY `user_registration` ASC, `user_real_name` DESC;

Pour éviter ce classement automatique du GROUP BY, utiliser ORDER BY NULL :

 SELECT user_registration, user_real_name, MAX(user_editcount)
 FROM wiki1_user
 GROUP BY `user_registration`, `user_real_name` ORDER BY NULL;

Le nombre maximum d'enregistrements dans le résultat est facultatif, on l'indique avec le mot LIMIT :

 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 10;

Ce résultat retourne donc entre 0 et 10 lignes.

Généralement cela s'emploie après un ORDER BY pour avoir les maximums et minimums, mais voici un exemple pour en avoir trois au hasard :

 SELECT * FROM `wiki1_page` ORDER BY rand() LIMIT 3;

Il est possible de définir une plage d’enregistrements, sachant que le premier est le numéro zéro :

 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 10;
 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 0, 10; -- synonyme

On peut donc paginer les requêtes dont les résultats peuvent saturer le serveur :

 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 0, 10;  -- première page
 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 10, 10; -- seconde page
 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 20, 10; -- troisième page

La seconde commande est équivalente à celle-ci :

 SELECT * FROM `wiki1_page` ORDER BY page_id LIMIT 10 OFFSET 10

Une astuce consiste à déboguer la syntaxe de sa requête rapidement en lui demandant un résultat vide, et observer ainsi s'il y a des messages d'erreur sans attendre :

 SELECT ... LIMIT 0
Conseils d'optimisation
  • SQL_CALC_FOUND_ROWS peut accélérer les requêtes[2][3].
  • LIMIT est particulièrement pratique dans des SELECT avec ORDER BY, DISTINCT et GROUP BY, car leurs calculs n'impliquent pas toutes les lignes.
  • Si la requête est résolue par le serveur en copiant les résultats dans une table temporaire, LIMIT aide MySQL à calculer combien de mémoire est requise par la table.

Le mot DISTINCT peut être utilisé pour supprimer les doublons des lignes du résultat :

 SELECT DISTINCT * FROM `wiki1_page`    -- aucun doublon
 SELECT DISTINCTROW * FROM `wiki1_page` -- synonyme
 SELECT ALL * FROM `wiki1_page`         -- doublons (comportement par défaut)

Cela permet par exemple de récupérer la liste de toutes les valeurs différentes d'un champ :

 SELECT DISTINCT `user_real_name` FROM `wiki1_page` ORDER BY `user_real_name`

On peut également en sortir les différentes combinaisons de valeurs :

 SELECT DISTINCT `user_real_name`, `user_editcount` FROM `wiki1_page` ORDER BY `user_real_name`
 si une clé primaire ou un index unique fait partie de la sélection, le DISTINCT devient inutile. C'est également le cas avec GROUP BY.

Logo

La fonction COUNT() a un comportement différent du GROUP BY :

  • SELECT COUNT(*) FROM maTable GROUP BY monChamp) : renvoie une ligne pour chaque valeur du champ, avec le nombre d'occurrences dans la table de chacune.
  • SELECT COUNT(monChamp) FROM maTable GROUP BY monChamp) : idem mais sans compter les NULL (ils apparaissent avec un total à 0).
  • SELECT COUNT(DISTINCT monChamp) FROM maTable : le nombre de valeurs différentes du champ (c'est-à-dire le nombre de lignes des requêtes précédentes).
  • SELECT DISTINCT COUNT(*) FROM maTable : le nombre de lignes dans la table.
  • SELECT DISTINCT COUNT(monChamp) FROM maTable : le nombre de lignes dans la table sans compter celles où monChamp est NULL.

Équivalent du signe =, qui ne nécessite pas d'être répété quand il concerne plusieurs valeurs :

 SELECT page_id
 FROM wiki1_page
 WHERE page_namespace IN (0, 1);

-- Liste des pages qui ont des propriétés plus celles qui n'ont aucun hyperlien
 SELECT page_id
 FROM wiki1_page as p, wiki1_user as u WHERE p.page_id = u.user_id
UNION
 SELECT page_id
 FROM wiki1_page WHERE page_id NOT IN (SELECT pp_page FROM wiki1_page_props);

Logo

Pour améliorer les performances du NOT IN (1, 2), il est généralement préférable d'utiliser NOT EXISTS (select 1, 2)[4].

Fonction disponible depuis MySQL 4.

 -- N'affiche la première sélection que si la seconde n'est pas nulle
 SELECT page_title
 FROM wiki1_page
 WHERE EXISTS (SELECT * FROM wiki1_page_props WHERE pp_propname = 'noindex');
 -- Ne renvoie que les pages dont le numéro est le seul de la seconde sélection
 SELECT page_title
 FROM wiki1_page
 WHERE page_id = ALL (SELECT pp_page FROM wiki1_page_props WHERE pp_propname = 'defaultsort');

UNION et UNION All

[modifier | modifier le wikicode]

Compatible MySQL 4 et plus. L'union de sélections nécessite qu'elles aient le même nombre de colonnes.

La requête suivante renvoie tous les enregistrements de deux tables :

 SELECT page_title FROM wiki1_page
 UNION ALL
 SELECT user_name FROM wiki1_user;

UNION est équivalent à UNION DISTINCT, ce qui le distingue de UNION ALL qui ne filtre pas les doublons.

 SELECT page_id FROM wiki1_page
 UNION
 SELECT page_id FROM wiki1_page;
-- égal
 (SELECT page_id FROM wiki1_page)
 UNION DISTINCT
 (SELECT page_id FROM wiki1_page)
 ORDER BY page_id;


Les relations entre les tables permettent de joindre intelligemment leurs résultats. La jointure naturelle est la plus rapide sur la plupart des plateformes SQL.

L'exemple suivant compare les nombres en anglais et en hindi.

 CREATE TABLE english (Tag int, Inenglish varchar(255));
 CREATE TABLE hindi (Tag int, Inhindi varchar(255));

 INSERT INTO english (Tag, Inenglish) VALUES (1, 'One');
 INSERT INTO english (Tag, Inenglish) VALUES (2, 'Two');
 INSERT INTO english (Tag, Inenglish) VALUES (3, 'Three');

 INSERT INTO hindi (Tag, Inhindi) VALUES (2, 'Do');
 INSERT INTO hindi (Tag, Inhindi) VALUES (3, 'Teen');
 INSERT INTO hindi (Tag, Inhindi) VALUES (4, 'Char');
 select * from english select * from hindi
TagInenglish Tag Inhindi
1 One 2 Do
2 Two 3 Teen
3 Three 4 Char
 SELECT hindi.Tag, english.Inenglish, hindi.Inhindi
 FROM english, hindi
 WHERE english.Tag = hindi.Tag
 -- égal
 SELECT hindi.Tag, english.Inenglish, hindi.Inhindi
 FROM english INNER JOIN hindi ON english.Tag = hindi.Tag
Tag Inenglish Inhindi
2 Two Do
3 Three Teen
 Le comportement d'un JOIN seul est INNER JOIN, qui est aussi synonyme de CROSS JOIN[5] (jointure cartésienne).

La jointure cartésienne décrit le cas où chaque ligne d'une table est jointe à toutes celles d'une autre.

 SELECT * FROM english, hindi
 -- égal 
 SELECT * FROM english CROSS JOIN hindi

3*3 = 9 lignes :

TagInenglishTag Inhindi
1 One 2 Do
2 Two 2 Do
3 Three 2 Do
1 One 3 Teen
2 Two 3 Teen
3 Three 3 Teen
1 One 4 Char
2 Two 4 Char
3 Three 4 Char

La jointure naturelle équivaut à INNER JOIN sur toutes les colonnes communes des deux tables.

Le mot USING est compatible MySQL 4, mais change avec MySQL 5. La requête suivante est équivalente à celles INNER JOIN ci-dessus :

 SELECT hindi.tag, hindi.Inhindi, english.Inenglish
 FROM hindi NATURAL JOIN english
 USING (Tag)
 SELECT hindi.Tag, english.Inenglish, hindi.Inhindi
 FROM english OUTER JOIN hindi ON english.Tag = hindi.Tag
Tag Inenglish Tag Inhindi
1 One   
2 Two 2 Do
3 Three 3 Teen
    4 Char

LEFT JOIN / LEFT OUTER JOIN

[modifier | modifier le wikicode]
 SELECT field1, field2 FROM table1 LEFT JOIN table2 ON field1=field2

 SELECT e.Inenglish as English, e.Tag, h.Inhindi as Hindi
 FROM english AS e
 LEFT JOIN hindi AS h ON e.Tag=h.Tag 
 WHERE h.Inhindi IS NULL
English  tag   Hindi
One      1     NULL
 Naturellement comme pour le inner join, s'il y a plusieurs lignes non NULL à droite, on les retrouve toutes en résultat.

RIGHT OUTER JOIN

[modifier | modifier le wikicode]
 SELECT e.Inenglish AS English, h.tag, h.Inhindi AS Hindi
 FROM english AS e RIGHT JOIN hindi AS h
 ON e.Tag=h.Tag
 WHERE e.Inenglish IS NULL
English   tag     Hindi
NULL       4      Char
  • S'assurer que le type des clés de jointes est le même dans les deux tables.
  • Les mots clés LEFT et RIGHT ne sont pas absolus, ils opèrent selon le contexte : en intervertissant les tables le résultat sera identique.
  • La jointure par défaut est INNER JOIN (pas OUTER).

FULL OUTER JOIN

[modifier | modifier le wikicode]

MySQL et MariaDB n'ont pas de jointure FULL OUTER JOIN. Voici comment l'émuler :

     (SELECT a.*, b* 
         FROM tab1 a LEFT JOIN tab2 b
         ON a.id = b.id)
 UNION
     (SELECT a.*, b* 
         FROM tab1 a RIGHT JOIN tab2 b
         ON a.id = b.id)

Jointures multiples

[modifier | modifier le wikicode]

Il est possible de joindre plus de deux tables :

 SELECT ... FROM a JOIN (b JOIN c on b.id=c.id) ON a.id=b.id

Exemple :

 mysql> SELECT group_type.type_id, group_type.nom, COUNT(people_job.job_id) AS count 
        FROM group_type
         JOIN (groups JOIN people_job ON groups.group_id = people_job.group_id) 
         ON group_type.type_id = groups.type
        GROUP BY type_id ORDER BY type_id
 +---------+--------------------------------------+-------+
 | type_id | nom                                  | count |
 +---------+--------------------------------------+-------+
 |       1 | Official GNU software                |   148 |
 |       2 | non-GNU software and documentation   |   268 |
 |       3 | www.gnu.org portion                  |     4 |
 |       6 | www.gnu.org translation team         |     5 |
 +---------+--------------------------------------+-------+
 4 rows in set (0.02 sec)

Sous requêtes

[modifier | modifier le wikicode]

Compatible MySQL 4.1 et plus.

  • Les sous-requêtes SQL permettent aux résultats d'une requête d'être utilisés par une autre.
  • Elles apparaissent toujours comme une partie de clause WHERE ou HAVING.
  • Seul un champ peut être dans la sous-requête SELECT.
  • Les ORDER BY ne sont donc pas autorisés (inutiles sur une seule colonne).

Par exemple, la "table" RepOffice = OfficeNbr from Offices, liste les bureaux où le quota de vente excède la somme des quotas des vendeurs individuels :

 SELECT ville FROM Offices WHERE Target > ???

??? est la somme des quotas des vendeurs.

 SELECT SUM(Quota)
 FROM SalesReps 
 WHERE RepOffice = OfficeNbr

En combinant ces deux requêtes, les points d'interrogations disparaissent :

 SELECT ville FROM Offices 
 WHERE Target > (SELECT SUM(Quota) FROM SalesReps 
 WHERE RepOffice = OfficeNbr)

Par exemple, tous les clients avec des commandes ou limites de crédits > 50000 €. En utilisant le mot DISTINCT pour ne lister les clients qu'une seule fois :

 SELECT DISTINCT CustNbr 
 FROM Customers, Orders 
 WHERE CustNbr = Cust AND (CreditLimit > 50000 OR Amt > 50000);
 il y a donc trois types de filtre.
  • ON : filtre les lignes d'une seule table.
  • WHERE : filtre les lignes de toutes les tables.
  • HAVING : filtre les lignes de toutes les tables après regroupements.

Logo

Les sous-requêtes ne peuvent pas faire référence à un élément de la requête qui les contient. Si c'est nécessaire, il faut les transformer en jointures : ... JOIN (SELECT...) q ON q.id = ...

 Dans MySQL 8.0 et MariaDB 10.2.1, le mot WITH permet de se référer aux sous-requêtes récurrentes, récursives ou pas[6].

Exemple :

 WITH
   sub_request1 AS (SELECT field1, field2 FROM table1),
   sub_request2 AS (SELECT field2, field3 FROM table2)
 SELECT field1, field3
 FROM sub_request1 INNER JOIN sub_request2
 WHERE sub_request1.field1 = sub_request2.field1;