Laisser un commentaire

Maitriser MySQL en CLI – avancé

Nous avions vu dans un précédent article comment gérer MySQL en ligne de commandes. Nous avons donc appris à créer, modifier, supprimer des tables et des bases. Nous avons aussi vu comment lire, écrire, effacer et trier des données dans ces tables. Ces opérations constituent le plus gros du travail que l’on a à effectuer dans des bases SQL.

Cependant, nous avons parfois à gérer des données complexes, dont l’organisation nécessite des traitements plus poussés. Nous allons donc parler d’index, de clefs primaires, de clefs étrangères ou encore de recherches FULLTEXT. Autant de méthodes qui vont nous aider à organiser et gérer des schémas de données complexes.

Index de colonnes

Les index servent à accélérer les requêtes et sont indispensables pour créer des clefs, que nous verrons par la suite. Lorsqu’un index est créé, le moteur SQL stocke cet index dans une structure de données séparée. Cette structure de données contient contient les valeurs triées spécifiquement pour cette donnée. De cette manière le moteur SQL est en mesure de localiser très efficacement la donnée recherchée.

Les index permettent donc d’accélérer les requêtes qui font usage de critères de recherche, tel que le WHERE.

En revanche, les index prennent de la place en mémoire et ralentissent les INSERT et UPDATE puisqu’il faut écrire l’index à chaque fois. Il faut alors choisir judicieusement les colonnes à indexer et privilégier les colonnes sur lesquelles on fera souvent des recherches par exemple.

Avant d’entrer dans le détails des index et d’en créer, sachez qu’il est bien entendu possible de les afficher :

SHOW INDEX FROM Facebook;
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Facebook |          0 | PRIMARY     |            1 | id          | A         |         378 |     NULL | NULL   |      | BTREE      |         |               |
| Facebook |          1 | ind_user_id |            1 | user_id     | A         |         378 |     NULL | NULL   |      | BTREE      |         |               |
| Facebook |          1 | ind_fb_id   |            1 | fb_id       | A         |         378 |     NULL | NULL   |      | BTREE      |         |               |
| Facebook |          1 | ind_fb_mail |            1 | mail        | A         |         378 |     NULL | NULL   |      | BTREE      |         |               |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

À ce propos, il est possible de faire un index sur plusieurs colonnes à la fois, et on peut se servir de cet index même si on ne fait pas une recherche sur toutes les colonnes indexées. C’est ce qu’on appelle l’index par la gauche.

Vous avez une table membre :

Vous faites un index sur les trois à la fois. L’index triera par ordre les noms, puis les prénoms, puis les pseudos. Donc, si vous voulez faire une recherche sur les noms uniquement, l’ordre sera exactement le même que si on avait créé un index uniquement sur les noms.

De même si on fait une recherche sur nom et prénom, l’ordre ne bouge toujours pas comparé à un index sur ces deux colonnes. En revanche, si on veut faire une recherche sur pseudo uniquement ou prénom uniquement, ça ne marchera pas.

En effet, les pseudos sont ordonnés par les noms et les prénoms qui les précèdent dans les colonnes de gauches, avant d’être trié eux-même alphabétiquement. C’est pour cette raison que ça s’appelle “par la gauche”, ça marche tant que l’on effectue la recherche sur toutes les colonnes qui sont à gauches. Dans le cas contraire, il faut créer un autre index.

Plusieurs types d’INDEX

UNIQUE

Comme son nom l’indique, il fait en sorte qu’il ne puisse y avoir qu’une seule fois une valeur dans la table. C’est par exemple l’index que nous utilisons dans la PRIMARY KEY.

FULLTEXT

Cet index permet de faire des recherche puissantes sur du contenu texte. Ce type d’index ne fonctionne que sur les champs de type TEXT, CHAR et VARCHAR (sinon ce n’est plus du texte !). On verra ensuite ce qu’il permet de faire.

Note : Il n’est pas possible d’utiliser la technique “d’index par la gauche” avec le FULLTEXT. De plus, cet index n’est disponible qu’avec le moteur myisam.

SPATIAL

Cet index est un peu spécifique puisqu’il concerne les données spatiales. Si vous avez besoin de plus d’infos, RTFM ;)

Création des index

CREATE TABLE nom_table (
    colonne1 description_colonne1,
    [colonne2 description_colonne2,
    colonne3 description_colonne3,
    ...,]
    [PRIMARY KEY (colonne_clé_primaire)],
    [{INDEX|KEY} [nom_index] (colonne1_index [, colonne2_index, ...]],
    [UNIQUE [INDEX|KEY] [nom_index] (colonne1_index [, colonne2_index, ...]]
)
[ENGINE=moteur];

Exemple :

CREATE TABLE ma_table (
    id SMALLINT NOT NULL AUTO_INCREMENT,
    nom char(255) NOT NULL,
    email VARCHAR(100),
    INDEX ind_nom (nom(25)),
    UNIQUE INDEX ind_email (email)
)
ENGINE=INNODB;

On peut aussi créer les index directement dans les colonnes. Il faudra utiliser le mot KEY, ou UNIQUE si l’on veut un index unique. Cette syntaxe est plus limité puisqu’on ne peut définir des index que sur une seule colonne à la fois, ni spécifier une limite de taille dans l’index (x premiers caractères d’un char par ex.).

CREATE TABLE nom_table (
    colonne1 INT KEY, -- Crée un index simple sur colonne1
    colonne2 VARCHAR(40) UNIQUE, -- Crée un index unique sur colonne2
);

Ajout après la création de la table

Parfois, on ne pense pas à tout, tout de suite, et on voudrait ajouter un index sur une table déjà existante. ALTER TABLE vous connaissez ?

ALTER TABLE nom_table
ADD INDEX [nom_index] (colonne_index [, colonne2_index ...]);

Il y a une autre commande, qui permet de faire la même chose : CREATE INDEX.

CREATE [UNIQUE | FULLTEXT] INDEX nom_index
ON nom_table (colonne_index [, colonne2_index ...]);

Personnellement, j’aime moins cette manière de faire car il faut retenir une commande supplémentaire alors que ALTER TABLE le fait très bien. En plus vous verrez par la suite que CREATE INDEX permet moins de choses qu’ALTER TABLE, comme par exemple la suppression de l’index :

ALTER TABLE nom_table
DROP INDEX nom_index;

Facile n’est-ce pas ?!

Insertion ou mise à jour

Les index unique vous empêchent d’insérer une donnée en double. Si une telle insertion est tentée, une erreur est levée. Ils ouvrent toutefois une nouvelle possibilité : l’UPSERT, comprendre UPDATE ou INSERT.

Cette commande permet de n’insérer une donnée que si elle n’existe pas déjà. Dans le cas contraire, on peut choisir de ne rien faire ou de mettre à jour la donnée.

Dans les exemples suivant, nous considérons qu’il y a un index unique sur telephone.

INSERT INTO contacts (telephone, firstname, lastname)
VALUES ('0912345678', 'Antoine', 'Bettinger')
ON DUPLICATE KEY UPDATE id = id;

Dans cet exemple, rien ne change, nous évitons simplement la levée d’une erreur. Nous spécifions en effet, qu’en cas de valeur dupliquée, l’id de l’enregistrement sera égal à l’id, donc aucune modification n’est faite.

Nous pouvons bien entendu définir des valeurs à modifier.

INSERT INTO contacts (telephone, firstname, lastname)
VALUES ('0912345678', 'Antoine', 'Bettinger')
ON DUPLICATE KEY UPDATE telephone = '0667862356', lastname = 'Lalane';

Ici, dans le cas où la valeur n’existe pas, elle est créé, mais si elle existe, les colonnes telephone et lastname sont mises à jour avec des valeurs de notre choix.

Enfin, on peut effectuer les mises à jour de manière dynamique. Cela se fait grâce au mot-clef VALUE (VALUES avant MariaDB 10.3.3). Avec ce mot-clef, la valeur de VALUE(column) équivaut à la valeur telle qu’elle aurait été insérée. En revanche, si vous spécifiez column = column, alors il s’agit de la valeur actuelle. Comme dans le premier exemple, cette possibilité permet de définir une expression après UPDATE.

INSERT INTO contacts (telephone, firstname, lastname)
VALUES ('0912345678', 'Antoine', 'Bettinger')
ON DUPLICATE KEY UPDATE firstname = VALUE(firstname), lastname = VALUE(lastname);

Cette fois ci, si le numéro de téléphone existe déjà, alors on met simplement à jour le nom et le prénom du contact.

La recherche FULLTEXT

Le FULLTEXT est très puissant pour la recherche. Il découpe les chaines de caractères en mots pour les analyser un par un. Un mot est une suite de caractères sans espace (oh ?!) ou deux mots séparés par un underscore “_” ou par une apostrophe. Les mots composés séparés par des traits d’union sont considérés comme deux mots distincts.

De plus, pour faire ces recherches, FULLTEXT tient compte des règles suivantes :

La recherche FULLTEXT se fait comme ceci :

SELECT colonnes_indexes_en_fulltext
FROM ma_table
WHERE MATCH (colonel [, 2si_je_veux, …])
AGAINST ('la chaine que je cherche');

Il y a 3 types de recherches FULLTEXT :

Pour préciser le type de recherche :

SELECT colonnes_indexes_en_fulltext
FROM ma_table
WHERE MATCH (colonel [, 2si_je_veux, …])
AGAINST ('la chaine que je cherche' [WITH QUERY EXPANSION | IN BOOLEAN MODE | IN NATURAL LANGUAGE MODE]);

Vous l’aurez compris, les index FULLTEXT ont un grand intérêt lorsqu’il s’agit d’opérer des recherches sur la table. Malheureusement, ce type d’index n’est présent que sur les tables MyISAM. Et vous allez voir par la suite qu’InnoDB possède de nombreux atouts que MyISAM n’a pas. Bref, il faudra faire un choix.

Cefs Primaires

Les clefs sont des contraintes. Nous connaissons les clefs primaires, que nous appliquons en général à la colonne id de nos tables. Une clef primaire ou PRIMARY KEY est en fait un INDEX NOT NULL, elle est donc la somme de deux contraintes. Au cas où vous voudriez mettre un index sur une colonne PRIMARY KEY, c’est inutile puisque KEY est synonyme d’index. Une clef primaire est donc un index.

Cette clef permet de s’assurer que :

Pour la créer, je pense que vous savez faire (on l’a déjà vu plusieurs fois) :

CREATE TABLE ma_table (
    id SMALLINT NOT NULL AUTO_INCREMENT,
    nom char(255) NOT NULL,
    PRIMARY KEY (id)
)
ENGINE= MyISAM | INNODB;

On peut bien entendu le préciser directement dans la colonne. Je ne vous remontre pas comment faire. De plus, on peut créer la clé primaire dans un second temps, avec ALTER TABLE, chose qu’on ne peut pas faire avec CREATE INDEX (je vous l’avais bien dit !).

ALTER TABLE ma_table
ADD PRIMARY KEY (id);

Idem pour la supprimer… enfin vous savez faire.

Clefs Étrangères

Les clefs étrangères permettent de croiser les données de plusieurs tables en s’assurant de la bonne correspondance de celles-ci. Je m’explique : vous avez deux tables, la première contient la liste de tous vos films, la seconde la liste des catégories dans lesquelles sont classés les films. Pourquoi ne met-on pas tout dans une seule table ?

Admettons qu’il y ait cinq catégories : thrillers, comédie, animation, documentaires, famille. Si vous répétez ça par le nombre de films que vous avez (disons 500) ça vous fait un paquet de données redondantes à enregistrer, et autant de place perdue.

Vous allez donc mettre ces 5 catégories dans une autre table et placerez l’id correspondant au format dans la table des films. Le but des clefs étrangères est donc que si vous avez 5 formats et que leurs identifiants sont respectivement 1, 2, 3, 4 et 5, elles vous éviteront d’avoir un id 7, qui ne correspondrait à rien. C’est un contrôle d’intégrité en somme.

2, 3 trucs à savoir :

Maintenant que vous savez ça, you’re good to go ! On doit d’abord créer la table qui sert de référence, sinon MySQL lèvera une erreur et ne pourra pas créer la table qui reçoit les clefs étrangères. Comment voulez vous créer une clef étrangère sur quelque chose qui n’existe pas ?!

CREATE TABLE Categories
(
    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(30) NOT NULL,
    PRIMARY KEY (id)
)
ENGINE = INNODB;


-- puis la table films
CREATE TABLE Films
(
    id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
    title VARCHAR(100) NOT NULL,
    category SMALLINT UNSIGNED,
    PRIMARY KEY (id),
    CONSTRAINT fk_cats
        FOREIGN KEY (category)
        REFERENCES Categories(id)
)
ENGINE=INNODB;

Le [CONSTRAINT [ma_clef_etrangere]] est utile si on veut définir le nom de la clef. Bien qu’on puisse aussi faire CONSTRAINT FOREIGN KEY sans préciser de nom, il est toujours utile de nommer ses clefs étrangères pour pouvoir les désigner plus tard afin de les modifiers ou de les supprimer.

Comme toujours, on peut l’ajouter après la création de la table :

ALTER TABLE nom_table
ADD CONSTRAINT fk_cats FOREIGN KEY (category) REFERENCES Categories(id);


-- Et pour la supprimer :
ALTER TABLE nom_table
DROP FOREIGN KEY symbole_contrainte

Les options des clefs étrangères

Les clefs étrangères permettent de maintenir une certaine cohérence au sein de nos tables. Il ne faudrait donc pas que l’on puisse supprimer ou modifier une table de référence sans quoi toutes les clefs étrangères n’auraient plus de sens. D’ailleurs, si vous tentez de supprimer une référence alors alors que dans d’autres tables, des clefs lui sont liées, ça lèvera une erreur. Il est justement possible de définir le comportement par défaut pour ce genre de cas.

Nous avons 4 options à notre disposition :

Dans notre exemple précédent. Si je met un SET NULL à la clef étrangère category, si je supprime la cotégorie thrillers dans ma tables Categories, tous les films qui avaient pour catégorie thrillers, auront dorénavant NULL comme catégorie. En revanche, si le réglage était fait sur CASCADE, tous les films ayant pour catégorie thrillers, auraient été supprimés de la table.

ALTER TABLE nom_table
ADD [CONSTRAINT nom_clef_etrangere]
    FOREIGN KEY (colonne)
    REFERENCES table_ref(col_ref)
    ON DELETE | UPDATE {RESTRICT | NO ACTION | SET NULL | CASCADE};

Problème de FOREIGN KEY

On rencontre parfois des problèmes lors de la suppression où de la mise à jour de colonnes dans une table, à cause des clef étrangères. Il est souvent très instructif d’afficher le statut du moteur de table (INNODB) SHOW ENGINE INNODB STATUS et de jeter un œil à la section des dernières erreurs concernant les FOREIGN KEYS (LATEST FOREIGN KEY ERROR).

SHOW ENGINE INNODB STATUS
------------------------
LATEST FOREIGN KEY ERROR
------------------------
141215 22:11:15 Error in foreign key constraint of table buzut/#sql-6b09_41276:
 FOREIGN KEY (articles_id) REFERENCES Articles(id) ON DELETE SET NULL:
You have defined a SET NULL condition though some of the
columns are defined as NOT NULL.

Les jointures

Les jointures permettent de rassembler deux tables (ou plus). On a vu avec les clefs étrangères qu’on peut vouloir stocker certaines info dans une seconde table. Si l’on veut récupérer ces infos comme si elles faisaient partie d’une seule et même table, on utilisera les jointures !

Il y a deux sortes de jointures, les internes et les externes. Les jointures internes retournent les résultats pour les colonnes qui ont une correspondance dans chacune des tables, tandis que les jointures externes retournent toutes les colonnes.

Dans notre exemple des catégories, si nous n’avons aucun film qui a pour catégorie vacances (disons qu’on en a rajouté), avec une jointure interne, cette catégorie ne sera pas affichée, car aucun film ne lui correspond. Avec une jointure externe en revanche, on retrouvera vacances dans notre tableau.

Jointure interne

SELECT nom_table.nom_colonne
FROM nom_table
[INNER] JOIN nom_2eme_table
ON colonne1 = colonne2
WHERE ce_quil_te_faut;

Pour le SELECT, on n’est pas obligé de préciser le nom de la table, sauf si celui-ci existe dans les deux tables. Il est ici judicieux de mettre un alias. La jointure est par défaut interne, il est donc inutile de spécifier INNER. ON précise les critères à remplir pour effectuer la jointure, ce sera le plus souvent =, mais les autres opérateurs sont valables aussi.

Si on veut par exemple récupérer tous les films correspondant à la catégorie thrillers :

SELECT title
FROM Films
JOIN Categories
ON category = Categories.id
WHERE Categories.name = 'thrillers';
+--------------------+
| title              |
+--------------------+
| The punisher       |
| Le pacte des loups |
| LIMITLESS          |
| Film down          |
| RAPT               |
| Secret Defense     |
| Michel Vaillant    |
| Largo Winch        |
| Wonderland         |
| Laffaire Farewell  |
| Constantine        |
| Cloverfield        |
+--------------------+
12 rows in set (0.00 sec)

Jointure externe

Il faut savoir que pour les jointure externe il y a deux modes. La jointure par la droite et la jointure par la gauche. C’est à dire que si on fait une jointure par la gauche, nous voulons toutes les données de la table de gauche, même si elle n’a pas de correspondance dans la table de droite, et vice versa.

Quelle est la droite, quelle est la gauche. Easy as abc, baby ! On lit de gauche à droite, donc la table de gauche est la première à être mentionnée. Comme tout ceci n’est pas bien clair, on va prendre un exemple. Disons que je veux afficher tous mes films avec leur catégories correspondantes. Le nom des catégories pas leur id bien entendu… On va donc faire une jointure par la gauche pour afficher aussi les films qui n’ont pas de catégorie.

SELECT title, Categories.name
FROM Films
LEFT JOIN Categories
ON Films.category = Categories.id;
+-------------------------------------------+------------------+
| title                                     | name             |
+-------------------------------------------+------------------+
| The punisher                              | Thrillers        |
| Le pacte des loups                        | Thrillers        |
| LIMITLESS                                 | Thrillers        |
| Paul                                      | NULL             |
| Film down                                 | Thrillers        |
| RAPT                                      | Thrillers        |
| Secret Defense                            | Thrillers        |
| Michel Vaillant                           | Thrillers        |
| Largo Winch                               | Thrillers        |
| Wonderland                                | Thrillers        |
| Laffaire Farewell                         | Thrillers        |
| Constantine                               | Thrillers        |
| Yan Arthus Bertrand                       | NULL             |
| Cloverfield                               | Thrillers        |
| Wild Wild West                            | NULL             |
| GRAN TORINO                               | NULL             |
| Scrubs S03 E10                            | Scrubs           |
| Scrubs S03 E06                            | Scrubs           |
| Scrubs S03 E09                            | Scrubs           |
| Scrubs S03 E08                            | Scrubs           |
+-------------------------------------------+------------------+
21 rows in set (0.01 sec)

Une jointure interne ne nous aurait ici pas retourné les films n’ayant pas de catégorie. De la même manière, nous pourrions afficher toutes les catégories. Je pense que vous avez compris le principe. C’est exactement la même chose pour les jointures par la droite donc je ne redonne pas d’exemple.

Syntaxes alternatives

Il est possible d’omettre la clause JOIN pour les jointures internes, dans ce cas, la clause ON est remplacée par WHERE. Cependant, il devient difficile de faire la différence entre les conditions de jointure (normalement précisées dans le ON) et les conditions normales (WHERE). À vous de voir…

SELECT *
FROM films, formats
WHERE films.formats = formats.id

Une pratique qui simplifie la vie et qui cette fois peut-être utilisée sans problème, le NATURAL JOIN. Cette jointure interne évite de préciser la clause ON si les champs des deux tables sur lesquelles on veux opérer la jointure portent le même nom.

SELECT *
FROM table
NATURAL JOIN table_de_jointure

Sous requêtes

Ce sont en quelques sortes des requêtes imbriquées. Elle permettent de faire en une requête ce qui qu’il nous aurait fallu faire en plusieurs. Parmi mes films, je voudrais sélectionner le plus récent des thrillers :

SELECT title
FROM (
    SELECT max(Films.id), title
    FROM Films
    JOIN Categories
    ON category = Categories.id
    WHERE Categories.name = 'thrillers'
)
AS newest_film;

Il est évident que les parenthèse servent à différencier la sous-requête de la requête principale, sans elles, vous aurez donc une erreur. Il est aussi obligatoire de nommer un alias pour la requête intermédiaire, sans cela, une erreur sera levée aussi.

Comparaisons

Il est possible d’utiliser d’autres clauses que le FROM dans les sous-requêtes, cependant, il faut que la sous requête renvoie soit une seule valeur (donc une seule ligne et une seule colonne), soit une seule ligne, soit une seule colonne.

Les opérateurs logiques

Dans le cas où la réponse retournée par la sous-requête est une unique valeur, on peut utiliser tous les opérateurs logiques habituels en lieu et place du from (<, >, =…).

Si la réponse retourne une ligne, on peut utiliser les opérateurs = et !=.

IN et NOT IN

Ces opérateurs signifient “se trouve” ou “ne se trouve pas” dans la sous requête. Ils sont utilisable avec les requêtes qui renvoient une valeur ou une colonne.

ANY, SOME et ALL

ANY signifie “au moins une des valeurs” tandis que ALL signifie “toutes les valeurs”. SOME quant à lui est synonyme de ANY. Ces opérateurs sont spécifiques aux sous-requêtes, vous ne les trouverez pas ailleurs dans MySQL. Ils s’appliquent dans les mêmes conditions que IN et NOT IN, c’est à dire lorsque la requête renvoie une unique valeur ou une colonne.

Je n’ai pas d’exemple à vous donner concernant les comparaisons, cependant, ils s’utilisent de la même manière qu’avec le FROM.

Corrélation de requêtes

Un peu comme avec les jointures, on peut faire référence dans une sous requête à une table qui n’est pas définie dans sa clause FROM. Je vais donner un petit exemple. La requête que nous allons faire ne rime pas à grand chose, mais c’est pas grave. On va sélectionner tous les films dont les id correspondent à l’id d’une catégorie. Je sais c’est un peu WTF, mais si je vous disais un peu plus haut ne pas avoir d’exemple, ce n’est pas pour rien !

SELECT id, title, category
FROM Films
WHERE category IN (
    SELECT id
    FROM Categories
    WHERE Categories.id = Films.id
);
+----+-------------------------------------------+----------+
| id | title                                     | category |
+----+-------------------------------------------+----------+
|  1 | Qu'est-ce que c'est être étudiant ?       |        1 |
|  2 | VTT pedralta                              |        2 |
| 10 | Futurama S01 E03                          |       10 |
+----+-------------------------------------------+----------+
3 rows in set (0.00 sec)

Attention cependant, ce fonctionnement marche en remontant d’un ou plusieurs niveau. Ce qui veut dire qu’une sous sous requête pour aller chercher la table correspondante deux niveaux au-dessus (ou plus), mais pas dans une autre sous requête de même niveau ou de niveau inférieur.

UNION

UNION permet de rassembler plusieurs SELECT dans une même requête :

SELECT *
FROM table1
UNION
SELECT *
FROM table2

Les requêtes doivent avoir le même nombre de colonnes, il faut aussi que les colonnes correspondantes soient de même type (bien que MySQL ne lève pas d’erreur pour ça, c’est bien pour la cohérence).

UNION élimine automatiquement les doublons. Si vous voulez les afficher, il faut faire UNION ALL.

Les cas LIMIT et ORDER BY

Il est possible d’opérer un LIMIT sur une requête ou sur l’ensemble des deux requêtes :

-- Ne sélectionne que les 3 premiers résultats de la première requête
SELECT *
FROM table1
LIMIT 3
UNION
SELECT *
FROM table2;
-- Ne sélectionne que les 3 premiers résultats de la totalité des requêtes
SELECT *
FROM table1
UNION
SELECT *
FROM table2
LIMIT 3;

Pour faire un LIMIT sur la seconde (ou plus) requête, utilisez les parenthèses :

SELECT *
FROM table1
UNION
(SELECT *
FROM table2
LIMIT 3);

Pour le ORDER BY, il n’est possible de l’utiliser que pour le résultat d’ensemble, pas pour des requêtes individuelles.

SELECT *
FROM table1
UNION
SELECT *
FROM table2
ORDER BY ID

Sauf, dans le cas où on lui adjoint un LIMIT !

SELECT *
FROM table1
ORDER BY ID DESC
LIMIT 8
UNION
SELECT *
FROM table2

Commentaires

Rejoignez la discussion !

Vous pouvez utiliser Markdown pour les liens [ancre de lien](url), la mise en *italique* et en **gras**. Enfin pour le code, vous pouvez utiliser la syntaxe `inline` et la syntaxe bloc

```
ceci est un bloc
de code
```