Частина 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)
Якщо ви хочете виконати перетин двох різних таблиць, використовуючи базу даних, яка не підтримує запит перетину, вам потрібно буде створити з'єднання в кожному стовпчику таблиць.