J'aimerais savoir ce qui suit:
Je prévois de l'utiliser dans mon application (par exemple, PHP), mais je ne souhaite pas exécuter plusieurs requêtes sur la base de données. Quelles sont les options pour obtenir les données de plusieurs tables en une seule requête?
Note: J'écris ceci car je voudrais pouvoir créer un lien vers un guide bien écrit sur les nombreuses questions que je rencontre constamment dans la file d'attente PHP. Je poste une réponse.
Les réponses couvrent les points suivants:
Cette réponse couvre:
Il existe plusieurs méthodes pour extraire des données de plusieurs tables d'une base de données. Dans cette réponse, j'utiliserai la syntaxe de jointure ANSI-92. Cela peut être différent de plusieurs autres tutoriels utilisant l'ancienne syntaxe ANSI-89 (et si vous en avez l'habitude, cela peut sembler beaucoup moins intuitif - mais tout ce que je peux dire, c'est d'essayer) tel quel beaucoup plus facile à comprendre lorsque les requêtes commencent à devenir plus complexes. Pourquoi l'utiliser? Y a-t-il un gain de performance? 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 dispose d'une base de données pour garder une trace des voitures disponibles. Le propriétaire vous a engagé comme informaticien et s'attend à ce que vous soyez en mesure de lui transmettre les données qu'il demande en un rien de temps.
J'ai créé un certain nombre de tables de consultation qui seront utilisées par la table finale. Cela nous donnera un modèle de travail raisonnable. Pour commencer, je vais exécuter mes requêtes sur un exemple de base de données ayant la structure suivante. Je vais essayer de réfléchir aux erreurs courantes commises lors du démarrage et d’expliquer ce qui ne va pas avec elles - et bien sûr de montrer comment les corriger.
La première table est simplement une liste de couleurs afin que nous sachions quelles couleurs nous avons dans le garage.
_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 constructeur 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 modèle de table couvrira différents types de voitures, il sera plus simple 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 attacher toutes ces autres tables, la table qui relie tout. Le champ ID est en réalité 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, ainsi que suffisamment de données pour les rendre rentables.
Le patron veut donc connaître les identifiants de toutes ses voitures de sport .
Ceci est une simple jointure à deux tables. Nous avons une table qui identifie le modèle et la table avec le stock disponible qu'il contient. Comme vous pouvez le constater, les données de la colonne model
de la table cars
se rapportent à la colonne models
de la table cars
que nous avons. Nous savons maintenant que la table de modèles a un ID de _1
_ pour Sports
, nous allons donc écrire la jointure.
_select
ID,
model
from
cars
join models
on model=ID
_
Donc, cette requête a l'air bien non? Nous avons identifié les deux tables et contient les informations nécessaires. 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 a effectivement les bonnes colonnes, mais certaines d'entre elles existent dans les deux tables, de sorte que la base de données devient confuse quant à la colonne réelle que nous voulons dire et où. Il existe deux solutions pour résoudre ce problème. La première est agréable et 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é et s'appelle aliasing de table. Les tables de cet exemple portent des noms simples et courts, mais saisir quelque chose comme _KPI_DAILY_SALES_BY_DEPARTMENT
_ vieillirait probablement rapidement. Un moyen simple consiste donc à surnommer la table de la manière suivante:
_select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
_
Revenons maintenant à la demande. Comme vous pouvez le constater, nous disposons des informations dont nous avons besoin, mais nous avons également des informations qui ne nous ont pas été demandées. Nous devons donc inclure une clause where dans la déclaration afin de ne récupérer que les voitures de sport demandées. Comme je préfère la méthode des alias de table plutôt que d’utiliser les noms de table encore et encore, je vais m'en tenir à ça à partir de maintenant.
Clairement, nous devons ajouter une clause where à notre requête. Nous pouvons identifier les voitures de sport par _ID=1
_ ou _model='Sports'
_. Comme l'ID est indexé et la clé primaire (et il se trouve que vous tapez moins), utilisons-le 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 ne jamais être satisfait de ce qu'il a demandé, il regarde les informations, puis dit Je veux aussi les couleurs .
Bon, nous avons déjà écrit une bonne partie de notre requête, mais nous devons utiliser un troisième tableau, celui des couleurs. Maintenant, notre table principale d'informations cars
enregistre l'ID de couleur de la voiture et 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)
_
Bon sang, bien que la table ait été correctement jointe et que les colonnes associées soient liées, nous avons oublié d’extraire les informations réelles de la nouvelle table que nous venons de créer un lien.
_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, c'est le patron de notre dos pour un moment. Maintenant, pour expliquer certaines de ces choses un peu plus en détail. Comme vous pouvez le constater, la clause from
de notre instruction lie notre table principale (j’utilise souvent une table contenant des informations plutôt qu’une table de recherche ou de dimension. La requête fonctionnerait tout aussi bien avec les tables basculées, mais elles auraient 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 soit agréable et facile à comprendre - présentez-la de manière intuitive, utilisez la mise en retrait de Nice pour que tout soit aussi clair Si vous continuez à enseigner aux autres, essayez d’incorporer ces caractéristiques à leurs requêtes - en particulier si vous comptez les résoudre.
Il est tout à fait possible de continuer à 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'ai oublié d'inclure une table dans laquelle nous pourrions vouloir joindre plus d'une colonne dans l'instruction join
, voici un exemple. Si la table models
avait des modèles propres à la marque et donc également une colonne appelée brand
qui renvoyait à la table brands
du champ ID
, cela pourrait se faire 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
_
Comme vous pouvez le constater, la requête ci-dessus lie non seulement les tables jointes à la table principale cars
, mais spécifie également des jointures entre les tables déjà jointes. Si cela n'a pas été fait, le résultat s'appelle une jointure cartésienne - ce qui est dba parler pour le mal. Une jointure cartésienne est une jointure dans laquelle les lignes sont renvoyées car les informations n'indiquant pas à la base de données comment limiter les résultats, la requête renvoie all toutes les lignes répondre aux critères.
Donc, pour donner un exemple de jointure cartésienne, lançons 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é ID
from cars
et model
from models
. Cependant, comme nous n’avons pas précisé comment joindre les tables, la base de données a correspondu chaque ligne de la première table avec toutes les lignes de la deuxième table.
Bon, le patron est de retour et il veut plus d'informations. Je veux la même liste, mais j'y inclut également les 4WD .
Cependant, cela nous donne une bonne excuse pour examiner deux manières 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 fonctionne parfaitement, voyons cela différemment, c’est une excellente excuse pour montrer comment une requête union
fonctionnera.
Nous savons que ce qui suit retournera 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 retourneraient tous les 4WD:
_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
_
Donc, en ajoutant une clause _union all
_ entre eux, les résultats de la seconde 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 constater, les résultats de la première requête sont renvoyés en premier, suivis des résultats de la seconde requête.
Dans cet exemple, il aurait bien sûr été beaucoup plus simple d’utiliser la première requête, mais les requêtes union
peuvent s'avérer très utiles dans certains cas. Ils constituent un excellent moyen de renvoyer des résultats spécifiques à partir de tables provenant de tables qui ne sont pas facilement jointes - ou d'ailleurs complètement des tables non liées. Il y a quelques règles à suivre cependant.
Maintenant, vous pourriez vous demander quelle est la différence entre utiliser union
et _union all
_. Une requête union
supprimera les doublons, alors que _union all
_ ne le fera pas. Cela signifie qu'il y a une petite baisse de performance lors de l'utilisation de union
sur _union all
_ mais les résultats peuvent en valoir la peine - je ne spéculerai pas sur ce genre de chose dans ce cas-ci.
Sur cette note, il convient de noter quelques notes supplémentaires ici.
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 s'appelle ID
et non _a.ID
_ - même si le même alias a été utilisé dans les deux requêtes. .order by
_, et il doit s'agir de la dernière instruction.Pour les exemples suivants, j'ajoute quelques lignes supplémentaires à nos tables.
J'ai ajouté Holden
à la table des marques. J'ai également ajouté une ligne dans cars
qui a la valeur color
de _12
_ - qui n'a pas de référence dans la table des couleurs.
D'accord, le patron est de retour et aboie des requêtes - * Je veux un décompte de chaque marque que nous vendons et du nombre de voitures qu'il contient! `- Typiques, nous arrivons à une partie intéressante de notre discussion et le patron veut plus de travail .
Rightyo, la première chose à faire est donc 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 ceci à notre table 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 la belle marque Holden
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 dans les voitures de type Holden
, elles ne sont pas renvoyées. C'est ici que nous pouvons utiliser une jointure outer
. Ceci renverra tous les résultats d'une table, qu'ils soient appariés 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égat pour obtenir un décompte et libérer le patron de notre dos un instant.
_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, éloignez le patron.
Maintenant, pour expliquer cela plus en détail, les jointures externes peuvent être du type left
ou right
. La gauche ou la droite définit quelle table est totalement incluse. Un _left outer join
_ inclura toutes les lignes du tableau de gauche, alors que (vous l’aurez deviné) un _right outer join
_ amène tous les résultats du tableau de droite dans les résultats.
Certaines bases de données autoriseront un _full outer join
_ qui rapportera les résultats (correspondants ou non) de des deux tables , mais cela n'est pas pris en charge dans toutes les bases de données.
Maintenant, je suppose que, à ce stade, vous vous demandez si vous pouvez ou non fusionner des 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 ne s'agit-il pas des résultats escomptés? En effet, bien que nous ayons sélectionné la jointure externe des voitures aux marques, cela n’a pas été spécifié dans la jointure aux couleurs - de sorte que cette jointure particulière ne ramène que les résultats correspondants dans les deux tableaux.
Voici la requête qui fonctionnerait pour 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, la requête contient deux jointures externes et les résultats sont obtenus comme prévu.
Maintenant, qu'en est-il des autres types de jointures que vous demandez? Qu'en est-il des intersections?
Eh bien, toutes les bases de données ne supportent pas intersection
mais quasiment toutes les bases de données vous permettront de créer une intersection via une jointure (ou au moins une instruction where bien structurée).
Une intersection est un type de jointure similaire à un union
comme décrit ci-dessus - mais la différence est qu’elle uniquement renvoie des lignes de données identiques (et Je veux dire identique) entre les différentes requêtes individuelles auxquelles le syndicat a adhéré. Seules les lignes identiques à tous égards seront retournées.
Un exemple simple serait comme tel:
_select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
_
Alors qu'une requête normale union
renverrait toutes les lignes de la table (la première requête renvoyant tout ce qui dépassait _ID>2
_ et la seconde tout ayant _ID<4
_) qui aboutirait à un ensemble complet, une requête intersecte ne renverrait que la ligne correspondant à _id=3
_ car elle répond aux deux critères.
Désormais, si votre base de données ne prend pas en charge une requête intersect
, la requête ci-dessus peut être facilement complétée avec 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 sur deux tables différentes en utilisant une base de données qui ne prend pas en charge de manière inhérente une requête d'intersection, vous devez créer une jointure sur chaque colonne des tables.
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 cela Fluffeh. Les autres personnes qui liront peut-être ceci et qui auront peut-être l'impression que je me trompe sont libres à 101% de modifier et de critiquer ma réponse. (Honnêtement, je suis très reconnaissant d'avoir corrigé mes erreurs.)
Je posterai certaines des questions fréquemment posées dans la balise MySQL
.
Étant donné 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 appartenant à au moins les deuxComedy
et Romance
caté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'
ce qui est vraiment très faux car cela produit pas de résultat. L'explication de ceci est qu'il n'y a qu'une seule valeur valide de CategoryName
sur chaque ligne. Par exemple, la première condition renvoie true, la seconde condition est toujours fausse. Ainsi, en utilisant l'opérateur AND
, 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')
et le résultat est toujours incorrect car il correspond à l'enregistrement qui a au moins une correspondance sur le categoryName
. La solution réelle serait en comptant 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
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 sur 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égat. 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
(la plupart des SGBDR génèrent une erreur de syntaxe car ils ne spécifient pas certaines des colonnes non agrégées de la clause group by
) le résultat produit le LatestVersion
correct sur chaque logiciel, mais évidemment, les DateReleased
sont incorrects. MySQL
ne prend pas _ en charge Window Functions
et Common Table Expression
comme certains RDBMS le font déjà. La solution de contournement à ce problème consiste à créer un subquery
qui obtient le maximum individuel versionNo
sur chaque logiciel, puis 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
Donc c'était ça. J'en posterai un autre bientôt si je me souviens de n'importe quel autre FAQ sur la balise MySQL
. Merci d'avoir lu ce petit article. J'espère que vous en aurez au moins même un peu de connaissance.
UPDATE 1
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
)
Ok, maintenant le patron a de nouveau éclaté - Je veux une liste de toutes nos voitures avec la marque et le nombre total de marques de cette marque que nous avons!
C’est une excellente occasion d’utiliser la prochaine astuce de notre sac de goodies SQL: la sous-requête. Si vous ne connaissez pas le terme, une sous-requête est une requête exécutée dans une autre requête. Il y a plusieurs façons de les utiliser.
Pour notre demande, commençons par rassembler 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 |
+--------+-----------+
Nous devrions donc pouvoir simplement ajouter la fonction de comptage à notre requête initiale, 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 cela. La raison en est que lorsque nous ajoutons l'ID de la voiture (colonne a.ID), nous devons l'ajouter au groupe - de sorte que maintenant, lorsque la fonction de comptage fonctionne, il n'y a qu'un seul ID correspondant par ID.
C’est là que nous pouvons toutefois 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 pour cela. La première consiste simplement à placer la sous-requête dans la clause select
. Cela signifie que chaque fois que nous obtenons une ligne de données, la sous-requête s’exécutera, obtiendra une colonne de données et l’affichera 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 l'affaire. Si vous avez remarqué, cette sous-requête devra être exécutée pour chaque ligne de données renvoyée. Même dans ce petit exemple, nous n’avons que cinq marques différentes de voiture, mais la sous-requête a été exécutée onze fois car nous avons onze rangées de données que nous retournons. 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 supposons que c'est 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 (classés légèrement différents - il semble que la base de données veuille renvoyer les résultats classés par la première colonne que nous avons sélectionnée cette fois) - mais avec les mêmes bons nombres.
Alors, quelle est la différence entre les deux - et quand devrions-nous utiliser chaque type de sous-requête? Premièrement, assurons-nous de comprendre le fonctionnement de cette seconde requête. Nous avons sélectionné deux tables dans la clause 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 dont la base de données est parfaitement satisfaite. Il peut présenter certains avantages à l’utilisation de 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 y avoir une amélioration considérable par rapport à la première méthode. Cependant, comme nous utilisons cela comme une table, nous devons importer des lignes de données supplémentaires afin qu'elles puissent être réellement reliées à nos lignes de données. Nous devons également nous assurer qu'il y a assez de lignes de données si nous allons utiliser une simple jointure comme dans la requête ci-dessus. Si vous vous en souvenez bien, la jointure ne récupérera que les lignes dont les données correspondent sur les deux côtés de la jointure. Si nous ne faisons pas attention, des données valides pourraient ne pas être renvoyées de notre table cars s'il n'y avait pas de ligne correspondante dans cette sous-requête.
Maintenant, en regardant la première sous-requête, il y a aussi quelques limitations. parce que nous récupérons les données dans une seule ligne, nous pouvons SEULEMENT récupérer une ligne de données. Les sous-requêtes utilisées dans la clause select
d'une requête utilisent très souvent uniquement une fonction d'agrégat telle que sum
, count
, max
ou une autre fonction d'agrégat similaire. Ils n'ont pas , mais c'est souvent ainsi qu'ils sont écrits.
Donc, avant de poursuivre, jetons un coup d'œil sur les endroits où nous pouvons utiliser une sous-requête. Nous pouvons l'utiliser dans la clause where
- maintenant, cet exemple est un peu artificiel car dans notre base de données, il existe de meilleurs moyens d'obtenir les données suivantes, mais vu que ce n'est qu'un exemple, jetons un coup d'oeil :
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'identifiants de marques et de noms de marques (la deuxième colonne est ajoutée uniquement pour indiquer les marques) qui contiennent la lettre o
dans le nom.
Maintenant, nous pourrions utiliser les résultats de cette requête dans une clause where comme 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, même si la sous-requête renvoyait les trois identifiants de marque, notre table cars ne contenait des entrées que pour deux d’entre elles.
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 à des entrées manuelles a modifié 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:
select
, d'autres dans la clause from
et quelques-unes dans la clause where
. vous introduisez rend votre requête plus complexe et susceptible de prendre plus de temps à être exécutée.Si vous avez besoin d'écrire du code efficace, il peut être avantageux d'écrire la requête de plusieurs manières et de voir (en la chronométrant ou en utilisant un plan explicatif) quelle est la requête optimale pour obtenir vos résultats. Le premier moyen qui fonctionne ne constitue peut-être pas toujours le meilleur moyen de le faire.
Je pensais ajouter quelques éléments supplémentaires, pour obtenir des conseils et astuces.
Une question que je vois assez souvent est . Comment puis-je obtenir des lignes non correspondantes de deux tables et je vois la réponse la plus communément acceptée comme quelque chose comme ci-dessous (basé sur notre tableau de voitures et marques - qui a Holden répertorié comme marque, mais n'apparaît pas dans le tableau de voitures):
select
a.ID,
a.brand
from
brands a
where
a.ID not in(select brand from cars)
Et oui cela fonctionnera.
+----+--------+
| ID | brand |
+----+--------+
| 6 | Holden |
+----+--------+
1 row in set (0.00 sec)
Cependant, il est pas efficace dans certaines bases de données. Voici un lien vers une question de débordement de pile demandant à ce sujet, et voici un excellent article en profondeur si vous voulez entrer dans le vif du sujet.
En bref, si l'optimiseur ne le gère pas efficacement, il peut être préférable d'utiliser une requête semblable à celle-ci pour obtenir des lignes non appariées:
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)
Ahhh, un autre vieux mais bonjour - l'ancien Vous ne pouvez pas spécifier les marques de la table cible pour la mise à jour dans la clause FROM .
MySQL ne vous autorisera pas à exécuter une requête update...
avec une sous-sélection sur la même table. Maintenant, vous pensez peut-être, pourquoi ne pas simplement l'insérer dans la clause where, n'est-ce pas? Mais que se passe-t-il si vous souhaitez mettre à jour uniquement la ligne avec la date max()
parmi plusieurs 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? Eh bien, pas exactement. Il existe une solution de contournement sournoise dont un nombre étonnamment grand d'utilisateurs ne sont pas au courant - bien qu'elle inclue certains piratages auxquels vous devrez prêter attention.
Vous pouvez coller la sous-requête dans une autre sous-requête, ce qui laisse un espace suffisant 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
Vous pouvez utiliser le concept de plusieurs requêtes 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 tables que vous le souhaitez. Utilisez les jointures externes et l'union chaque fois que c'est nécessaire, même à l'intérieur des sous-requêtes de table.
C'est une méthode très facile d'impliquer autant que des tables et des champs.
J'espère que cela vous permettra de trouver les tables pendant que vous lisez la chose:
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 | |
+-------+-------------+------+-----+---------+----------------+