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

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

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

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

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

ответы охватывают следующее:

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

6 ответов


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

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

  1. Часть 1
    • объединение двух или более таблиц с помощью внутреннего соединения (см. Википедии для получения дополнительной информации)
    • как использовать запрос на объединение
    • левый и правый внешние соединения (это stackOverflow ответ отлично описывает типы соединений)
    • пересечение запросов (и как их воспроизвести, если ваша база данных не поддерживает их) - это функция SQL-Server (см. info) и часть причина, по которой я написал все это в первую очередь.
  2. Часть 2
    • подзапросы-что они собой представляют, где их можно использовать и за чем следить
    • Картезиан присоединяется к АКА-о, несчастье!

Существует несколько способов извлечения данных из нескольких таблиц в база данных. В этом ответе я буду использовать синтаксис соединения ANSI-92. Это может отличаться от ряда других учебников, которые используют более старый синтаксис ANSI-89 (и если вы привыкли к 89, может показаться гораздо менее интуитивным, но все, что я могу сказать, это попробовать), как это много легче понять, когда запросы начинают усложняться. Зачем им пользоваться? Есть ли прирост производительности? The короткий ответ: нет, но это is легче читать, как только вы привыкнете к нему. Легче читать запросы, написанные другими людьми, используя этот синтаксис.

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

я сделал несколько таблиц, которые будут использоваться в итоговой таблице. Это даст нам разумную модель для работы. Для начала я буду запускать свои запросы к примеру базы данных, которая имеет следующую структуру. Я постараюсь вспомнить о распространенных ошибках, которые совершаются в начале, и объяснить, что с ними не так, а также, конечно, показать, как их исправить.

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

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)

и, наконец, связать все эти другие столы, стол, который связывает все вместе. Поле 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)

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

Итак, попадая в песок, Босс хочет знать идентификаторы всех спортивных автомобилей у него есть.

это простое соединение двух таблиц. У нас есть таблица, которая идентифицирует модель и таблицу с доступным запасом в ней. Как вы можете видеть, данные в на cars таблица относится к на 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'. Поскольку ID индексируется и первичный ключ (и это меньше ввода), позволяет использовать это в нашем запросе.

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 сохраняет цвет автомобиля ID и это ссылки обратно в цвета столбец id. Таким образом, аналогично оригиналу, мы можем присоединиться к третьей таблице:

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

вы можете видеть, что запрос выше не только связывает Соединенные таблицы с main 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)
строка с первого таблица с строка из второй таблицы.

Итак, босс вернулся, и он снова хочет получить больше информации. я хочу тот же список, но также включить в него 4WDs.

это, однако, дает нам отличный повод взглянуть на два разных способа достичь этого. Мы могли бы добавить еще одно условие к предложению 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

и следующее вернет все 4WDs:

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 - хотя то же самое alias использовался в обоих запросах.
  • у нас может быть только один 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 бренд I добавлен.

это потому, что соединение ищет сопоставление строки и таблицы. Поскольку нет данных в автомобилях типа 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)

и что, от босса skulks.

теперь, чтобы объяснить это более подробно, внешние соединения могут быть 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 но в значительной степени все базы данных позволят вам создать пересечение через join (или хорошо структурированный оператор where, по крайней мере).

пересечение-это тип соединения, несколько похожий на union как описано выше, но разница в том, что он только возвращает строки данных, которые идентичны (и я имею в виду идентичные) между различными отдельными запросами, Соединенными Союзом. Будут возвращены только строки, идентичные во всех отношениях.

A простой пример был бы таким:

select
    *
from
    colors
where
    ID>2
intersect
select
    *
from
    colors
where
    id<4

пока нормально union запрос вернет все строки таблицы (первый запрос возвращает что-либо более ID>2 а вторая что-то ID<4), что приведет к полному набору, запрос intersect будет возвращать только строку, соответствующую id=3 как он соответствует обоим критериям.

теперь, если ваша база данных не поддерживает intersect query, вышеуказанное можно легко accomlished с следующим запрос:

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)

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


хорошо, я нашел этот пост очень интересным, и я хотел бы поделиться некоторыми своими знаниями по созданию запроса. Спасибо за это 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

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

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

Демо SQLFiddle

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

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 ( ответ)


Трюк № 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

(большинство СУБД генерирует синтаксическую ошибку на этом из-за не указания некоторых неагрегированных столбцов на group by п.) результат дает правильный LatestVersion на каждом программном обеспечении, но, очевидно, DateReleased неверны. MySQL не поддерживает Window Functions и Common Table Expression еще как некоторые РСУБД уже делают. Решение этой проблемы заключается в создании 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 демо (ответ)


так вот оно что. Я отправлю еще один, как только вспомню любой другой часто задаваемые вопросы on 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


Часть 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)

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

здесь мы можем использовать подзапрос-на самом деле мы можем сделать два совершенно разных типа подзапроса, которые вернут те же результаты, которые нам нужны для этого. Первый-просто поместить подзапрос в 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)

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

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

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 предложение нашего запроса, а затем написал запрос и сказал базе данных, что это на самом деле таблица, а не - который база данных совершенно доволен. Там can быть некоторые преимущества использования этого метода (а также некоторые ограничения). Прежде всего, этот подзапрос ran после. Если бы наша база данных содержала большой объем данных, вполне могло бы быть значительное улучшение по сравнению с первым методом. Однако, поскольку мы используем это как таблицу, мы должны ввести дополнительные строки данных, чтобы они могли быть фактически присоединены к нашим строкам данных. Мы также должны быть уверены, что есть достаточно строки данных, если мы будем использовать простое соединение, как в запросе выше. Если вы помните, соединение будет только оттягивать строки, которые имеют соответствующие данные на и стороны соединения. Если мы не будем осторожны, это может привести к тому, что допустимые данные не будут возвращены из нашей таблицы cars, если в этом подзапросе не было соответствующей строки.

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

Итак, прежде чем мы продолжим, давайте быстро посмотрим, где еще мы можем использовать подзапрос. Мы можем использовать его в 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 this:

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 предложение-просто помните, что каждый из них вы вводите делает ваш запрос более сложным и, вероятно, займет больше времени для выполнения.

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


Часть 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)

, но это не эффективно в некоторых базах данных. Вот это ссылка на вопрос переполнения стека спрашиваю об этом, а вот Ан отличная в глубину статья если вы хотите попасть в nitty gritty.

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

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

вы можете использовать концепцию нескольких запросов в ключевом слове 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

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

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


надеется, что это заставит его найти таблицы, когда вы читаете вещь:

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