Частина 1 - Приєднання та об'єднання
Ця відповідь охоплює:
- Частина 1
- Частина 2
- Підзапити - що це таке, де їх можна використовувати і на що слідкувати
- Картезіан приєднується до AKA - О, нещастя!
Існує ряд способів отримання даних з декількох таблиць у базі даних. У цій відповіді я буду використовувати синтаксис приєднання ANSI-92. Це може відрізнятися від ряду інших навчальних посібників, які використовують старіший синтаксис ANSI-89 (і якщо ви звикли до 89, це може здатися набагато менш інтуїтивним - але все, що я можу сказати, це спробувати), оскільки це набагато простіше зрозуміти, коли запити починають ускладнюватися. Навіщо його використовувати? Чи є підвищення продуктивності? Чи не Коротка відповідь немає, але це легше читати , як тільки ви звикнете до нього. Простіше читати запити, написані іншими людьми, використовуючи цей синтаксис.
Я також збираюся використовувати концепцію невеликого кар'єру, який має базу даних, щоб відслідковувати, які машини у нього є. Власник найняв вас на посаду свого ІТ-комп’ютера та очікує, що ви зможете скинути йому ті дані, які він просить, при падінні шапки.
Я зробив ряд таблиць пошуку, які будуть використовуватися підсумковим столом. Це дасть нам розумну модель для роботи. Для початку я буду виконувати свої запити на прикладі бази даних, що має таку структуру. Я спробую подумати про поширені помилки, які робляться при запуску, і поясню, що в них виходить не так - а також, звичайно, покажу, як їх виправити.
Перша таблиця - це просто список кольорів, щоб ми знали, які кольори у нас на дворі автомобіля.
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)
Таблиця брендів визначає різні марки автомобілів, на яких може бути продано кар'єр.
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)
Таблиця моделей охоплюватиме різні типи автомобілів, для цього буде простіше використовувати різні типи автомобілів, а не фактичні моделі автомобілів.
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)
І нарешті, зв’язати всі ці інші таблиці, таблицю, яка з’єднує все разом. Поле ID - це насправді унікальний номер партії, який використовується для ідентифікації автомобілів.
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)
Це дасть нам достатньо даних (я сподіваюся), щоб прикрити наведені нижче приклади різних типів приєднань, а також дасть достатньо даних, щоб зробити їх корисними.
Тож, потрапляючи в суть цього, бос хоче знати Ідентифікатори всіх спортивних машин, які він має .
Це просте приєднання двох таблиць. У нас є таблиця, яка ідентифікує модель та таблицю з наявними в ній запасами. Як бачимо, дані у modelстовпці carsтаблиці стосуються modelsстовпця carsтаблиці, який ми маємо. Тепер ми знаємо , що таблиця моделей має ідентифікатор 1для Sportsтак дозволяє писати приєднатися.
select
ID,
model
from
cars
join models
on model=ID
Тож цей запит виглядає добре? Ми визначили дві таблиці і містять потрібну нам інформацію та використовуємо з'єднання, яке правильно визначає, до яких стовпців слід приєднатися.
ERROR 1052 (23000): Column 'ID' in field list is ambiguous
О, ні! Помилка нашого першого запиту! Так, і це слива. Розумієте, запит справді має правильні стовпці, але деякі з них існують в обох таблицях, тому база даних плутається з приводу того, який саме стовпець ми маємо на увазі та де. Для вирішення цього питання є два рішення. Перше добре і просто, ми можемо використовувати, tableName.columnNameщоб точно сказати базі даних, як це:
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)
Інший, мабуть, частіше використовується і називається псевдонімом таблиці. Таблиці в цьому прикладі мають гарні та короткі прості імена, але, набравши щось подібне KPI_DAILY_SALES_BY_DEPARTMENT, швидше за все, старіють, тому простим способом називати таблицю так:
select
a.ID,
b.model
from
cars a
join models b
on a.model=b.ID
Тепер повернемося до запиту. Як ви бачите, у нас є необхідна нам інформація, але ми також маємо інформацію, яку не просили, тому нам потрібно включити пункт де у заяві, щоб отримати лише Спортивні автомобілі, як просили. Оскільки я віддаю перевагу методу псевдоніму таблиці, а не використовувати назви таблиць знову і знову, я буду дотримуватися його з цього моменту.
Зрозуміло, нам потрібно додати в наш запит пункт де. Ми можемо ідентифікувати спортивні автомобілі за ID=1або model='Sports'. Оскільки ідентифікатор ідентифікований та первинний ключ (а він набирає меншої кількості тексту), дозволимо використовувати його у нашому запиті.
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)
Бінго! Начальник задоволений. Звичайно, будучи начальником і ніколи не задоволений тим, про що він просив, він переглядає інформацію, а потім каже, що я хочу і кольори .
Гаразд, тому у нас вже написана хороша частина запиту, але нам потрібно використовувати третю таблицю, яка є кольорами. Тепер наша основна інформаційна таблиця carsзберігає ідентифікатор кольору автомобіля, і він посилається на стовпчик кольорів. Отже, аналогічно оригіналу, ми можемо приєднатися до третьої таблиці:
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)
Блін, хоча таблиця була правильно з’єднана та пов'язані стовпчики, ми забули використати фактичну інформацію з нової таблиці, яку ми щойно пов’язали.
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)
Правильно, це бос на мить. Тепер пояснимо це дещо детальніше. Як бачимо, fromв нашому викладі застереження посилається на нашу основну таблицю (я часто використовую таблицю, яка містить інформацію, а не таблицю пошуку або розмірності. Запит буде працювати так само добре, як із таблицями, що перемикаються навколо, але має менший сенс, коли ми повертаємося до цього запиту, щоб прочитати його через кілька місяців, тому найчастіше найкраще спробувати написати запит, який буде приємним і зрозумілим - викладіть його інтуїтивно, використовуйте приємне відступ, щоб все було настільки чітко, як Якщо ви продовжуєте навчати інших, спробуйте прищепити цим характеристикам їх запити - особливо, якщо ви будете їх вирішувати.
Цілком можливо продовжувати зв’язувати все більше та більше таблиць таким чином.
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
Хоча я забув включити таблицю, де ми, можливо, захочемо приєднати більше одного стовпця до joinзаяви, ось приклад. Якщо в modelsтаблиці були моделі, характерні для бренда, і, отже, також був стовпчик, brandякий називався, який посилався назад на brandsтаблицю на IDполі, це можна зробити так:
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
Ви можете бачити, що вищезазначений запит не тільки пов'язує об'єднані таблиці з основною carsтаблицею, але і вказує приєднання між вже об'єднаними таблицями. Якщо цього не було зроблено, результат називається декартовим приєднанням - що dba говорить погано. Декартовий приєднання - це те, куди повертаються рядки, оскільки інформація не вказує базі даних, як обмежувати результати, тому запит повертає всі рядки, що відповідають критеріям.
Отже, щоб навести приклад декартового приєднання, давайте запустити наступний запит:
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)
Боже добрий, це некрасиво. Однак, що стосується бази даних, саме про це запитували. У запиті ми попросили отримати IDвід carsі modelвід models. Однак, оскільки ми не вказали, як з'єднати таблиці, база даних узгодила кожен рядок з першої таблиці з кожним рядком з другої таблиці.
Гаразд, значить, шеф повернувся, і він знову хоче більше інформації. Я хочу той самий список, але також включати до нього 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=1
or b.ID=3
Хоча вищезазначене буде працювати прекрасно, давайте подивимось на нього по-різному, це чудовий привід показати, як unionбуде працювати запит.
Ми знаємо, що всі спортивні автомобілі повернуть:
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
І наступне поверне всі 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
Таким чином, додавши 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=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)
Як бачимо, результати першого запиту повертаються першими, а потім результати другого запиту.
У цьому прикладі, звичайно, було б набагато простіше просто використовувати перший запит, але unionзапити можуть бути чудовими для конкретних випадків. Вони є прекрасним способом повернути конкретні результати з таблиць із таблиць, які не легко поєднуються разом - або, з цього приводу, абсолютно не пов'язані між собою таблиці. Однак слід дотримуватися кількох правил.
- Типи стовпців першого запиту повинні відповідати типам стовпців з усіх інших запитів нижче.
- Імена стовпців першого запиту будуть використані для ідентифікації всього набору результатів.
- Кількість стовпців у кожному запиті має бути однаковою.
Тепер вам може бути цікаво, в чому різниця між використанням unionта використанням union all. unionЗапит буде видаляти дублікати, а union allне буде. Це означає, що при використанні unionнад ними є невеликий показник ефективності, union allале результати, можливо, того варті - я не буду спекулювати на цьому подібних речах.
У цій примітці, можливо, варто зазначити деякі додаткові замітки тут.
- Якщо ми хотіли замовити результати, ми можемо використовувати
order byпсевдонім, але ви більше не можете використовувати псевдонім. У вищезазначеному запиті додавання символу order by a.IDпризведе до помилки - що стосується результатів, стовпець викликається, IDа не a.ID- хоча той самий псевдонім був використаний в обох запитах.
- У нас може бути лише одне
order byтвердження, і воно повинно бути як останнє твердження.
Для наступних прикладів я додаю кілька додаткових рядків до наших таблиць.
Я додав Holdenдо таблиці брендів. Я також додав рядок, carsякий має colorзначення 12- який не має посилання в таблиці кольорів.
Гаразд, бос знову повернувся, гавкаючи запити - * Я хочу рахувати кожну марку, яку ми перевозимо, і кількість машин у ній! '- Типово, ми просто переходимо до цікавого розділу нашого обговорення, і начальник хоче більше роботи .
Rightyo, тому перше, що нам потрібно зробити, - це отримати повний перелік можливих брендів.
select
a.brand
from
brands a
+--------+
| brand |
+--------+
| Ford |
| Toyota |
| Nissan |
| Smart |
| BMW |
| Holden |
+--------+
6 rows in set (0.00 sec)
Тепер, коли ми приєднуємо це до нашої таблиці автомобілів, ми отримуємо такий результат:
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)
Що, звичайно, є проблемою - ми не бачимо жодної згадки про прекрасний Holdenбренд, який я додав.
Це відбувається тому, що об'єднання шукає відповідні рядки в обох таблицях. Оскільки в автомобілях такого типу немає даних, Holdenвони не повертаються. Тут ми можемо використовувати outerз'єднання. Це поверне всі результати з однієї таблиці, незалежно від того, відповідають вони чи ні в іншій таблиці:
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)
Тепер, коли ми маємо це, ми можемо додати чудову функцію сукупності, щоб отримати кількість рахунків та відвести боса на мить.
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)
І з цим, подалі бос скрадеться.
Тепер, щоб пояснити це дещо детальніше, зовнішні з'єднання можуть бути типу leftабо rightтипу. Ліворуч або праворуч визначає, яка таблиця повністю включена. A left outer joinбуде включати всі рядки з таблиці зліва, а (ви здогадалися) a right outer joinвносить усі результати з таблиці праворуч у результати.
Деякі бази даних дозволять, full outer joinщо принесе результати ( обидва чи ні) з обох таблиць, але це не підтримується у всіх базах даних.
Зараз, напевно, я думаю, що в цей момент часу ви задаєтесь питанням, чи можете ви об'єднати типи об'єднання у запиті чи ні, і відповідь - так, ви абсолютно можете.
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)
Отже, чому це не результати, які очікували? Це тому, що, хоча ми обрали зовнішнє з'єднання від автомобілів до брендів, воно не було зазначено в поєднанні з кольорами - так що конкретний приєднання принесе лише результати, які відповідають обом таблицям.
Ось запит, який би спрацював, щоб отримати очікувані результати:
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)
Як ми бачимо, у запиті є два зовнішніх об’єднання, і результати проходять як очікується.
Тепер, як щодо тих інших типів приєднань, які ви запитуєте? Що з перехрестями?
Ну, не всі бази даних підтримують, intersectionале майже всі бази даних дозволять вам створити перехрестя за допомогою з'єднання (або добре структурованої де-небудь заяви).
Перетин - це тип з'єднання, дещо схожий на unionописаний вище, але різниця полягає в тому, що він повертає лише рядки даних, які є однаковими (і я маю на увазі ідентичними) між різними індивідуальними запитами, об'єднаними об'єднанням. Повертаються лише рядки, однакові в усіх відношеннях.
Простий приклад може бути таким:
select
*
from
colors
where
ID>2
intersect
select
*
from
colors
where
id<4
Хоча звичайний unionзапит повертає всі рядки таблиці (перший запит повертає щось, ID>2а другий - що завгодно ID<4), що призводить до повного набору, пересічний запит повертає лише відповідність рядків, id=3оскільки відповідає обом критеріям.
Тепер, якщо ваша база даних не підтримує intersectзапит, вищезазначене може бути легко виконано за допомогою наступного запиту:
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)
Якщо ви хочете виконати перетин двох різних таблиць, використовуючи базу даних, яка не підтримує запит перетину, вам потрібно буде створити з'єднання в кожному стовпчику таблиць.