it-swarm.com.ru

SQL-запрос возвращает данные из нескольких таблиц

Я хотел бы знать следующее:

  • как получить данные из нескольких таблиц в моей базе данных?
  • какие методы существуют для этого?
  • что такое объединения и союзы и чем они отличаются друг от друга?
  • Когда я должен использовать каждый из них по сравнению с другими?

Я планирую использовать это в моем (например, PHP) приложении, но не хочу выполнять несколько запросов к базе данных, какие варианты у меня есть, чтобы получить данные из нескольких таблиц в одном запросе?

Примечание: я пишу это так, как мне хотелось бы, чтобы я мог дать ссылку на хорошо написанное руководство по многочисленным вопросам, с которыми я постоянно сталкиваюсь в очереди PHP, поэтому я могу сослаться на это для более подробной информации, когда Я отправляю ответ.

Ответы покрывают следующее:

  1. Часть 1 - Объединения и Союзы
  2. Часть 2 - Подзапросы
  3. Часть 3 - Уловки и эффективный код
  4. Часть 4 - Подзапросы в предложении From
  5. Часть 5 - Смешанная сумка с хитростями Джона
418
Fluffeh

Часть 1 - Объединения и Союзы

Этот ответ охватывает:

  1. Часть 1
  2. Часть 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 для 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 вместо union all наблюдается небольшое снижение производительности, но результаты могут стоить того, хотя я не буду спекулировать на подобных вещах.

На этой заметке, возможно, стоит отметить некоторые дополнительные заметки здесь.

  • Если мы хотим упорядочить результаты, мы можем использовать order by, но вы больше не можете использовать псевдоним. В приведенном выше запросе добавление order by a.ID может привести к ошибке - что касается результатов, столбец называется ID, а не a.ID - даже если в обоих запросах использовался один и тот же псевдоним.
  • У нас может быть только один оператор order by, и он должен быть последним.

В следующих примерах я добавляю несколько дополнительных строк в наши таблицы.

Я добавил Holden в таблицу брендов. Я также добавил строку в cars со значением color12, которое не имеет ссылки в таблице цветов.

Ладно, босс снова вернулся, выкрикивая запросы - * Я хочу подсчитать каждую марку, которую мы несем, и количество автомобилей в ней! `- Как правило, мы просто попадаем в интересный раздел нашего обсуждения, и босс хочет больше работы ,.

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. Влево или вправо определяет, какая таблица полностью включена. left outer join будет включать все строки из таблицы слева, а (как вы уже догадались) 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)

Если вы хотите выполнить пересечение двух разных таблиц, используя базу данных, которая по сути не поддерживает запрос на пересечение, вам нужно будет создать соединение в каждом столбце из таблиц.

449
Fluffeh

Хорошо, я нашел этот пост очень интересным, и я хотел бы поделиться некоторыми своими знаниями по созданию запроса. Спасибо за это Fluffeh. Другие, кто может прочитать это и могут почувствовать, что я неправ, на 101% свободны редактировать и критиковать мой ответ. ( Честно говоря, я очень благодарен за исправление моей ошибки. )

Я буду публиковать некоторые часто задаваемые вопросы в теге MySQL.


Трюк № 1 ( строк, который соответствует нескольким условиям )

Учитывая эту схему

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');

ВОПРОС

Найдите все фильмы , которые принадлежат как минимум к обеим категориям Comedy и Romance.

Решение

Этот вопрос иногда может быть очень сложным. Может показаться, что такой запрос будет ответом:

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

SQLFiddle Demo

что, безусловно, очень неправильно, потому что это не дает никакого результата . Объяснение этому заключается в том, что существует только одно допустимое значение CategoryName в каждой строке . Например, первое условие возвращает true , второе условие всегда false. Таким образом, с помощью оператора AND оба условия должны выполняться; в противном случае это будет ложным. Другой запрос такой,

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

SQLFiddle Demo

и результат все еще неверен, потому что он соответствует записи, которая имеет хотя бы одно одно совпадение с categoryName. Реальным решением было бы подсчет количества экземпляров записи на фильм . Номер экземпляра должен соответствовать общему количеству значений, указанных в условии.

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

SQLFiddle Demo (ответ)


Трюк № 2 ( максимальная запись для каждой записи )

Данная схема,

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');

ВОПРОС

Найдите последнюю версию для каждого программного обеспечения. Вывести следующие столбцы: SoftwareName, Descriptions, LatestVersion ( из столбца VersionNo ), DateReleased

Решение

Некоторые разработчики SQL по ошибке используют агрегатную функцию MAX(). Они имеют тенденцию создавать, как это,

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

SQLFiddle Demo

( большинство СУБД генерирует синтаксическую ошибку по этому поводу, поскольку не указываются некоторые неагрегированные столбцы в предложении group by), в результате получается корректное LatestVersion для каждого программного обеспечения, но, очевидно, DateReleased неверны. MySQL не поддерживает Window Functions и Common Table Expression, как это уже делают некоторые RDBMS. Обойти эту проблему можно, создав subquery, которая получает индивидуальный максимум versionNo для каждого программного обеспечения, а затем объединяется с другими таблицами.

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

SQLFiddle Demo (ответ)


Так вот и все. Я опубликую еще один, как только вспомню любой другой FAQ по тегу MySQL. Спасибо за чтение этой маленькой статьи. Я надеюсь, что вы хотя бы получили хоть немного знаний об этом.

ОБНОВЛЕНИЕ 1


Трюк № 3 ( Поиск последней записи между двумя идентификаторами )

Данная схема

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');

ВОПРОС

Найти последний разговор между двумя пользователями.

Решение

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
)

SQLFiddle Demo

98
John Woo

Часть 2 - Подзапросы

Хорошо, теперь босс снова ворвался - я хочу список всех наших автомобилей с маркой и общее количество этой марки у нас!

Это прекрасная возможность использовать следующий трюк в нашем наборе SQL вкусностей - подзапрос. Если вы не знакомы с этим термином, подзапрос - это запрос, который выполняется внутри другого запроса. Есть много разных способов их использования.

Для нашего запроса, давайте сначала составим простой запрос, в котором будут перечислены все автомобили и марки:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID

Теперь, если мы хотим просто подсчитать количество автомобилей, отсортированных по марке, мы можем, конечно, написать это:

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 |
+--------+-----------+

Итак, мы должны иметь возможность просто добавить функцию count к нашему исходному запросу, верно?

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)

К сожалению, нет, мы не можем этого сделать. Причина в том, что когда мы добавляем идентификатор автомобиля (столбец a.ID), мы должны добавить его в группу, так что теперь, когда работает функция подсчета, для каждого идентификатора соответствует только один идентификатор.

Однако здесь мы можем использовать подзапрос - фактически мы можем сделать два совершенно разных типа подзапроса, которые будут возвращать те же результаты, которые нам нужны для этого. Первый заключается в том, чтобы просто поместить подзапрос в предложение select. Это означает, что каждый раз, когда мы получаем строку данных, подзапрос будет выполняться, получать столбец данных и затем вставлять его в наш ряд данных.

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)

И Бэм !, это сделало бы нас. Однако, если вы заметили, этот подзапрос должен будет выполняться для каждой строки данных, которую мы возвращаем. Даже в этом небольшом примере у нас всего пять разных марок автомобилей, но подзапрос выполнялся одиннадцать раз, поскольку у нас есть одиннадцать строк данных, которые мы возвращаем. Так что в данном случае это не самый эффективный способ написания кода.

Для другого подхода, давайте запустим подзапрос и представим, что это таблица:

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)

Итак, у нас есть одинаковые результаты (упорядоченные немного по-другому - кажется, что база данных хотела вернуть результаты, упорядоченные по первому столбцу, который мы выбрали на этот раз) - но те же правильные числа.

Итак, в чем разница между ними и когда мы должны использовать каждый тип подзапроса? Во-первых, давайте убедимся, что мы понимаем, как работает этот второй запрос. Мы выбрали две таблицы в предложении from нашего запроса, а затем написали запрос и сказали базе данных, что на самом деле это была таблица, и база данных полностью ей довольна. может есть некоторые преимущества использования этого метода (а также некоторые ограничения). Прежде всего, этот подзапрос выполнялся один раз. Если бы наша база данных содержала большой объем данных, вполне может быть значительное улучшение по сравнению с первым методом. Однако, поскольку мы используем это как таблицу, мы должны добавить дополнительные строки данных, чтобы они могли быть присоединены к нашим строкам данных. Мы также должны быть уверены, что есть достаточно строк данных, если мы собираемся использовать простое соединение, как в запросе выше. Если вы помните, объединение будет извлекать только те строки, которые имеют совпадающие данные на обеих сторонах объединения. Если мы не будем осторожны, это может привести к тому, что действительные данные не будут возвращены из нашей таблицы автомобилей, если в этом подзапросе не было подходящей строки.

Теперь, оглядываясь на первый подзапрос, также есть некоторые ограничения. поскольку мы собираем данные обратно в одну строку, мы можем ТОЛЬКО извлечь одну строку данных. Подзапросы, используемые в предложении select запроса, очень часто используют только агрегатную функцию, такую ​​как sum, count, max или другая аналогичная агрегатная функция. Они не имеют к, но это часто, как они написаны.

Итак, прежде чем мы продолжим, давайте кратко рассмотрим, где еще мы можем использовать подзапрос. Мы можем использовать его в предложении where - теперь этот пример немного надуманный, как и в нашей базе данных, есть более эффективные способы получения следующих данных, но если посмотреть только на пример, давайте посмотрим:

select
    ID,
    brand
from
    brands
where
    brand like '%o%'

+----+--------+
| ID | brand  |
+----+--------+
|  1 | Ford   |
|  2 | Toyota |
|  6 | Holden |
+----+--------+
3 rows in set (0.00 sec)

Это возвращает нам список идентификаторов брендов и названий брендов (второй столбец добавлен только для того, чтобы показать нам бренды), которые содержат в имени букву o.

Теперь мы можем использовать результаты этого запроса в предложении where:

select
    a.ID,
    b.brand
from
    cars a
        join brands b
            on a.brand=b.ID
where
    a.brand in
        (
        select
            ID
        from
            brands
        where
            brand like '%o%'
        )

+----+--------+
| ID | brand  |
+----+--------+
|  2 | Ford   |
|  7 | Ford   |
|  1 | Toyota |
|  5 | Toyota |
|  8 | Toyota |
|  9 | Toyota |
| 11 | Toyota |
+----+--------+
7 rows in set (0.00 sec)

Как вы можете видеть, несмотря на то, что подзапрос возвращал три идентификатора бренда, наша таблица автомобилей содержала записи только для двух из них.

В этом случае, для более подробной информации, подзапрос работает так, как если бы мы написали следующий код:

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)

Опять же, вы можете увидеть, как подзапрос по сравнению с ручным вводом изменил порядок строк при возврате из базы данных.

Пока мы обсуждаем подзапросы, давайте посмотрим, что еще мы можем сделать с подзапросом:

  • Вы можете поместить подзапрос в другой подзапрос, и так далее, и так далее. Существует предел, который зависит от вашей базы данных, но, за исключением рекурсивных функций некоторых безумных и маниакальных программистов, большинство людей никогда не достигнет этого предела.
  • Вы можете поместить несколько подзапросов в один запрос, несколько в предложении select, некоторые в предложении from и еще пару в предложении where - просто помните, что каждый добавленный вами запрос делает ваш запрос более сложным и может занять больше времени, чтобы выполнить.

Если вам нужно написать какой-то эффективный код, может быть полезно написать запрос несколькими способами и посмотреть (либо по времени, либо с помощью плана объяснения), какой запрос является оптимальным для получения ваших результатов. Первый способ, который работает, не всегда может быть лучшим способом сделать это.

61
Fluffeh

Часть 3 - хитрости и эффективный код

MySQL в () эффективность

Я думал, что добавлю несколько дополнительных битов, для подсказок и трюков, которые придумали.

Один вопрос, который я вижу, справедливо задан: Как получить несоответствующие строки из двух таблиц , и я вижу ответ, наиболее часто принимаемый как нечто как показано ниже (на основе нашей таблицы автомобилей и марок - в которой Холден указан как бренд, но не отображается в таблице автомобилей):

select
    a.ID,
    a.brand
from
    brands a
where
    a.ID not in(select brand from cars)

И да это будет работать.

+----+--------+
| ID | brand  |
+----+--------+
|  6 | Holden |
+----+--------+
1 row in set (0.00 sec)

Однако это не эффективно в некоторых базах данных. Вот ссылка на вопрос переполнения стека об этом, а вот отличная статья если вы хотите разобраться с мелочами.

Короткий ответ: если оптимизатор не справляется с этим эффективно, может быть гораздо лучше использовать запрос, подобный следующему, чтобы получить несопоставленные строки:

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)

Обновить таблицу с той же таблицей в подзапросе

Аааа, еще один старый, но хороший - старый Вы не можете указать целевую таблицу 'бренды' для обновления в предложении FROM .

MySQL не позволит вам выполнить запрос update... с вложенным выбором в той же таблице. Теперь вы можете подумать, почему бы просто не вставить это в пункт where, верно? Но что, если вы хотите обновить только строку с датой max() среди множества других строк? Вы не можете точно сделать это в предложении 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

Итак, мы не можем этого сделать, а? Ну, не совсем так. Существует хитрый обходной путь, о котором удивительно большое количество пользователей не знает, хотя он включает в себя некоторые хакерские действия, на которые вам нужно будет обратить внимание.

Вы можете вставить подзапрос в другой подзапрос, который создает достаточный промежуток между двумя запросами, чтобы он работал. Однако обратите внимание, что может быть безопаснее прикрепить запрос в транзакции - это предотвратит любые другие изменения в таблицах во время выполнения запроса.

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
58
Fluffeh

Вы можете использовать концепцию множественных запросов в ключевом слове FROM. Позвольте мне показать вам один пример:

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

Вы можете использовать столько таблиц, сколько захотите. Используйте внешние объединения и объединения там, где это необходимо, даже внутри подзапросов таблиц.

Это очень простой способ задействовать столько таблиц и полей.

18
prashant1988

Надеюсь, что это заставит его находить таблицы, когда вы читаете эту вещь:

jsfiddle

mysql> show columns from colors;                                                         
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+           
| id    | int(3)      | NO   | PRI | NULL    | auto_increment |
| color | varchar(15) | YES  |     | NULL    |                |
| Paint | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+
6
Anton Chan