Requête SQL renvoyant des données de plusieurs tables


434

Je voudrais savoir ce qui suit:

  • comment obtenir des données de plusieurs tables dans ma base de données?
  • Quels types de méthodes existe-t-il pour ce faire?
  • que sont les syndicats et les syndicats et en quoi sont-ils différents les uns des autres?
  • Quand devrais-je utiliser chacun 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 sur la base de données, quelles options ai-je pour obtenir des données de plusieurs tables dans une seule requête?

Remarque: j'écris ceci car je voudrais pouvoir me lier à un guide bien écrit sur les nombreuses questions que je rencontre constamment dans la file d'attente PHP, donc je peux me lier à cela pour plus de détails lorsque je poste une réponse.

Les réponses couvrent les points suivants:

  1. Partie 1 - Jointures et unions
  2. Partie 2 - Sous-requêtes
  3. Partie 3 - Astuces et code efficace
  4. Partie 4 - Sous-requêtes dans la clause From
  5. Partie 5 - Sac mixte de John's Tricks

Réponses:


469

Partie 1 - Jointures et unions

Cette réponse couvre:

  1. Partie 1
    • Joindre deux tables ou plus en utilisant une jointure interne (Voir l' entrée wikipedia pour plus d'informations)
    • Comment utiliser une requête d'union
    • Jointures externes gauche et droite (cette réponse stackOverflow est excellente pour décrire les types de jointures)
    • Intersection des requêtes (et comment les reproduire si votre base de données ne les prend pas en charge) - c'est une fonction de SQL-Server ( voir info ) et une des raisons pour lesquelles j'ai écrit tout cela en premier lieu.
  2. Partie 2
    • Sous-requêtes - ce qu'elles sont, où elles peuvent être utilisées et à quoi faire attention
    • Cartésien rejoint 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, j'utiliserai 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é à 89, cela peut sembler beaucoup moins intuitif - mais tout ce que je peux dire est de l'essayer) car c'est beaucoup plus facile pour comprendre quand les requêtes commencent à devenir plus complexes. Pourquoi l'utiliser? Y a-t-il un gain de performances? La réponse courte est non, mais elle est plus facile à lire une fois que vous vous y êtes habitué. Il est plus facile de lire les requêtes écrites par d'autres personnes en utilisant cette syntaxe.

Je vais également utiliser le concept d'un petit chantier qui a une base de données pour garder une trace des voitures disponibles. Le propriétaire vous a embauché comme son informaticien et s'attend à ce que vous puissiez lui laisser les données qu'il demande en un rien de temps.

J'ai créé un certain nombre de tables de recherche 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 un exemple de base de données qui a la structure suivante. J'essaierai de penser aux erreurs courantes commises lors du démarrage et d'expliquer ce qui ne va pas avec elles - ainsi que, bien sûr, de montrer 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 le chantier pourrait é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 d'utiliser des types de voitures différents 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 relie tout. 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 (j'espère) pour couvrir les exemples ci-dessous de différents types de jointures et donnera également suffisamment de données pour les rendre utiles.

Donc, pour entrer dans le vif du sujet, le patron veut connaître les identifiants de toutes les voitures de sport qu'il possède .

Il s'agit d'une simple jointure à deux tables. Nous avons un tableau qui identifie le modèle et le tableau avec le stock disponible. Comme vous pouvez le voir, les données de la modelcolonne du carstableau se rapportent à la modelscolonne du carstableau que nous avons. Maintenant, nous savons que la table des modèles a un ID 1pour for Sportspermet donc d'écrire la jointure.

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

Donc, cette requête semble bonne non? Nous avons identifié les deux tables et contenir les informations dont nous avons besoin et utilisons une jointure qui identifie correctement sur quelles colonnes se 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 a en effet les bonnes colonnes, mais certaines d'entre elles existent dans les deux tables, donc la base de données est confuse quant à la colonne réelle que nous voulons dire et où. Il existe deux solutions pour résoudre ce problème. Le premier est agréable et simple, nous pouvons utiliser tableName.columnNamepour 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é et est appelé alias de table. Les tables de cet exemple ont des noms simples agréables et courts, mais taper quelque chose comme KPI_DAILY_SALES_BY_DEPARTMENTserait probablement vieux rapidement, donc un moyen simple est de surnommer 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 comme cela a été demandé. Comme je préfère la méthode d'alias de table plutôt que d'utiliser les noms de table encore et encore, je m'en tiendrai à partir de ce point.

De toute évidence, nous devons ajouter une clause where à notre requête. Nous pouvons identifier les voitures de sport par ID=1ou model='Sports'. Comme l'ID est indexé et la clé primaire (et il se trouve qu'il y a moins de frappe), utilisons cela 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, étant un patron et n'étant jamais satisfait de ce qu'il a demandé, il regarde les informations, puis dit que je veux aussi les couleurs .

D'accord, nous avons donc une bonne partie de notre requête déjà écrite, mais nous devons utiliser un troisième tableau qui est les couleurs. Maintenant, notre tableau d'informations principal carsstocke l'ID de couleur de la voiture et cela renvoie à la colonne ID de couleurs. Ainsi, de manière similaire à l'original, nous pouvons rejoindre 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)

Merde, bien que la table ait été correctement jointe et que les colonnes liées aient été liées, nous avons oublié d'extraire les informations réelles 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)

Bon, c'est le patron de notre dos pendant un moment. Maintenant, pour en expliquer un peu plus en détail. Comme vous pouvez le voir, la fromclause de notre instruction relie notre table principale (j'utilise souvent une table qui contient des informations plutôt qu'une table de recherche ou de dimension. La requête fonctionnerait tout aussi bien avec les tables toutes inversées, mais aurait moins de sens lorsque nous revenons à 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 - disposez-la intuitivement, utilisez une indentation agréable pour que tout soit aussi clair que Si vous continuez à enseigner aux autres, essayez d'inculquer ces caractéristiques dans leurs requêtes - surtout si vous allez les dépanner.

Il est tout à fait possible de continuer à lier 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 où nous pourrions vouloir joindre plus d'une colonne dans la joindéclaration, voici un exemple. Si la modelstable avait des modèles spécifiques à la marque et avait donc également une colonne appelée brandqui renvoyait à la brandstable sur le IDterrain, cela pourrait être fait 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 relie non seulement les tables jointes à la carstable principale , mais spécifie également les jointures entre les tables déjà jointes. Si cela n'a pas été fait, le résultat est appelé une jointure cartésienne - ce qui est mauvais pour dba. Une jointure cartésienne est celle où les lignes sont renvoyées car les informations ne disent pas à la base de données comment limiter les résultats, de sorte que la requête renvoie toutes les lignes qui correspondent 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é le IDfrom carset le modelfrom models. Cependant, comme nous n'avons pas spécifié comment joindre les tables, la base de données a mis en correspondance chaque ligne de la première table avec chaque ligne de la deuxième table.

D'accord, donc le patron est de retour et il veut encore plus d'informations. Je veux la même liste, mais j'inclus également des 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 ce qui précède fonctionnera parfaitement bien, regardons les choses différemment, c'est une excellente excuse pour montrer comment une unionrequête fonctionnera.

Nous savons que ce qui suit rendra 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 ce qui suit retournerait 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 une union allclause entre eux, les résultats de la deuxième requête seront ajoutés aux résultats de la première requête.

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 voir, 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 les unionrequêtes peuvent être utiles pour des cas spécifiques. Ils sont un excellent moyen de renvoyer des résultats spécifiques à partir de tableaux à partir de tableaux qui ne sont pas facilement réunis - ou d'ailleurs des tableaux complètement indépendants. 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 ci-dessous.
  • 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 vous demandez peut- être quelle est la différence entre utiliser unionet union all. Une unionrequête supprimera les doublons, union allcontrairement à une requête . Cela signifie qu'il y a un petit impact sur les performances lors de l'utilisation de unionOver union allmais les résultats peuvent en valoir la peine - je ne spéculerai pas sur ce genre de chose cependant.

Sur cette note, il peut être utile de noter ici quelques notes supplémentaires.

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

Pour les exemples suivants, j'ajoute quelques lignes supplémentaires à nos tableaux.

J'ai ajouté Holdenau tableau des marques. J'ai également ajouté une ligne carsqui a la colorvaleur 12- qui n'a aucune référence dans le tableau des couleurs.

D'accord, le patron est de retour, aboyant des demandes - * Je veux un décompte de chaque marque que nous portons et le nombre de voitures qu'il contient! `` - Typiquement, nous arrivons à une section intéressante de notre discussion et le patron veut plus de travail .

Rightyo, donc la première chose que nous devons 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 cela à notre table de 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 la belle Holdenmarque que j'ai ajoutée.

En effet, une jointure recherche les lignes correspondantes dans les deux tables. Comme il n'y a pas de données de type dans les voitures, Holdenelles ne sont pas retournées. C'est là que nous pouvons utiliser une outerjointure. Cela renverra tous les résultats d'une table, qu'ils correspondent ou non dans 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 avons cela, nous pouvons ajouter une belle fonction d'agrégation pour obtenir un compte et mettre le patron de notre dos pendant 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, loin du boss.

Maintenant, pour expliquer cela plus en détail, les jointures externes peuvent être de type leftou right. La gauche ou la droite définit quelle table est entièrement incluse. A left outer joininclura toutes les lignes du tableau de gauche, tandis que (vous l'avez deviné) a right outer joinapporte tous les résultats du tableau de droite dans les résultats.

Certaines bases de données autorisent un full outer joinqui ramènera les résultats (qu'ils correspondent ou non) des deux tables, mais cela n'est pas pris en charge dans toutes les bases de données.

Maintenant, je suppose qu'à ce stade, vous vous demandez si vous pouvez ou non fusionner les types de jointure dans une requête - et la réponse est oui, vous le pouvez 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 ce ne sont pas les résultats attendus? 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 - de sorte que la jointure particulière ne ramènera que les résultats qui correspondent dans les deux tableaux.

Voici la requête qui fonctionnerait pour obtenir les résultats attendus:

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 voir, nous avons deux jointures externes dans la requête et les résultats arrivent comme prévu.

Maintenant, qu'en est-il des autres types de jointures que vous demandez? Et les intersections?

Eh bien, toutes les bases de données ne prennent pas en charge le intersectionmais presque toutes les bases de données vous permettront de créer une intersection via une jointure (ou une instruction where bien structurée au moins).

Une intersection est un type de jointure quelque peu similaire à un uniontel que décrit ci-dessus - mais la différence est qu'elle ne renvoie que des lignes de données identiques (et je veux dire identiques) entre les différentes requêtes individuelles jointes par l'union. Seules les lignes identiques à tous égards seront renvoyées.

Un exemple simple serait en tant que tel:

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

Alors qu'une unionrequête normale retournerait toutes les lignes de la table (la première requête renvoyant quoi que ce soit ID>2et la deuxième chose ayant ID<4) ce qui entraînerait un ensemble complet, une requête intersectée ne retournerait que la correspondance de ligne id=3car elle répond aux deux critères.

Maintenant, si votre base de données ne prend pas en charge une intersectrequête, ce qui précède peut être facilement accompagné de 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 effectuer une intersection entre deux tables différentes à l'aide d'une base de données qui ne prend pas en charge intrinsèquement une requête d'intersection, vous devrez créer une jointure sur chaque colonne des tables.


2
@Fluffeh Bonnes réponses. J'ai une suggestion: si vous voulez en faire un tutoriel SQL tueur, il ne vous manque que pour ajouter des diagrammes Venn; J'ai tout de suite compris les jointures gauche et droite grâce à eux. Demande personnelle: avez-vous un tutoriel sur les erreurs courantes / l'optimisation des performances?
StrayChild01

25
Oh mon. Ma roulette de défilement est cassée. Grande question et réponse. J'aimerais pouvoir voter 10 fois.
Amal Murali

3
Hehe, merci pour la rétroaction positive. Continuez à faire défiler cependant, ce n'était que la première réponse. SO a dit que ma réponse était trop longue pour l'adapter à une seule "réponse", j'ai donc dû en utiliser quelques-uns :)
Fluffeh

7
Honnêtement, je pense que cette réponse doit être quelque peu raccourcie.
einpoklum

Excellent article. Database Joins 101.
maqs

101

Ok, j'ai trouvé ce post très intéressant et je voudrais partager certaines de mes connaissances sur la création d'une requête. Merci pour ce Fluffeh . Ceux qui peuvent lire ceci et penser que je me trompe sont 101% libres de modifier et de critiquer ma réponse. ( Honnêtement, je me sens très reconnaissant d'avoir corrigé mes erreurs. )

Je posterai certaines des questions fréquemment posées dans la MySQLbalise.


Astuce n ° 1 ( lignes correspondant à plusieurs conditions )

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

Trouver tous les films qui appartiennent à au moins deux Comedy et Romancecatégories.

Solution

Cette question peut parfois être très délicate. Il peut sembler qu'une requête comme celle-ci sera 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émo SQLFiddle

ce qui est définitivement très faux car il ne produit aucun résultat . L'explication est qu'il n'y a qu'une seule valeur valide de CategoryNamesur chaque ligne . Par exemple, la première condition renvoie vrai , la deuxième condition est toujours fausse. Ainsi, en utilisant l' ANDopérateur, les deux conditions doivent être vraies; sinon, ce sera faux. Une autre requête est comme ça,

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

Démo SQLFiddle

et le résultat est toujours incorrect car il correspond à l'enregistrement qui a au moins une correspondance sur le categoryName. La vraie 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émo SQLFiddle (la réponse)


Astuce n ° 2 ( record maximum pour chaque entrée )

Étant donné le schéma,

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 sur chaque logiciel. Afficher les colonnes suivantes: SoftwareName, Descriptions, LatestVersion( de la colonne VersionNo ),DateReleased

Solution

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

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émo SQLFiddle

(la plupart des SGBDR génèrent une erreur de syntaxe à ce sujet car ils ne spécifient pas certaines des colonnes non agrégées sur la group byclause ) le résultat produit le correct LatestVersionsur chaque logiciel mais évidemment les DateReleasedsont incorrects. MySQLne supporte pas Window Functionset Common Table Expressionpourtant comme certains SGBDR le font déjà. La solution de contournement sur ce problème est de créer un subqueryqui obtient le maximum individuel versionNosur chaque logiciel et plus tard d'ê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émo SQLFiddle (la réponse)


C'était donc ça. J'en posterai un autre bientôt car je me souviens de toute autre FAQ sur le MySQLtag. Merci d'avoir lu ce petit article. J'espère que vous en avez au moins obtenu même un peu de connaissances.

MISE À JOUR 1


Astuce n ° 3 ( Trouver le dernier enregistrement entre deux identifiants )

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émo SQLFiddle


Impressionnant! Une mise en garde John, votre première solution ne fonctionne que parce qu'il existe une contrainte unique sur les deux champs. Vous auriez pu utiliser une solution plus générale pour résoudre un problème courant. À mon avis, la seule solution est de faire des sélections individuelles pour comedyet romance. Havingne convient pas alors ..
nawfal

@nawfal pas vraiment, si aucune contrainte unique n'a été ajoutée, vous devez alors ajouter distinctla clause having SQLFiddle Demo : D
John Woo

63

Partie 2 - Sous-requêtes

D'accord, maintenant le patron a éclaté à nouveau - je veux une liste de toutes nos voitures avec la marque et un total de combien de cette marque nous avons!

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

Pour notre demande, nous allons d'abord mettre en place une requête simple qui listera chaque voiture et la 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 d'origine, 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'ID de la voiture (colonne a.ID), nous devons l'ajouter dans le groupe par - alors maintenant, lorsque la fonction de comptage fonctionne, il n'y a qu'un seul ID correspondant par ID.

C'est là que nous pouvons cependant utiliser une sous-requête - en fait, nous pouvons faire deux types de sous-requête complètement différents qui retourneront les mêmes résultats que ceux dont nous avons besoin pour cela. La première consiste à simplement mettre la sous-requête dans la selectclause. Cela signifie que chaque fois que nous obtenons une ligne de données, la sous-requête s'exécute, obtient une colonne de données, puis l'insère 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 !, cela nous ferait du bien. Si vous l'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 voitures différentes, mais la sous-requête a été exécutée onze fois car nous avons onze lignes de données que nous renvoyons. Donc, dans ce cas, cela ne semble pas être le moyen le plus efficace d'écrire du code.

Pour une approche différente, exécutons une sous-requête et imaginons qu'il s'agit d'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)

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

Alors, quelle est la différence entre les deux - et quand devrions-nous utiliser chaque type de sous-requête? Tout d'abord, assurons-nous de comprendre le fonctionnement de cette deuxième requête. Nous avons sélectionné deux tables dans la fromclause de notre requête, puis écrit une requête et dit à la base de données qu'il s'agissait en fait d'une table à la place - ce dont la base de données est parfaitement satisfaite. Il peut y avoir certains avantages à utiliser cette méthode (ainsi que certaines limitations). Tout d'abord, cette sous-requête a été exécutée 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 table, nous devons apporter des lignes de données supplémentaires - afin qu'elles puissent réellement être jointes à nos lignes de données. Nous devons également nous assurer qu'il y a suffisammentlignes de données si nous voulons utiliser une simple jointure comme dans la requête ci-dessus. Si vous vous souvenez, la jointure ne récupérera que les lignes contenant des données correspondantes des deux côtés de la jointure. Si nous ne faisons pas attention, cela pourrait entraîner le non-retour de données valides de notre table cars s'il n'y avait pas de ligne correspondante dans cette sous-requête.

Maintenant, en repensant à la première sous-requête, il y a aussi quelques limitations. parce que nous retirons données dans une seule ligne, nous pouvons seulement retirer une rangée de données. Les sous - requêtes utilisées dans la selectclause d'une requête très souvent utiliser uniquement une fonction d' agrégation tels que sum, count, maxou une autre fonction d' agrégation similaire. Ils n'ont pas à le faire , mais c'est souvent ainsi qu'ils sont écrits.

Donc, avant de passer à autre chose, voyons rapidement où nous pouvons utiliser une sous-requête. Nous pouvons l'utiliser dans la whereclause - maintenant, cet exemple est un peu artificiel comme dans notre base de données, il existe de meilleures façons d'obtenir les données suivantes, mais vu qu'il ne s'agit que d'un exemple, jetons un coup d'œil:

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 marque et de noms de marque (la deuxième colonne n'est ajoutée que pour nous montrer les marques) contenant la lettre odans le nom.

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

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 voir, même si la sous-requête renvoyait les trois identifiants de marque, notre tableau des voitures ne comportait des entrées que pour deux d'entre eux.

Dans ce cas, pour plus de détails, la sous-requête fonctionne comme si nous écrivions 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 discutons 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 à court de fonctions récursives de certains programmeurs fous et maniaques, 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 selectclause, certaines dans la fromclause et quelques autres dans la whereclause - rappelez-vous simplement que chacune que vous mettez rend votre requête plus complexe et risque exécuter.

Si vous avez besoin d'écrire du code efficace, il peut être avantageux 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 de fonctionner n'est pas toujours la meilleure.


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

59

Partie 3 - Astuces et code efficace

MySQL en () efficacité

Je pensais que j'ajouterais quelques bits supplémentaires, pour les trucs et astuces qui ont surgi.

Une question que je vois apparaître assez souvent est la suivante: Comment puis-je obtenir des lignes non correspondantes de deux tableaux et je vois la réponse la plus communément acceptée comme quelque chose comme la suivante (basée sur notre tableau voitures et marques - qui contient Holden comme 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 n'est pas efficace dans certaines bases de données. Voici un lien vers une question Stack Overflow qui le pose, et voici un excellent article en profondeur si vous voulez entrer dans le vif du sujet.

La réponse courte est que si l'optimiseur ne le gère pas efficacement, il peut être préférable d'utiliser une requête comme la suivante pour obtenir des 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 la table avec la même table dans la sous-requête

Ahhh, un autre oldie mais goodie - l'ancien Vous ne pouvez pas spécifier de «marques» de table cible pour la mise à jour dans la clause FROM .

MySQL ne vous permettra pas d'exécuter une update...requête avec une sous-sélection sur la même table. Maintenant, vous pensez peut-être, pourquoi ne pas simplement le gifler dans la clause where? Mais que se passe-t-il si vous souhaitez mettre à jour uniquement la ligne avec la max()date parmi un tas d'autres lignes? Vous ne pouvez pas faire exactement cela 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? Enfin, pas exactement. Il existe une solution de contournement sournoise qu'un nombre étonnamment élevé d'utilisateurs ne connaissent pas - bien qu'elle comprenne un certain piratage auquel vous devrez faire attention.

Vous pouvez coller la sous-requête dans une autre sous-requête, ce qui crée suffisamment d'espace entre les deux requêtes pour que cela fonctionne. Cependant, notez qu'il peut être plus sûr de coller la requête dans une transaction - cela empêchera toute autre modification apportée aux 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

3
Je veux juste noter que la construction WHERE NOT EXISTS () est à peu près identique d'un «point de vue efficacité» mais à mon avis beaucoup plus facile à lire / à comprendre. Là encore, mes connaissances sont limitées à MSSQL et je ne peux pas faire de vœu si la même chose est vraie sur d'autres plates-formes.
dérobie

J'ai juste essayé ce type de comparaison l'autre jour, où NOT IN () avait une liste d'environ plusieurs centaines d'ID et il n'y avait aucune différence entre celui-ci et la version join de la requête. Peut-être que cela fait une différence lorsque vous atteignez des milliers ou des milliards.
Buttle Butkus

18

Vous pouvez utiliser le concept de plusieurs requêtes dans le mot clé FROM. Permettez-moi de 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 l'union partout où cela est nécessaire, même à l'intérieur des sous-requêtes de table.

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


8

J'espère que cela lui permet de trouver les tables pendant que vous lisez la chose:

jsfiddle

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    |                |
+-------+-------------+------+-----+---------+----------------+
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.