452 votes

Requête SQL retournant les données de plusieurs tables

Je voudrais savoir ce qui suit :

  • comment obtenir des données de plusieurs tables dans ma base de données ?
  • quels sont les types de méthodes pour y parvenir ?
  • que sont les unions et les syndicats et en quoi sont-ils différents les uns des autres ?
  • Quand dois-je utiliser chacun d'eux par rapport aux autres ?

Je prévois de l'utiliser dans mon application (par exemple - PHP), mais je ne veux pas exécuter plusieurs requêtes contre la base de données, quelles options ai-je pour obtenir des données de plusieurs tables dans une seule requête ?

Note : J'écris ceci car j'aimerais pouvoir faire un lien vers un guide bien écrit sur les nombreuses questions que je rencontre constamment dans la file d'attente PHP, afin de pouvoir faire un lien vers ce guide pour plus de détails lorsque je publie une réponse.

Les réponses couvrent les points suivants :

  1. Partie 1 - Joints et unions
  2. Partie 2 - Sous-requêtes
  3. Partie 3 - Trucs et astuces et code efficace
  4. Partie 4 - Sous-requêtes dans la clause From
  5. Partie 5 - Un mélange d'astuces de John

491voto

Fluffeh Points 21893

Partie 1 - Joints et unions

Cette réponse couvre :

  1. Partie 1
    • La jointure de deux ou plusieurs tables à l'aide d'une jointure interne (voir la rubrique entrée de wikipédia pour plus d'informations)
    • Comment utiliser une requête d'union
    • Joints externes gauche et droit (cette réponse de stackOverflow est excellent pour décrire les types de jointures)
    • Requêtes d'intersection (et comment les reproduire si votre base de données ne les supporte pas) - il s'agit d'une fonction de SQL-Server ( voir info ) et une partie de la la raison pour laquelle j'ai écrit tout ça en premier lieu.
  2. Partie 2
    • Sous-requêtes - ce qu'elles sont, où elles peuvent être utilisées et ce dont il faut se méfier
    • Cartesian joins AKA - Oh, la misère !

Il existe plusieurs façons d'extraire des données de plusieurs tables dans une base de données. Dans cette réponse, je vais utiliser la syntaxe de jointure ANSI-92. Cela peut être différent d'un certain nombre d'autres tutoriels qui utilisent l'ancienne syntaxe ANSI-89 (et si vous êtes habitué à la syntaxe 89, cela peut sembler beaucoup moins intuitif - mais tout ce que je peux dire, c'est d'essayer) car elle est beaucoup plus facile à comprendre lorsque les requêtes commencent à devenir plus complexes. Pourquoi l'utiliser ? Y a-t-il un gain de performance ? Le site réponse courte est non, mais il est plus facile à lire une fois qu'on s'y est habitué. Il est plus facile de lire des requêtes écrites par d'autres personnes utilisant cette syntaxe.

Je vais également utiliser le concept d'une petite cariole qui dispose d'une base de données pour suivre les voitures disponibles. Le propriétaire vous a engagé comme informaticien et s'attend à ce que vous soyez capable de lui fournir les données qu'il demande en un clin d'œil.

J'ai créé un certain nombre de tables de consultation qui seront utilisées par la table finale. Cela nous donnera un modèle raisonnable à partir duquel travailler. Pour commencer, je vais exécuter mes requêtes sur une base de données d'exemple qui a la structure suivante. J'essaierai de penser aux erreurs courantes que l'on commet lorsqu'on débute et j'expliquerai ce qui ne va pas avec elles - tout en montrant bien sûr comment les corriger.

Le premier tableau est simplement une liste de couleurs afin que nous sachions quelles couleurs nous avons dans le parc automobile.

mysql> create table colors(id int(3) not null auto_increment primary key, 
    -> color varchar(15), paint varchar(10));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from colors;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)

mysql> insert into colors (color, paint) values ('Red', 'Metallic'), 
    -> ('Green', 'Gloss'), ('Blue', 'Metallic'), 
    -> ('White' 'Gloss'), ('Black' 'Gloss');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from colors;
+----+-------+----------+
| id | color | paint    |
+----+-------+----------+
|  1 | Red   | Metallic |
|  2 | Green | Gloss    |
|  3 | Blue  | Metallic |
|  4 | White | Gloss    |
|  5 | Black | Gloss    |
+----+-------+----------+
5 rows in set (0.00 sec)

Le tableau des marques identifie les différentes marques de voitures que les caryards pourraient éventuellement vendre.

mysql> create table brands (id int(3) not null auto_increment primary key, 
    -> brand varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from brands;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| brand | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.01 sec)

mysql> insert into brands (brand) values ('Ford'), ('Toyota'), 
    -> ('Nissan'), ('Smart'), ('BMW');
Query OK, 5 rows affected (0.00 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from brands;
+----+--------+
| id | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  3 | Nissan |
|  4 | Smart  |
|  5 | BMW    |
+----+--------+
5 rows in set (0.00 sec)

Le tableau des modèles couvrira différents types de voitures, il sera plus simple pour cela d'utiliser différents types de voitures plutôt que des modèles de voitures réels.

mysql> create table models (id int(3) not null auto_increment primary key, 
    -> model varchar(15));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from models;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| model | varchar(15) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> insert into models (model) values ('Sports'), ('Sedan'), ('4WD'), ('Luxury');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from models;
+----+--------+
| id | model  |
+----+--------+
|  1 | Sports |
|  2 | Sedan  |
|  3 | 4WD    |
|  4 | Luxury |
+----+--------+
4 rows in set (0.00 sec)

Et enfin, pour lier toutes ces autres tables, la table qui lie tout ensemble. Le champ ID est en fait le numéro de lot unique utilisé pour identifier les voitures.

mysql> create table cars (id int(3) not null auto_increment primary key, 
    -> color int(3), brand int(3), model int(3));
Query OK, 0 rows affected (0.01 sec)

mysql> show columns from cars;
+-------+--------+------+-----+---------+----------------+
| Field | Type   | Null | Key | Default | Extra          |
+-------+--------+------+-----+---------+----------------+
| id    | int(3) | NO   | PRI | NULL    | auto_increment |
| color | int(3) | YES  |     | NULL    |                |
| brand | int(3) | YES  |     | NULL    |                |
| model | int(3) | YES  |     | NULL    |                |
+-------+--------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

mysql> insert into cars (color, brand, model) values (1,2,1), (3,1,2), (5,3,1), 
    -> (4,4,2), (2,2,3), (3,5,4), (4,1,3), (2,2,1), (5,2,3), (4,5,1);
Query OK, 10 rows affected (0.00 sec)
Records: 10  Duplicates: 0  Warnings: 0

mysql> select * from cars;
+----+-------+-------+-------+
| id | color | brand | model |
+----+-------+-------+-------+
|  1 |     1 |     2 |     1 |
|  2 |     3 |     1 |     2 |
|  3 |     5 |     3 |     1 |
|  4 |     4 |     4 |     2 |
|  5 |     2 |     2 |     3 |
|  6 |     3 |     5 |     4 |
|  7 |     4 |     1 |     3 |
|  8 |     2 |     2 |     1 |
|  9 |     5 |     2 |     3 |
| 10 |     4 |     5 |     1 |
+----+-------+-------+-------+
10 rows in set (0.00 sec)

Cela nous donnera suffisamment de données (je l'espère) pour couvrir les exemples ci-dessous de différents types de jointures et aussi suffisamment de données pour les rendre utiles.

Donc, en entrant dans le vif du sujet, le patron veut savoir Les numéros d'identification de toutes les voitures de sport qu'il possède. .

Il s'agit d'une simple jointure de deux tables. Nous avons une table qui identifie le modèle et une table qui contient le stock disponible. Comme vous pouvez le voir, les données dans la table model de la colonne cars se rapporte à la models de la colonne cars que nous avons. Maintenant, nous savons que la table des modèles a un ID de 1 pour Sports alors écrivons la jointure.

select
    ID,
    model
from
    cars
        join models
            on model=ID

Cette requête semble bonne, non ? Nous avons identifié les deux tables qui contiennent les informations dont nous avons besoin et nous utilisons une jointure qui identifie correctement les colonnes à joindre.

ERROR 1052 (23000): Column 'ID' in field list is ambiguous

Oh non ! Une erreur dans notre première requête ! Oui, et c'est une prune. Vous voyez, la requête contient bien les bonnes colonnes, mais certaines d'entre elles existent dans les deux tables, si bien que la base de données ne sait plus de quelle colonne il s'agit et où. Il y a deux solutions pour résoudre ce problème. La première est très simple, nous pouvons utiliser tableName.columnName pour dire à la base de données exactement ce que nous voulons dire, comme ceci :

select
    cars.ID,
    models.model
from
    cars
        join models
            on cars.model=models.ID

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
|  2 | Sedan  |
|  4 | Sedan  |
|  5 | 4WD    |
|  7 | 4WD    |
|  9 | 4WD    |
|  6 | Luxury |
+----+--------+
10 rows in set (0.00 sec)

L'autre est probablement plus souvent utilisée et est appelée aliasing de table. Les tables de cet exemple ont des noms simples et courts, mais en tapant quelque chose comme KPI_DAILY_SALES_BY_DEPARTMENT deviendrait rapidement obsolète. Une façon simple de procéder est de donner un surnom à la table comme ceci :

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID

Maintenant, revenons à la demande. Comme vous pouvez le voir, nous avons les informations dont nous avons besoin, mais nous avons également des informations qui n'ont pas été demandées, nous devons donc inclure une clause where dans la déclaration pour obtenir uniquement les voitures de sport demandées. Comme je préfère la méthode des alias de table à l'utilisation répétée des noms de table, je vais m'y tenir à partir de maintenant.

Il est clair que nous devons ajouter une clause where à notre requête. Nous pouvons identifier les voitures de sport soit par ID=1 o model='Sports' . Comme l'ID est indexé et qu'il s'agit de la clé primaire (et qu'il se trouve qu'elle est moins typée), utilisons-la dans notre requête.

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Bingo ! Le patron est content. Bien sûr, comme il est un patron et qu'il n'est jamais satisfait de ce qu'il a demandé, il regarde les informations, puis dit Je veux aussi les couleurs .

Bon, nous avons donc déjà écrit une bonne partie de notre requête, mais nous devons utiliser une troisième table, celle des couleurs. Maintenant, notre table d'information principale cars stocke l'ID de la couleur de la voiture, ce qui renvoie à la colonne ID des couleurs. Donc, d'une manière similaire à l'original, nous pouvons joindre une troisième table :

select
    a.ID,
    b.model
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  3 | Sports |
|  8 | Sports |
| 10 | Sports |
+----+--------+
4 rows in set (0.00 sec)

Bon sang, bien que la table ait été correctement jointe et que les colonnes liées aient été reliées, nous avons oublié de tirer dans le véritable information de la nouvelle table que nous venons de lier.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
where
    b.ID=1

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
+----+--------+-------+
4 rows in set (0.00 sec)

Bien, le patron n'est plus sur notre dos pour un moment. Maintenant, pour expliquer un peu plus en détail. Comme vous pouvez le voir, le from dans notre déclaration relie notre tableau principal (j'utilise souvent un tableau qui contient des informations plutôt qu'un tableau de consultation ou de dimension. La requête fonctionnerait tout aussi bien si les tables étaient interverties, mais elle aurait moins de sens si nous revenions à cette requête pour la lire dans quelques mois. Il est donc souvent préférable d'essayer d'écrire une requête qui sera agréable et facile à comprendre - présentez-la de manière intuitive, utilisez de belles indentations pour que tout soit aussi clair que possible. Si vous enseignez ensuite à d'autres personnes, essayez d'inculquer ces caractéristiques à leurs requêtes, surtout si vous devez les dépanner.

Il est tout à fait possible de relier de plus en plus de tables de cette manière.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Bien que j'aie oublié d'inclure un tableau dans lequel nous pourrions vouloir joindre plus d'une colonne dans le fichier join Voici un exemple. Si le models avait des modèles spécifiques à une marque et avait donc aussi une colonne appelée brand qui renvoie à la brands table sur le ID on pourrait procéder comme suit :

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
            and b.brand=d.ID
where
    b.ID=1

Vous pouvez voir que la requête ci-dessus ne relie pas seulement les tables jointes à la table principale. cars mais spécifie également les jointures entre les tables déjà jointes. Si cela n'a pas été fait, le résultat est appelé jointure cartésienne - ce qui est le terme de dba pour mauvais. Une jointure cartésienne est une jointure où des lignes sont renvoyées parce que les informations ne disent pas à la base de données comment limiter les résultats, de sorte que la requête renvoie tous les lignes qui répondent aux critères.

Ainsi, pour donner un exemple de jointure cartésienne, exécutons la requête suivante :

select
    a.ID,
    b.model
from
    cars a
        join models b

+----+--------+
| ID | model  |
+----+--------+
|  1 | Sports |
|  1 | Sedan  |
|  1 | 4WD    |
|  1 | Luxury |
|  2 | Sports |
|  2 | Sedan  |
|  2 | 4WD    |
|  2 | Luxury |
|  3 | Sports |
|  3 | Sedan  |
|  3 | 4WD    |
|  3 | Luxury |
|  4 | Sports |
|  4 | Sedan  |
|  4 | 4WD    |
|  4 | Luxury |
|  5 | Sports |
|  5 | Sedan  |
|  5 | 4WD    |
|  5 | Luxury |
|  6 | Sports |
|  6 | Sedan  |
|  6 | 4WD    |
|  6 | Luxury |
|  7 | Sports |
|  7 | Sedan  |
|  7 | 4WD    |
|  7 | Luxury |
|  8 | Sports |
|  8 | Sedan  |
|  8 | 4WD    |
|  8 | Luxury |
|  9 | Sports |
|  9 | Sedan  |
|  9 | 4WD    |
|  9 | Luxury |
| 10 | Sports |
| 10 | Sedan  |
| 10 | 4WD    |
| 10 | Luxury |
+----+--------+
40 rows in set (0.00 sec)

Bon dieu, c'est moche. Cependant, en ce qui concerne la base de données, c'est exactement ce qui a été demandé. Dans la requête, nous avons demandé pour le ID de cars y el model de models . Cependant, comme nous n'avons pas spécifié comment pour joindre les tables, la base de données a fait correspondre chaque de la première table avec chaque de la deuxième table.

Ok, donc le patron est de retour, et il veut encore plus d'informations. Je veux la même liste, mais en y incluant les 4x4. .

Cependant, cela nous donne une excellente excuse pour examiner deux façons différentes d'y parvenir. Nous pourrions ajouter une autre condition à la clause where comme ceci :

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
    or b.ID=3

Bien que la méthode ci-dessus fonctionne parfaitement bien, regardons-la différemment, c'est une excellente excuse pour montrer comment une union La requête fonctionnera.

Nous savons que les suivantes rendront toutes les voitures de sport :

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1

Et les suivants rendraient tous les 4x4 :

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

Ainsi, en ajoutant un union all entre elles, les résultats de la seconde requête seront ajoutés aux résultats de la première.

select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=1
union all
select
    a.ID,
    b.model,
    c.color
from
    cars a
        join models b
            on a.model=b.ID
        join colors c
            on a.color=c.ID
        join brands d
            on a.brand=d.ID
where
    b.ID=3

+----+--------+-------+
| ID | model  | color |
+----+--------+-------+
|  1 | Sports | Red   |
|  8 | Sports | Green |
| 10 | Sports | White |
|  3 | Sports | Black |
|  5 | 4WD    | Green |
|  7 | 4WD    | White |
|  9 | 4WD    | Black |
+----+--------+-------+
7 rows in set (0.00 sec)

Comme vous pouvez le constater, les résultats de la première requête sont renvoyés en premier, suivis des résultats de la deuxième requête.

Dans cet exemple, il aurait bien sûr été beaucoup plus facile d'utiliser simplement la première requête, mais union Les requêtes peuvent être excellentes pour des cas spécifiques. Elles constituent un excellent moyen de renvoyer des résultats spécifiques à partir de tables qui ne sont pas facilement reliées entre elles - ou d'ailleurs complètement des tables sans rapport entre elles. Il y a cependant quelques règles à suivre.

  • Les types de colonnes de la première requête doivent correspondre aux types de colonnes de toutes les autres requêtes suivantes.
  • Les noms des colonnes de la première requête seront utilisés pour identifier l'ensemble des résultats.
  • Le nombre de colonnes dans chaque requête doit être le même.

Maintenant, vous pourriez se demander ce que le La différence se situe entre l'utilisation union y union all . A union supprimera les doublons, tandis que la requête union all ne le fera pas. Cela signifie qu'il y a une légère baisse de performance lors de l'utilisation de l'application union sur union all mais les résultats peuvent en valoir la peine - je ne vais pas spéculer sur ce genre de choses dans le cadre de ce dossier.

Sur ce point, il pourrait être utile de noter quelques remarques supplémentaires ici.

  • Si nous voulions ordonner les résultats, nous pouvons utiliser une fonction order by mais vous ne pouvez plus utiliser l'alias. Dans la requête ci-dessus, l'ajout d'un order by a.ID entraînerait une erreur - en ce qui concerne les résultats, la colonne est appelée ID plutôt que a.ID - même si le même alias a été utilisé dans les deux requêtes.
  • Nous ne pouvons avoir qu'un seul order by et il doit s'agir de la dernière déclaration.

Pour les prochains exemples, je vais ajouter quelques lignes supplémentaires à nos tableaux.

J'ai ajouté Holden dans le tableau des marques. J'ai également ajouté une ligne dans cars qui a le color valeur de 12 - qui n'a pas de référence dans le tableau des couleurs.

Ok, le patron est de retour, aboyant des demandes - *Je veux un compte de chaque marque que nous portons et le nombre de voitures dans celle-ci!` - Typique, nous arrivons juste à une section intéressante de notre discussion et le patron veut plus de travail.

Rightyo, donc la première chose à faire est d'obtenir une liste complète des marques possibles.

select
    a.brand
from
    brands a

+--------+
| brand  |
+--------+
| Ford   |
| Toyota |
| Nissan |
| Smart  |
| BMW    |
| Holden |
+--------+
6 rows in set (0.00 sec)

Maintenant, lorsque nous joignons cette table à celle des voitures, nous obtenons le résultat suivant :

select
    a.brand
from
    brands a
        join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Nissan |
| Smart  |
| Toyota |
+--------+
5 rows in set (0.00 sec)

Ce qui est bien sûr un problème - nous ne voyons aucune mention de l'adorable Holden marque que j'ai ajoutée.

C'est parce qu'une jointure cherche des lignes correspondantes dans ambos tables. Comme il n'y a pas de données dans les voitures qui soient de type Holden il n'est pas renvoyé. C'est là que nous pouvons utiliser un outer rejoindre. Cela donnera tous les résultats d'une table, qu'ils correspondent ou non à ceux de l'autre table :

select
    a.brand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+
| brand  |
+--------+
| BMW    |
| Ford   |
| Holden |
| Nissan |
| Smart  |
| Toyota |
+--------+
6 rows in set (0.00 sec)

Maintenant que nous l'avons, nous pouvons ajouter une belle fonction d'agrégation pour obtenir un compte et nous débarrasser du patron pour un moment.

select
    a.brand,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
group by
    a.brand

+--------+--------------+
| brand  | countOfBrand |
+--------+--------------+
| BMW    |            2 |
| Ford   |            2 |
| Holden |            0 |
| Nissan |            1 |
| Smart  |            1 |
| Toyota |            5 |
+--------+--------------+
6 rows in set (0.00 sec)

Et avec ça, le patron s'en va en se cachant.

Pour expliquer cela plus en détail, les jointures externes peuvent être de type left o right type. La gauche ou la droite définit quelle table est entièrement inclus. A left outer join inclura toutes les lignes du tableau de gauche, tandis que (vous l'avez deviné) un fichier right outer join apporte tous les résultats du tableau de droite dans les résultats.

Certaines bases de données permettent à un full outer join qui ramènera les résultats (qu'ils soient appariés ou non) à partir de ambos mais cela n'est pas pris en charge par toutes les bases de données.

Je pense qu'à ce stade, vous vous demandez si vous pouvez ou non fusionner les types de jointures dans une requête, et la réponse est oui, absolument.

select
    b.brand,
    c.color,
    count(a.id) as countOfBrand
from
    cars a
        right outer join brands b
            on b.ID=a.brand
        join colors c
            on a.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
+--------+-------+--------------+
9 rows in set (0.00 sec)

Alors, pourquoi n'est-ce pas les résultats escomptés ? C'est parce que, bien que nous ayons sélectionné la jointure externe des voitures aux marques, elle n'a pas été spécifiée dans la jointure aux couleurs - donc cette jointure particulière ne ramènera que les résultats qui correspondent dans les deux tables.

Voici la requête qui permettrait d'obtenir les résultats escomptés :

select
    a.brand,
    c.color,
    count(b.id) as countOfBrand
from
    brands a
        left outer join cars b
            on a.ID=b.brand
        left outer join colors c
            on b.color=c.ID
group by
    a.brand,
    c.color

+--------+-------+--------------+
| brand  | color | countOfBrand |
+--------+-------+--------------+
| BMW    | Blue  |            1 |
| BMW    | White |            1 |
| Ford   | Blue  |            1 |
| Ford   | White |            1 |
| Holden | NULL  |            0 |
| Nissan | Black |            1 |
| Smart  | White |            1 |
| Toyota | NULL  |            1 |
| Toyota | Black |            1 |
| Toyota | Green |            2 |
| Toyota | Red   |            1 |
+--------+-------+--------------+
11 rows in set (0.00 sec)

Comme nous pouvons le constater, nous avons deux jointures externes dans la requête et les résultats sont conformes aux attentes.

Maintenant, qu'en est-il de ces autres types de jointures, vous vous demandez ? Qu'en est-il des Intersections ?

Toutes les bases de données ne supportent pas l'option intersection mais pratiquement toutes les bases de données vous permettront de créer une intersection par le biais d'une jointure (ou au moins d'une instruction where bien structurée).

Une Intersection est un type de jointure similaire à une union comme décrit ci-dessus - mais la différence est qu'elle sólo renvoie les lignes de données qui sont identiques (et je dis bien identiques) entre les différentes requêtes individuelles jointes par l'union. Seules les lignes qui sont identiques à tous égards seront retournées.

Un exemple simple serait le suivant :

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

Alors qu'une union renverrait toutes les lignes de la table (la première requête renvoyant quelque chose de plus que ID>2 et la seconde chose ayant ID<4 ) qui donnerait un ensemble complet, une requête intersect ne renverrait que la ligne correspondant à id=3 car il répond aux deux critères.

Maintenant, si votre base de données ne prend pas en charge l'option intersect La requête ci-dessus peut être facilement complétée par la requête suivante :

select
    a.ID,
    a.color,
    a.paint
from
    colors a
        join colors b
            on a.ID=b.ID
where
    a.ID>2
    and b.ID<4

+----+-------+----------+
| ID | color | paint    |
+----+-------+----------+
|  3 | Blue  | Metallic |
+----+-------+----------+
1 row in set (0.00 sec)

Si vous souhaitez réaliser une intersection entre deux tables différentes à l'aide d'une base de données qui ne prend pas en charge de manière inhérente une requête d'intersection, vous devrez créer une jointure sur chaque colonne des tables.

3 votes

@Fluffeh Bonnes réponses. J'ai une suggestion : Si vous voulez en faire un tutoriel SQL qui tue, il ne vous manque plus qu'à ajouter les diagrammes de Venn ; j'ai tout de suite compris les jointures gauche et droite grâce à eux. Une demande personnelle : Avez-vous un tutoriel sur les erreurs courantes/le réglage des performances ?

28 votes

Oh là là. Ma molette de défilement est cassée. Superbe question et réponse. J'aimerais pouvoir le noter 10 fois.

4 votes

Hehe, merci pour le retour positif. Continuez à faire défiler, ce n'était que la première réponse. SO a dit que ma réponse était trop longue pour tenir dans une seule "réponse", alors j'ai dû en utiliser plusieurs :)

103voto

John Woo Points 132738

Ok, j'ai trouvé ce post très intéressant et j'aimerais partager certaines de mes connaissances sur la création d'une requête. Merci pour ce Fluffeh . Les autres personnes qui lisent ce texte et qui pensent que je me trompe sont libres à 101% de modifier et de critiquer ma réponse. ( Honnêtement, je me sens très reconnaissant d'avoir corrigé mon ou mes erreurs. )

Je publierai certaines des questions les plus fréquemment posées en MySQL étiquette.


Trick No. 1 ( les lignes qui correspondent à des conditions multiples )

Compte tenu de ce schéma

CREATE TABLE MovieList
(
    ID INT,
    MovieName VARCHAR(25),
    CONSTRAINT ml_pk PRIMARY KEY (ID),
    CONSTRAINT ml_uq UNIQUE (MovieName)
);

INSERT INTO MovieList VALUES (1, 'American Pie');
INSERT INTO MovieList VALUES (2, 'The Notebook');
INSERT INTO MovieList VALUES (3, 'Discovery Channel: Africa');
INSERT INTO MovieList VALUES (4, 'Mr. Bean');
INSERT INTO MovieList VALUES (5, 'Expendables 2');

CREATE TABLE CategoryList
(
    MovieID INT,
    CategoryName VARCHAR(25),
    CONSTRAINT cl_uq UNIQUE(MovieID, CategoryName),
    CONSTRAINT cl_fk FOREIGN KEY (MovieID) REFERENCES MovieList(ID)
);

INSERT INTO CategoryList VALUES (1, 'Comedy');
INSERT INTO CategoryList VALUES (1, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Romance');
INSERT INTO CategoryList VALUES (2, 'Drama');
INSERT INTO CategoryList VALUES (3, 'Documentary');
INSERT INTO CategoryList VALUES (4, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Comedy');
INSERT INTO CategoryList VALUES (5, 'Action');

QUESTION

Trouvez tous les films qui appartiennent à au moins ambos Comedy y Romance catégories.

Solution

Cette question peut parfois être très délicate. Il peut sembler qu'une requête comme celle-ci soit la réponse :-.

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName = 'Comedy' AND
        b.CategoryName = 'Romance'

Démonstration de SQLFiddle

ce qui est définitivement très mauvais car cela produit aucun résultat . L'explication est qu'il n'y a qu'une seule valeur valide de CategoryName en chaque ligne . Par exemple, la première condition renvoie vrai la deuxième condition est toujours fausse. Ainsi, en utilisant AND les deux conditions doivent être vraies, sinon, elles seront fausses. Une autre requête est comme ceci,

SELECT  DISTINCT a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')

Démonstration de SQLFiddle

et le résultat est toujours incorrect car il correspond à un enregistrement qui a au moins un match sur le categoryName . Le site véritable solution serait de compter le nombre d'instances d'enregistrement par film . Le nombre d'instances doit correspondre au nombre total de valeurs fournies dans la condition.

SELECT  a.MovieName
FROM    MovieList a
        INNER JOIN CategoryList b
            ON a.ID = b.MovieID
WHERE   b.CategoryName IN ('Comedy','Romance')
GROUP BY a.MovieName
HAVING COUNT(*) = 2

Démonstration de SQLFiddle (la réponse)


Truc n°2 ( enregistrement maximal pour chaque entrée )

Schéma donné,

CREATE TABLE Software
(
    ID INT,
    SoftwareName VARCHAR(25),
    Descriptions VARCHAR(150),
    CONSTRAINT sw_pk PRIMARY KEY (ID),
    CONSTRAINT sw_uq UNIQUE (SoftwareName)  
);

INSERT INTO Software VALUES (1,'PaintMe','used for photo editing');
INSERT INTO Software VALUES (2,'World Map','contains map of different places of the world');
INSERT INTO Software VALUES (3,'Dictionary','contains description, synonym, antonym of the words');

CREATE TABLE VersionList
(
    SoftwareID INT,
    VersionNo INT,
    DateReleased DATE,
    CONSTRAINT sw_uq UNIQUE (SoftwareID, VersionNo),
    CONSTRAINT sw_fk FOREIGN KEY (SOftwareID) REFERENCES Software(ID)
);

INSERT INTO VersionList VALUES (3, 2, '2009-12-01');
INSERT INTO VersionList VALUES (3, 1, '2009-11-01');
INSERT INTO VersionList VALUES (3, 3, '2010-01-01');
INSERT INTO VersionList VALUES (2, 2, '2010-12-01');
INSERT INTO VersionList VALUES (2, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 3, '2011-12-01');
INSERT INTO VersionList VALUES (1, 2, '2010-12-01');
INSERT INTO VersionList VALUES (1, 1, '2009-12-01');
INSERT INTO VersionList VALUES (1, 4, '2012-12-01');

QUESTION

Trouvez la dernière version de chaque logiciel. Affichez les colonnes suivantes : SoftwareName , Descriptions , LatestVersion ( de la colonne VersionNo ), DateReleased

Solution

Certains développeurs SQL utilisent par erreur MAX() fonction d'agrégation. Ils ont tendance à créer comme ceci,

SELECT  a.SoftwareName, a.Descriptions,
        MAX(b.VersionNo) AS LatestVersion, b.DateReleased
FROM    Software a
        INNER JOIN VersionList b
            ON a.ID = b.SoftwareID
GROUP BY a.ID
ORDER BY a.ID

Démonstration de SQLFiddle

( La plupart des SGBDR génèrent une erreur de syntaxe à ce sujet, car certaines des colonnes non agrégées ne sont pas spécifiées dans le fichier de données de l'utilisateur. group by clause ) le résultat donne le bon LatestVersion sur chaque logiciel mais évidemment le DateReleased sont incorrects. MySQL ne prend pas en charge Window Functions y Common Table Expression encore que certains SGBDR le font déjà. La solution à ce problème consiste à créer un fichier subquery qui obtient le maximum individuel versionNo sur chaque logiciel et plus tard être joint sur les autres tables.

SELECT  a.SoftwareName, a.Descriptions,
        b.LatestVersion, c.DateReleased
FROM    Software a
        INNER JOIN
        (
            SELECT  SoftwareID, MAX(VersionNO) LatestVersion
            FROM    VersionList
            GROUP BY SoftwareID
        ) b ON a.ID = b.SoftwareID
        INNER JOIN VersionList c
            ON  c.SoftwareID = b.SoftwareID AND
                c.VersionNO = b.LatestVersion
GROUP BY a.ID
ORDER BY a.ID

Démonstration de SQLFiddle (la réponse)


C'était donc ça. J'en posterai un autre dès que je me rappellerai d'autres. FAQ en MySQL tag. Merci d'avoir lu ce petit article. J'espère que vous y avez trouvé un minimum de connaissances.

MISE À JOUR 1


Truc n°3 ( Trouver le dernier enregistrement entre deux ID )

Schéma donné

CREATE TABLE userList
(
    ID INT,
    NAME VARCHAR(20),
    CONSTRAINT us_pk PRIMARY KEY (ID),
    CONSTRAINT us_uq UNIQUE (NAME)  
);

INSERT INTO userList VALUES (1, 'Fluffeh');
INSERT INTO userList VALUES (2, 'John Woo');
INSERT INTO userList VALUES (3, 'hims056');

CREATE TABLE CONVERSATION
(
    ID INT,
    FROM_ID INT,
    TO_ID INT,
    MESSAGE VARCHAR(250),
    DeliveryDate DATE
);

INSERT INTO CONVERSATION VALUES (1, 1, 2, 'hi john', '2012-01-01');
INSERT INTO CONVERSATION VALUES (2, 2, 1, 'hello fluff', '2012-01-02');
INSERT INTO CONVERSATION VALUES (3, 1, 3, 'hey hims', '2012-01-03');
INSERT INTO CONVERSATION VALUES (4, 1, 3, 'please reply', '2012-01-04');
INSERT INTO CONVERSATION VALUES (5, 3, 1, 'how are you?', '2012-01-05');
INSERT INTO CONVERSATION VALUES (6, 3, 2, 'sample message!', '2012-01-05');

QUESTION

Trouvez la dernière conversation entre deux utilisateurs.

Solution

SELECT    b.Name SenderName,
          c.Name RecipientName,
          a.Message,
          a.DeliveryDate
FROM      Conversation a
          INNER JOIN userList b
            ON a.From_ID = b.ID
          INNER JOIN userList c
            ON a.To_ID = c.ID
WHERE     (LEAST(a.FROM_ID, a.TO_ID), GREATEST(a.FROM_ID, a.TO_ID), DeliveryDate)
IN
(
    SELECT  LEAST(FROM_ID, TO_ID) minFROM,
            GREATEST(FROM_ID, TO_ID) maxTo,
            MAX(DeliveryDate) maxDate
    FROM    Conversation
    GROUP BY minFROM, maxTo
)

Démonstration de SQLFiddle

0 votes

Génial ! Un avertissement John, ta première solution ne fonctionne que parce qu'il y a une contrainte unique sur les deux champs. Tu aurais pu utiliser une solution plus générale pour résoudre un problème courant. À mon avis, la seule solution consiste à effectuer des sélections individuelles pour les champs suivants comedy y romance . Having ne convient pas alors

0 votes

@nawfal pas vraiment, si la contrainte unique n'a pas été ajoutée, vous devez alors ajouter distinct sur la clause de l'avoir Démonstration de SQLFiddle :D

65voto

Fluffeh Points 21893

Partie 2 - Sous-requêtes

Ok, maintenant le patron a encore fait irruption - Je veux une liste de toutes nos voitures avec la marque et un total du nombre de voitures de cette marque que nous avons !

C'est une excellente occasion d'utiliser la prochaine astuce de notre sac à malices SQL - la sous-requête. Si vous n'êtes pas familier avec ce terme, une sous-requête est une requête qui s'exécute à l'intérieur d'une autre requête. Il existe de nombreuses façons de les utiliser.

Pour notre demande, nous allons d'abord élaborer une requête simple qui répertorie chaque voiture et sa marque :

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Maintenant, si nous voulions simplement obtenir un nombre de voitures triées par marque, nous pourrions bien sûr écrire ceci :

select
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    b.brand

+--------+-----------+
| brand  | countCars |
+--------+-----------+
| BMW    |         2 |
| Ford   |         2 |
| Nissan |         1 |
| Smart  |         1 |
| Toyota |         5 |
+--------+-----------+

Donc, nous devrions être en mesure d'ajouter simplement la fonction de comptage à notre requête originale, n'est-ce pas ?

select
    a.ID,
    b.brand,
    count(a.ID) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID
group by
    a.ID,
    b.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         1 |
|  2 | Ford   |         1 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         1 |
|  6 | BMW    |         1 |
|  7 | Ford   |         1 |
|  8 | Toyota |         1 |
|  9 | Toyota |         1 |
| 10 | BMW    |         1 |
| 11 | Toyota |         1 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Malheureusement, non, nous ne pouvons pas faire ça. La raison en est que lorsque nous ajoutons l'identifiant de la voiture (colonne a.ID), nous devons l'ajouter au groupe par. Ainsi, lorsque la fonction de comptage fonctionne, il n'y a qu'une seule correspondance par identifiant.

C'est là que nous pouvons cependant utiliser une sous-requête - en fait, nous pouvons faire deux types de sous-requêtes complètement différents qui renverront les mêmes résultats que ceux dont nous avons besoin. La première consiste à placer simplement la sous-requête dans le champ select clause. Cela signifie qu'à chaque fois que nous obtenons une ligne de données, la sous-requête va chercher une colonne de données et l'insérer dans notre ligne de données.

select
    a.ID,
    b.brand,
    (
    select
        count(c.ID)
    from
        cars c
    where
        a.brand=c.brand
    ) as countCars
from
    cars a
        join brands b
            on a.brand=b.ID

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  2 | Ford   |         2 |
|  7 | Ford   |         2 |
|  1 | Toyota |         5 |
|  5 | Toyota |         5 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 11 | Toyota |         5 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  6 | BMW    |         2 |
| 10 | BMW    |         2 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Et Bam !, ça nous ferait du bien. Mais si vous avez remarqué, cette sous-requête devra être exécutée pour chaque ligne de données que nous retournons. Même dans ce petit exemple, nous n'avons que cinq marques de voiture différentes, mais la sous-requête est exécutée onze fois, car nous avons onze lignes de données à renvoyer. Donc, dans ce cas, cela ne semble pas être la manière la plus efficace d'écrire du code.

Pour une approche différente, exécutons une sous-requête et faisons comme si c'était une table :

select
    a.ID,
    b.brand,
    d.countCars
from
    cars a
        join brands b
            on a.brand=b.ID
        join
            (
            select
                c.brand,
                count(c.ID) as countCars
            from
                cars c
            group by
                c.brand
            ) d
            on a.brand=d.brand

+----+--------+-----------+
| ID | brand  | countCars |
+----+--------+-----------+
|  1 | Toyota |         5 |
|  2 | Ford   |         2 |
|  3 | Nissan |         1 |
|  4 | Smart  |         1 |
|  5 | Toyota |         5 |
|  6 | BMW    |         2 |
|  7 | Ford   |         2 |
|  8 | Toyota |         5 |
|  9 | Toyota |         5 |
| 10 | BMW    |         2 |
| 11 | Toyota |         5 |
+----+--------+-----------+
11 rows in set (0.00 sec)

Bon, nous avons donc les mêmes résultats (ordonnés légèrement différemment - il semble que la base de données voulait renvoyer les résultats ordonnés par la première colonne que nous avons choisie cette fois-ci) - mais les mêmes bons chiffres.

Alors, quelle est la différence entre les deux - et quand devons-nous utiliser chaque type de sous-requête ? Tout d'abord, assurons-nous de bien comprendre le fonctionnement de cette deuxième requête. Nous avons sélectionné deux tables dans la base de données from de notre requête, puis nous avons écrit une requête et indiqué à la base de données qu'il s'agissait en fait d'une table - ce qui convient parfaitement à la base de données. Voilà peut L'utilisation de cette méthode présente certains avantages (ainsi que certaines limites). Le plus important est que cette sous-requête une fois . Si notre base de données contenait un grand volume de données, il pourrait bien y avoir une amélioration massive par rapport à la première méthode. Cependant, comme nous l'utilisons comme une table, nous devons introduire des lignes de données supplémentaires pour qu'elles puissent être jointes à nos lignes de données. Nous devons également nous assurer qu'il y a assez de lignes de données si nous utilisons une simple jointure comme dans la requête ci-dessus. Si vous vous rappelez, la jointure ne récupère que les lignes qui ont des données correspondantes dans le champ ambos côtés de la jointure. Si nous ne faisons pas attention, des données valides pourraient ne pas être renvoyées de la table des voitures s'il n'y avait pas de ligne correspondante dans cette sous-requête.

Maintenant, en regardant de nouveau la première sous-requête, il y a aussi quelques limitations. Parce que nous ramenons les données dans une seule ligne, nous pouvons UNIQUEMENT reculer d'une ligne de données. Les sous-requêtes utilisées dans la select d'une requête n'utilisent très souvent qu'une fonction d'agrégation telle que sum , count , max ou une autre fonction agrégée similaire. Ils ne ont mais c'est souvent ainsi qu'ils sont écrits.

Avant de poursuivre, voyons rapidement où nous pouvons utiliser une sous-requête. Nous pouvons l'utiliser dans le where Cet exemple est un peu artificiel, car dans notre base de données, il existe de meilleures façons d'obtenir les données suivantes, mais comme il ne s'agit que d'un exemple, voyons ce qu'il en est :

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

Cela nous renvoie une liste d'ID de marques et de noms de marques (la deuxième colonne n'est ajoutée que pour nous montrer les marques) qui contiennent la lettre o dans le nom.

Maintenant, nous pourrions utiliser les résultats de cette requête dans une clause où ceci :

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in
        (
        select
            ID
        from
            brands
        where
            brand like '%o%'
        )

+----+--------+
| ID | brand  |
+----+--------+
|  2 | Ford   |
|  7 | Ford   |
|  1 | Toyota |
|  5 | Toyota |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Comme vous pouvez le constater, bien que la sous-requête renvoie les trois identifiants de marque, notre table des voitures ne contient que deux entrées.

Dans ce cas, pour plus de détails, la sous-requête fonctionne comme si nous avions écrit le code suivant :

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in (1,2,6)

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Toyota |
|  2 | Ford   |
|  5 | Toyota |
|  7 | Ford   |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Encore une fois, vous pouvez voir comment une sous-requête par rapport aux entrées manuelles a changé l'ordre des lignes lors du retour de la base de données.

Pendant que nous parlons des sous-requêtes, voyons ce que nous pouvons faire d'autre avec une sous-requête :

  • Vous pouvez placer une sous-requête dans une autre sous-requête, et ainsi de suite. Il y a une limite qui dépend de votre base de données, mais à moins que les fonctions récursives d'un programmeur fou et maniaque, la plupart des gens n'atteindront jamais cette limite.
  • Vous pouvez placer un certain nombre de sous-requêtes dans une seule requête, quelques-unes dans la section select certains dans la from et quelques autres dans la where N'oubliez pas que chaque clause que vous insérez rend votre requête plus complexe et risque de prendre plus de temps à exécuter.

Si vous devez écrire un code efficace, il peut être utile d'écrire la requête de plusieurs façons et de voir (soit en la chronométrant, soit en utilisant un plan d'explication) quelle est la requête optimale pour obtenir vos résultats. La première façon qui fonctionne n'est pas toujours la meilleure.

0 votes

Très important pour les nouveaux développeurs : les sous-requêtes sont probablement exécutées une fois pour chaque résultat. sauf si vous pouvez utiliser la sous-requête comme une jointure (voir ci-dessus).

60voto

Fluffeh Points 21893

Partie 3 - Trucs et astuces et code efficace

L'efficacité de MySQL in()

J'ai pensé ajouter quelques éléments supplémentaires, pour des trucs et astuces qui sont apparus.

Une question qui revient souvent est la suivante . Comment obtenir des lignes non concordantes dans deux tables ? et je vois la réponse la plus communément acceptée comme quelque chose comme ce qui suit (sur la base de notre tableau de voitures et de marques - qui a Holden répertoriée comme une marque, mais n'apparaît pas dans le tableau des voitures) :

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

Et oui ça va marcher.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

Cependant, il est no efficace dans une base de données. Voici une lien vers une question Stack Overflow à ce sujet, et voici une excellent article en profondeur si vous voulez entrer dans le vif du sujet.

En bref, si l'optimiseur ne traite pas efficacement ce problème, il est préférable d'utiliser une requête comme la suivante pour obtenir les lignes non correspondantes :

select
    a.brand
from
    brands a
        left join cars b
            on a.id=b.brand
where
    b.brand is null

+--------+
| brand  |
+--------+
| Holden |
+--------+
1 row in set (0.00 sec)

Mettre à jour une table avec la même table dans une sous-requête

Ahhh, un autre vieux mais bon - le vieux Vous ne pouvez pas spécifier la table cible 'brands' pour la mise à jour dans la clause FROM. .

MySQL ne vous permettra pas d'exécuter un update... avec une sous-sélection sur la même table. Maintenant, vous vous dites peut-être : pourquoi ne pas simplement l'insérer dans la clause where ? Mais que se passe-t-il si vous voulez mettre à jour uniquement la ligne avec la clause max() date au milieu d'un tas d'autres rangs ? Vous ne pouvez pas exactement faire ça dans une clause where.

update 
    brands 
set 
    brand='Holden' 
where 
    id=
        (select 
            id 
        from 
            brands 
        where 
            id=6);
ERROR 1093 (HY000): You can't specify target table 'brands' 
for update in FROM clause

Donc, on ne peut pas faire ça, hein ? Eh bien, pas exactement. Il existe une solution de contournement sournoise qu'un nombre étonnamment élevé d'utilisateurs ne connaissent pas, bien qu'elle comporte quelques manipulations auxquelles vous devrez prêter attention.

Vous pouvez placer la sous-requête à l'intérieur d'une autre sous-requête, ce qui crée un écart suffisant entre les deux requêtes pour que cela fonctionne. Cependant, notez qu'il est plus sûr de placer la requête dans une transaction - cela empêchera toute autre modification des tables pendant l'exécution de la requête.

update 
    brands 
set 
    brand='Holden' 
where id=
    (select 
        id 
    from 
        (select 
            id 
        from 
            brands 
        where 
            id=6
        ) 
    as updateTable);

Query OK, 0 rows affected (0.02 sec)
Rows matched: 1  Changed: 0  Warnings: 0

4 votes

Il convient de noter que la construction WHERE NOT EXISTS() est pratiquement identique du point de vue de l'efficacité, mais qu'elle est, à mon avis, beaucoup plus facile à lire et à comprendre. Encore une fois, mes connaissances sont limitées à MSSQL et je ne peux pas dire si c'est la même chose sur d'autres plateformes.

0 votes

Je viens d'essayer ce type de comparaison l'autre jour, où le NOT IN() avait une liste de plusieurs centaines d'ID et il n'y avait aucune différence entre lui et la version jointe de la requête. Peut-être que cela fait une différence lorsque vous atteignez des milliers ou des milliards.

19voto

prashant1988 Points 123

Vous pouvez utiliser le concept de requêtes multiples dans le mot-clé FROM. Laissez-moi vous montrer un exemple :

SELECT DISTINCT e.id,e.name,d.name,lap.lappy LAPTOP_MAKE,c_loc.cnty COUNTY    
FROM  (
          SELECT c.id cnty,l.name
          FROM   county c, location l
          WHERE  c.id=l.county_id AND l.end_Date IS NOT NULL
      ) c_loc, emp e 
      INNER JOIN dept d ON e.deptno =d.id
      LEFT JOIN 
      ( 
         SELECT l.id lappy, c.name cmpy
         FROM   laptop l, company c
         WHERE l.make = c.name
      ) lap ON e.cmpy_id=lap.cmpy

Vous pouvez utiliser autant de tableaux que vous le souhaitez. Utilisez les jointures externes et les unions partout où c'est nécessaire, même à l'intérieur des sous-requêtes de tables.

C'est une méthode très simple pour impliquer autant de tables et de champs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X