MySQL/Requêtes
SELECT
modifierLa 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
modifierIl 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
modifierPour 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
WHERE
modifierCette 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;
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)
GROUP BY
modifierQuand 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`;
Si on place le MAX dans le CASE la transposition ne s'effectue pas.
Voir aussi GROUP_CONCAT()
pour transposer les colonnes en lignes.
WITH ROLLUP
modifierAjouter cette clause à un GROUP BY permet d'ajouter le résultat d'une super-agrégation reprenant l'ensemble des résultats[1].
HAVING
modifierHAVING
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;
ORDER BY
modifierIl 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;
LIMIT
modifierLe 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 desSELECT
avecORDER BY
,DISTINCT
etGROUP 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.
DISTINCT
modifierLe 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`
DISTINCT
devient inutile. C'est également le cas avec GROUP BY
.
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.
IN and NOT IN
modifierÉ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);
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].
EXISTS
modifierFonction 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');
ALL
modifier -- 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
modifierCompatible 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;
JOIN
modifier
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 | ||
Tag | Inenglish | Tag | Inhindi |
1 | One | 2 | Do |
2 | Two | 3 | Teen |
3 | Three | 4 | Char |
INNER JOIN
modifier 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 |
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 :
Tag | Inenglish | Tag | 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 |
NATURAL JOIN
modifierLa jointure naturelle équivaut à INNER JOIN
sur toutes les colonnes communes des deux tables.
USING
modifierLe 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)
OUTER JOIN
modifier 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 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
inner join
, s'il y a plusieurs lignes non NULL à droite, on les retrouve toutes en résultat.RIGHT OUTER JOIN
modifier 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
etRIGHT
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
(pasOUTER
).
FULL OUTER JOIN
modifierMySQL 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
modifierIl 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
modifierCompatible 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
ouHAVING
. - 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);
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.
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 = ...
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;
References
modifier- ↑ https://dev.mysql.com/doc/refman/5.7/en/group-by-modifiers.html
- ↑ http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
- ↑ http://dev.mysql.com/doc/refman/5.0/en/information-functions.html
- ↑ https://stackoverflow.com/questions/173041/not-in-vs-not-exists
- ↑ https://dev.mysql.com/doc/refman/5.7/en/join.html
- ↑ https://mariadb.com/kb/en/with/