Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Изучаем.SQL

.pdf
Скачиваний:
61
Добавлен:
19.03.2016
Размер:
980.64 Кб
Скачать

+ + + + +

| Michael

| Smith

| NULL

| NULL

|

| Susan

| Barker

| Michael

| Smith

|

| Robert

| Tyler

| Michael

| Smith

|

| Susan

| Hawthorne

| Robert

| Tyler

|

| John

| Gooding

| Susan

| Hawthorne |

| Helen

| Fleming

| Susan

| Hawthorne |

| Chris

| Tucker

| Helen

| Fleming

|

| Sarah

| Parker

| Helen

| Fleming

|

| Jane

| Grossman

| Helen

| Fleming

|

| Paula

| Roberts

| Susan

| Hawthorne |

| Thomas

| Ziegler

| Paula

| Roberts

|

| Samantha

| Jameson

| Paula

| Roberts

|

| John

| Blake

| Susan

| Hawthorne |

| Cindy

| Mason

| John

| Blake

|

| Frank

| Portman

| John

| Blake

|

| Theresa

| Markham

| Susan

| Hawthorne |

| Beth

| Fowler

| Theresa

| Markham

|

| Rick

| Tulman

| Theresa

| Markham

|

+ + + + + 18 rows in set (0.00 sec)

Результирующий набор теперь включает Майкла Смита (Michael Smith), который является президентом банка, следовательно, начальника у не го нет. Для формирования списка всех сотрудников и их начальников, если таковые имеются, запрос использует левостороннее внешнее со единение. Если сделать внешнее соединение правосторонним, будут получены следующие результаты:

mysql>

SELECT e.fname, e.lname,

 

 

>

e_mgr.fname mgr_fname, e_mgr.lname mgr_lname

>

FROM

employee e RIGHT OUTER JOIN employee e_mgr

>

ON e.superior_emp_id = e_mgr.emp_id;

 

+ + + + +

| fname

| lname

| mgr_fname | mgr_lname |

+ + + + +

| Susan

| Barker

| Michael

| Smith

|

| Robert

| Tyler

| Michael

| Smith

|

| NULL

| NULL

| Susan

| Barker

|

| Susan

| Hawthorne

| Robert

| Tyler

|

| John

| Gooding

| Susan

| Hawthorne |

| Helen

| Fleming

| Susan

| Hawthorne |

| Paula

| Roberts

| Susan

| Hawthorne |

| John

| Blake

| Susan

| Hawthorne |

| Theresa | Markham

| Susan

| Hawthorne |

| NULL

| NULL

| John

| Gooding

|

| Chris

| Tucker

| Helen

| Fleming

|

| Sarah

| Parker

| Helen

| Fleming

|

| Jane

| Grossman

| Helen

| Fleming

|

| NULL

| NULL

| Chris

| Tucker

|

| NULL

| NULL

| Sarah

| Parker

|

| NULL

| NULL

| Jane

| Grossman

|

| Thomas

| Ziegler

| Paula

| Roberts

|

| Samantha

| Jameson

| Paula

| Roberts

|

| NULL

| NULL

| Thomas

| Ziegler

|

| NULL

| NULL

| Samantha

| Jameson

|

| Cindy

| Mason

| John

| Blake

|

| Frank

| Portman

| John

| Blake

|

| NULL

| NULL

| Cindy

| Mason

|

| NULL

| NULL

| Frank

| Portman

|

| Beth

| Fowler

| Theresa

| Markham

|

| Rick

| Tulman

| Theresa

| Markham

|

| NULL

| NULL

| Beth

| Fowler

|

| NULL

| NULL

| Rick

| Tulman

|

+ + + + + 28 rows in set (0.00 sec)

По этому запросу выбираются все руководители (по прежнему третий и четвертый столбцы) вместе со всеми их подчиненными. Поэтому Майкл Смит появляется дважды – как начальник Сьюзен Баркер (Su san Barker) и Роберта Тайлера (Robert Tyler). Сьюзен Баркер появляет ся один раз, она никем не руководит (значения null в первом и втором столбцах). Все 18 сотрудников появляются в третьем и четвертом столб цах, по крайней мере, один раз. Некоторые появляются несколько раз, если у них в подчинении несколько сотрудников. Таким образом, в ре зультирующем наборе 28 строк. Этот результат очень отличается от результата предыдущего запроса, а обеспечен он изменением всего од ного ключевого слова (left на right). Следовательно, при использова нии внешнего соединения необходимо тщательно продумывать, каким оно должно быть – левосторонним или правосторонним.

Перекрестные соединения

В главе 5 была представлена концепция декартова произведения, ко торое, в сущности, является результатом соединения нескольких таб лиц без указания каких либо условий соединения. Декартово произве дение довольно часто используется в результате случайности (т. е. ко гда разработчики просто забывают добавить в блок from условие соеди нения), но на самом деле не так уж широко распространено. Однако если действительно требуется получить декартово произведение двух таблиц, должно быть задано перекрестное соединение:

mysql> SELECT pt.name, p.product_cd, p.name

> FROM product p CROSS JOIN product_type pt;

+ + + + | name | product_cd | name | + + + +

| Customer Accounts

| AUT

| auto loan

|

| Customer Accounts

| BUS

|

business line of credit |

| Customer Accounts

| CD

|

certificate of deposit

|

| Customer Accounts

|

CHK

| checking account

|

| Customer Accounts

|

MM

| money market account

|

| Customer Accounts

|

MRT

| home mortgage

|

| Customer Accounts

|

SAV

| savings account

|

| Customer Accounts

|

SBL

| small business loan

|

| Insurance Offerings

|

AUT

| auto loan

|

| Insurance Offerings

|

BUS

| business line of credit

|

| Insurance Offerings

|

CD

| certificate of deposit

|

| Insurance Offerings

|

CHK

| checking account

|

| Insurance Offerings

|

MM

| money market account

|

| Insurance Offerings

|

MRT

| home mortgage

|

| Insurance Offerings

|

SAV

| savings account

|

| Insurance Offerings

|

SBL

| small business loan

|

| Individual and Business Loans |

AUT

| auto loan

|

| Individual and Business Loans |

BUS

| business line of credit |

| Individual and Business Loans |

CD

| certificate of deposit

|

| Individual and Business Loans |

CHK

| checking account

|

| Individual and Business Loans |

MM

| money market account

|

| Individual and Business Loans |

MRT

| home mortgage

|

| Individual and Business Loans |

SAV

| savings account

|

| Individual and Business Loans |

SBL

| small business loan

|

+ + + + 24 rows in set (0.00 sec)

Этот запрос формирует декартово произведение таблиц product и prod uct_type. В результате получаем 24 строки (8 строк product умножают ся на 3 строки product_type). Но теперь, когда известно, что такое пере крестное соединение и как оно задается, надо определиться с тем, за чем оно используется. Большинство книг по SQL описывают, что такое перекрестное соединение, и затем говорят, что используется оно ред ко. Но мне бы хотелось поделиться с читателем ситуациями, в кото рых я нахожу перекрестное соединение довольно полезным.

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

mysql>

SELECT 'Small Fry' name, 0 low_limit, 4999.99 high_limit

>

UNION ALL

 

 

 

 

 

>

SELECT 'Average Joes' name, 5000 low_limit, 9999.99 high_limit

>

UNION ALL

 

 

 

 

 

> SELECT 'Heavy Hitters'

name, 10000 low_limit, 9999999.99 high_limit;

+ + + +

| name

 

| low_limit |

high_limit |

+ + + +

| Small Fry

|

0

|

4999.99

|

| Average Joes

|

5000

|

9999.99

|

| Heavy Hitters |

10000

|

9999999.99

|

+ + + + 3 rows in set (0.00 sec)

Хотя эта таблица является именно тем, что требовалось для разделения клиентов на три группы на основании их совокупного остатка на счете, эта стратегия слияния однострочных таблиц с помощью оператора union all не очень подходит, если требуется соорудить большую таблицу.

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

SELECT '2004 01 01' dt UNION ALL

SELECT '2004 01 02' dt UNION ALL

SELECT '2004 01 03' dt UNION ALL

...

...

...

SELECT '2004 12 29' dt UNION ALL

SELECT '2004 12 30' dt UNION ALL

SELECT '2004 12 31' dt

Создавать запрос, соединяющий результаты 366 запросов, немного уто мительно, поэтому, наверное, нужна другая стратегия. Что если сгене рировать таблицу с 366 строками (2004 год был високосным) и одним столбцом, содержащим число от 0 до 366, и затем добавлять это число дней к 1 января 2004? Вот одна из возможных методик формирования подобной таблицы:

mysql>

SELECT ones.num + tens.num + hundreds.num

>

FROM

 

 

 

> (SELECT

0

num UNION ALL

>

SELECT

1

num UNION ALL

>

SELECT

2

num UNION ALL

>

SELECT

3

num UNION ALL

>

SELECT

4

num UNION ALL

>

SELECT

5

num UNION ALL

>

SELECT

6

num UNION ALL

>

SELECT

7

num UNION ALL

>

SELECT

8

num UNION ALL

>

SELECT

9

num) ones

>

CROSS JOIN

 

> (SELECT

0

num UNION ALL

>

SELECT

10

num UNION ALL

>

SELECT

20

num UNION ALL

>

SELECT

30

num UNION ALL

>

SELECT

40

num UNION ALL

>

SELECT

50

num UNION ALL

>

SELECT

60

num UNION ALL

>

SELECT

70

num UNION ALL

>

SELECT

80

num UNION ALL

>

SELECT

90

num) tens

>

CROSS JOIN

 

>

(SELECT

0 num UNION ALL

>

SELECT

100

num UNION ALL

>

SELECT

200

num UNION ALL

>

SELECT

300

num) hundreds;

+ + | ones.num + tens.num + hundreds.num | + +

|

0

|

|

1

|

|

2

|

|

3

|

|

4

|

|

5

|

|

6

|

|

7

|

|

8

|

|

9

|

|

10

|

|

11

|

|

12

|

...

 

 

...

 

 

...

 

 

|

391

|

|

392

|

|

393

|

|

394

|

|

395

|

|

396

|

|

397

|

|

398

|

|

399

|

+ + 400 rows in set (0.00 sec)

Если найти декартово произведение трех наборов – {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 10, 20, 30, 40, 50, 60, 70, 80, 90} и {0, 100, 200, 300} – и сло жить значения во всех трех столбцах, получится результат, состоя щий из 400 строк, содержащих все числа от 0 до 399. Хотя это больше 366 строк, необходимых для формирования набора дней 2004 года, из бавиться от лишних строк достаточно просто. Вскоре я покажу, как это сделать.

Следующий шаг – преобразовать числа в набор дат. Для этого восполь зуемся функцией date_add() и добавим каждое число в результирую щий набор к 1 января 2004 года. Затем введем условие фильтрации, чтобы отбросить все даты 2005 года.

mysql>

SELECT DATE_ADD('2004 01 01',

>

INTERVAL

(ones.num

+ tens.num + hundreds.num) DAY) dt

>

FROM

 

 

 

> (SELECT

0

num UNION

ALL

>

SELECT

1

num UNION

ALL

>

SELECT

2

num UNION

ALL

>

SELECT

3

num UNION

ALL

>

SELECT

4

num UNION

ALL

>

SELECT

5

num UNION

ALL

>

SELECT

6

num UNION

ALL

>

SELECT

7

num UNION

ALL

>

SELECT

8

num UNION

ALL

>

SELECT

9

num) ones

 

>

CROSS JOIN

 

> (SELECT

0

num UNION

ALL

>

SELECT

10

num UNION ALL

>

SELECT

20

num UNION ALL

>

SELECT

30

num UNION ALL

>

SELECT

40

num UNION ALL

>

SELECT

50

num UNION ALL

>

SELECT

60

num UNION ALL

>

SELECT

70

num UNION ALL

>

SELECT

80

num UNION ALL

>

SELECT

90

num) tens

 

>

CROSS JOIN

 

> (SELECT

0

num UNION

ALL

>

SELECT

100 num UNION ALL

>

SELECT

200 num UNION ALL

>

SELECT

300 num) hundreds

>

WHERE DATE_ADD('2004 01 01',

>

INTERVAL

(ones.num

+ tens.num + hundreds.num) DAY) < '2005 01 01';

+ +

 

 

 

| dt

|

 

 

 

+ +

 

 

 

| 2004 01 01 |

 

 

 

| 2004 01 02 | | 2004 01 03 | | 2004 01 04 | | 2004 01 05 | | 2004 01 06 | | 2004 01 07 | | 2004 01 08 | | 2004 01 09 | | 2004 01 10 |

...

...

...

| 2004 02 20 | | 2004 02 21 | | 2004 02 22 | | 2004 02 23 |

| 2004 02 24 | | 2004 02 25 | | 2004 02 26 | | 2004 02 27 | | 2004 02 28 | | 2004 02 29 | | 2004 03 01 |

...

...

...

| 2004 12 20 | | 2004 12 21 | | 2004 12 22 | | 2004 12 23 | | 2004 12 24 | | 2004 12 25 | | 2004 12 26 | | 2004 12 27 | | 2004 12 28 | | 2004 12 29 | | 2004 12 30 | | 2004 12 31 | + +

366 rows in set (0.01 sec)

В этом подходе замечательно то, что результирующий набор автомати чески включает 29 февраля без всяких дополнительных вмеша тельств, поскольку сервер БД вычисляет его, когда добавляет 59 дней к 1 января 2004 года.

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

mysql>

SELECT days.dt, COUNT(a.account_id)

>

FROM account a RIGHT OUTER JOIN

>

(SELECT DATE_ADD('2004 01 01',

>

INTERVAL (ones.num

+ tens.num + hundreds.num) DAY) dt

>

FROM

 

 

>

(SELECT 0

num UNION ALL

>

SELECT 1

num UNION ALL

>

SELECT 2

num UNION ALL

>

SELECT 3

num UNION ALL

>

SELECT 4

num UNION ALL

>

SELECT 5

num UNION ALL

>

SELECT 6

num UNION ALL

>

SELECT 7

num UNION ALL

>

SELECT 8

num UNION ALL

>

SELECT 9

num) ones

 

>

CROSS JOIN

 

>

(SELECT

0 num UNION ALL

 

 

>

SELECT

10 num UNION ALL

 

>

SELECT

20 num UNION ALL

 

>

SELECT

30 num UNION ALL

 

>

SELECT

40 num UNION ALL

 

>

SELECT

50 num UNION ALL

 

>

SELECT

60 num UNION ALL

 

>

SELECT

70 num UNION ALL

 

>

SELECT

80 num UNION ALL

 

>

SELECT

90 num) tens

 

 

>

CROSS JOIN

 

 

>

(SELECT

0 num UNION ALL

 

 

>

SELECT

100 num UNION ALL

 

>

SELECT

200 num UNION ALL

 

>

SELECT

300 num) hundreds

 

>

WHERE DATE_ADD('2004 01 01',

>

INTERVAL (ones.num + tens.num + hundreds.num) DAY) <

>

 

'2005 01 01') days

 

 

>

ON days.dt = a.open_date

 

 

> GROUP

BY days.dt;

 

 

+ + +

| dt

 

| COUNT(a.account_id) |

+ + +

| 2004 01 01

|

 

0

|

| 2004 01 02

|

 

0

|

| 2004 01 03

|

 

0

|

| 2004 01 04

|

 

0

|

| 2004 01 05

|

 

0

|

| 2004 01 06

|

 

0

|

| 2004 01 07

|

 

0

|

| 2004 01 08

|

 

0

|

| 2004 01 09

|

 

0

|

| 2004 01 10

|

 

0

|

| 2004 01 11

|

 

0

|

| 2004 01 12

|

 

1

|

| 2004 01 13

|

 

0

|

| 2004 01 14

|

 

0

|

| 2004 01 15

|

 

0

|

...

 

 

 

 

 

...

 

 

 

 

 

...

 

 

 

 

 

| 2004 12 15

|

 

0

|

| 2004 12 16

|

 

0

|

| 2004 12 17

|

 

0

|

| 2004 12 18

|

 

0

|

| 2004 12 19

|

 

0

|

| 2004 12 20

|

 

0

|

| 2004 12 21

|

 

0

|

| 2004 12 22

|

 

0

|

| 2004 12 23

|

 

0

|

| 2004 12 24

|

 

0

|

| 2004 12 25 |

0

|

| 2004 12 26 |

0

|

| 2004 12 27 |

0

|

| 2004 12 28

|

1

|

| 2004 12 29

|

0

|

| 2004 12 30

|

0

|

| 2004 12 31

|

0

|

+ + + 366 rows in set (0.03 sec)

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

Естественные соединения

Если вы ленивы (а кто не ленив?), можно выбрать тип соединения, при котором сервер БД сам определяет необходимые условия соединения указанных вами таблиц. Известный как естественное соединение (na tural join), этот тип соединения делает предположение о необходимых условиях соединения, полагаясь на идентичные имена столбцов в таб лицах. Например, таблица account включает столбец cust_id, являю щийся внешним ключом к таблице customer, первичный ключ которой также имеет имя cust_id. Таким образом, можно написать запрос, ис пользующий для соединения этих двух таблиц natural join:

mysql> SELECT a.account_id, a.cust_id, c.cust_type_cd, c.fed_id

> FROM account a NATURAL JOIN customer c;

+ + + + + | account_id | cust_id | cust_type_cd | fed_id | + + + + +

|

1

|

1

| I

| 111 11 1111 |

|

2

|

1

| I

| 111 11 1111 |

|

3

|

1

| I

| 111 11 1111 |

|

4

|

2

| I

| 222 22 2222 |

|

5

|

2

| I

| 222 22 2222 |

|

6

|

3

| I

| 333 33 3333 |

|

7

|

3

| I

| 333 33 3333 |

|

8

|

4

| I

| 444 44 4444 |

|

9

|

4

| I

| 444 44 4444 |

|

10

|

4

| I

| 444 44 4444

|

|

11

|

5

| I

| 555 55 5555

|

|

12

|

6

| I

| 666 66 6666

|

|

13

|

6

| I

| 666 66 6666

|

|

14

|

7

| I

| 777 77 7777 |

|

15

|

8

| I

| 888 88 8888 |

|

16

|

8

| I

| 888 88 8888 |

|

17

|

9

| I

| 999 99 9999 |

|

18

|

9

| I

| 999 99 9999 |

|

19

|

9

| I

| 999 99 9999 |

|

20

|

10

| B

| 04 1111111

|

|

21

|

10

| B

| 04 1111111

|

|

22

|

11

| B

| 04 2222222

|

|

23

|

12

| B

| 04 3333333

|

|

24

|

13

| B

| 04 4444444

|

+ + + + + 24 rows in set (0.02 sec)

Поскольку задано естественное соединение, сервер проверил описания таблиц и добавил для этих двух таблиц условие соединения a.cust_id = c.cust_id.

Все это хорошо и замечательно, но что будет, если имена столбцов в таблицах не совпадают? Например, в таблице account также есть внешний ключ к таблице branch, но этот столбец в таблице account на зван open_branch_id, а не branch_id. Посмотрим, что произойдет, если попытаться провести natural join между таблицами account и branch:

mysql> SELECT a.account_id, a.cust_id, a.open_branch_id,

> FROM account a NATURAL JOIN branch b;

+ + + + + | account_id | cust_id | open_branch_id | name | + + + + +

|

1

|

1

|

2

| Headquarters

|

|

2

|

1

|

2

| Headquarters

|

|

3

|

1

|

2

| Headquarters

|

|

4

|

2

|

2

| Headquarters

|

|

5

|

2

|

2

| Headquarters

|

|

6

|

3

|

3

| Headquarters

|

|

7

|

3

|

3

| Headquarters

|

|

8

|

4

|

1

| Headquarters

|

|

9

|

4

|

1

| Headquarters

|

|

10

|

4

|

1

| Headquarters

|

|

11

|

5

|

4

| Headquarters

|

|

12

|

6

|

1

| Headquarters

|

|

13

|

6

|

1

| Headquarters

|

|

14

|

7

|

2

| Headquarters

|

|

15

|

8

|

4

| Headquarters

|

|

16

|

8

|

4

| Headquarters

|

|

17

|

9

|

1

| Headquarters

|

|

18

|

9

|

1

| Headquarters

|

|

19

|

9

|

1

| Headquarters

|

|

20

|

10

|

4

| Headquarters

|

|

21

|

10

|

4

| Headquarters

|

|

22

|

11

|

2

| Headquarters

|

|

23

|

12

|

4

| Headquarters

|