Часть 1 - Объединения и Союзы
Этот ответ охватывает:
- Часть 1
- Часть 2
- Подзапросы - что это такое, где их можно использовать и на что обратить внимание
- Декартов присоединяется к АКА - О, несчастье!
Существует несколько способов получения данных из нескольких таблиц в базе данных. В этом ответе я буду использовать синтаксис соединения 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)
Таблица брендов определяет различные марки автомобилей, которые может продать Caryard.
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)
И, наконец, связать все эти другие столы, стол, который связывает все воедино. Поле идентификатора фактически является уникальным номером лота, используемым для идентификации автомобилей.
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
for, 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
Теперь вернемся к запросу. Как вы видите, у нас есть информация, которая нам нужна, но у нас также есть информация, которая не запрашивалась, поэтому нам нужно включить в заявление пункт where, чтобы получить спортивные автомобили только в соответствии с запросом. Поскольку я предпочитаю метод псевдонимов таблиц, а не использовать имена таблиц снова и снова, я буду придерживаться его с этого момента.
Ясно, что нам нужно добавить предложение where в наш запрос. Мы можем определить спортивные автомобили либо по, 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 .
Это, однако, дает нам отличный повод взглянуть на два разных способа сделать это. Мы можем добавить еще одно условие к предложению where, например:
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
over есть небольшой скачок производительности, union all
но результаты могут стоить того - хотя я не буду спекулировать на подобных вещах.
На этой заметке, возможно, стоит отметить некоторые дополнительные заметки здесь.
- Если мы хотим упорядочить результаты, мы можем использовать,
order by
но вы больше не можете использовать псевдоним. В приведенном выше запросе добавление an 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
но почти все базы данных позволят вам создать пересечение посредством соединения (или, по крайней мере, хорошо структурированного оператора where).
Пересечение - это тип объединения, несколько похожий на 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)
Если вы хотите выполнить пересечение двух разных таблиц, используя базу данных, которая по своей сути не поддерживает запрос пересечения, вам нужно будет создать соединение для каждого столбца таблиц.