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

Изучаем.SQL

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

FROM account a

WHERE (a.avail_balance, a.pending_balance) <> (SELECT

SUM(CASE

WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0

WHEN t.txn_type_cd = 'DBT' THEN t.amount * 1

ELSE t.amount END),

SUM(CASE

WHEN t.txn_type_cd = 'DBT' THEN t.amount * 1

ELSE t.amount END)

FROM transaction t

WHERE t.account_id = a.account_id);

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

Проверка существования

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

mysql>

SELECT c.cust_id, c.fed_id, c.cust_type_cd,

>

CASE

>

WHEN EXISTS (SELECT 1 FROM account a

>

WHERE a.cust_id = c.cust_id

>

AND a.product_cd = 'CHK') THEN 'Y'

>

ELSE 'N'

>

END has_checking,

>

CASE

>

WHEN EXISTS (SELECT 1 FROM account a

>

WHERE a.cust_id = c.cust_id

>

AND a.product_cd = 'SAV') THEN 'Y'

>

ELSE 'N'

>

END has_savings

>

FROM customer c;

+ + + + + + | cust_id | fed_id | cust_type_cd | has_checking | has_savings | + + + + + +

|

1

|

111 11 1111

|

I

|

Y

|

Y

|

|

2

|

222 22 2222

|

I

|

Y

|

Y

|

|

3

| 333 33 3333 | I

| Y

| N

|

|

4

| 444 44 4444 | I

| Y

| Y

|

|

5

| 555 55 5555 | I

| Y

| N

|

|

6

| 666 66 6666 | I

| Y

| N

|

|

7

| 777 77 7777 | I

| N

| N

|

|

8

| 888 88 8888 | I

| Y

| Y

|

|

9

| 999 99 9999 | I

| Y

| N

|

|

10

| 04 1111111

| B

| Y

| N

|

|

11

| 04 2222222

| B

| N

| N

|

|

12

| 04 3333333

| B

| Y

| N

|

|

13

| 04 4444444

| B

| N

| N

|

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

Каждое выражение case включает связанный подзапрос к таблице ac count: один для поиска текущих счетов, другой – сберегательных сче тов. Поскольку каждый блок when использует оператор exists, условия выполняются, если у клиента есть, по крайней мере, один счет иско мого типа.

В других случаях нас может интересовать количество встретившихся строк, но лишь постольку поскольку. Например, следующий запрос с помощью простого выражения case подсчитывает количество счетов каждого клиента, а затем возвращает 'None', '1', '2' или '3+':

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

> CASE (SELECT COUNT(*) FROM account a

>

WHERE a.cust_id = c.cust_id)

>

WHEN 0

THEN 'None'

>

WHEN 1

THEN '1'

>

WHEN 2

THEN '2'

>

ELSE '3+'

>

END num_accounts

> FROM customer c;

+ + + + + | cust_id | fed_id | cust_type_cd | num_accounts | + + + + +

|

1

| 111 11 1111

| I

| 3+

|

|

2

| 222 22 2222

| I

| 2

|

|

3

| 333 33 3333

| I

| 2

|

|

4

| 444 44 4444

| I

| 3+

|

|

5

| 555 55 5555

| I

| 1

|

|

6

| 666 66 6666

| I

| 2

|

|

7

| 777 77 7777

| I

| 1

|

|

8

| 888 88 8888

| I

| 2

|

|

9

| 999 99 9999

| I

| 3+

|

|

10

| 04 1111111

| B

| 2

|

|

11

| 04 2222222

| B

| 1

|

|

12

| 04 3333333

| B

| 1

|

|

13

| 04 4444444

| B

| 1

|

+ + + + + 13 rows in set (0.01 sec)

В этом запросе я не хотел проводить различия между клиентами, име ющими более двух счетов, поэтому выражение case просто создает ка тегорию '3+'. Подобный запрос может быть полезным при поиске кли ентов, с которыми можно связаться и предложить открыть новый счет в банке.

Ошибки деления на нуль

Проводя вычисления, включающие деление, нужно все время забо титься о том, чтобы знаменатель никогда не был равен нулю. Некото рые серверы БД, такие как Oracle Database, встретив нулевой знамена тель, формируют ошибку, а MySQL просто присваивает результату вы числения значение null, как показывает следующий пример:

mysql> SELECT 100 / 0;

+ + | 100 / 0 | + + | NULL | + +

1 row in set (0.00 sec)

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

mysql> SELECT a.cust_id, a.product_cd, a.avail_balance /

>

 

CASE

 

 

 

>

 

 

WHEN

prod_tots.tot_balance = 0

THEN 1

>

 

 

ELSE

prod_tots.tot_balance

 

>

 

END percent_of_total

 

 

> FROM account a INNER JOIN

 

 

>

(SELECT

a.product_cd, SUM(a.avail_balance) tot_balance

>

 

FROM account a

 

 

>

 

GROUP BY a.product_cd) prod_tots

 

>

 

ON

a.product_cd = prod_tots.product_cd;

+ + + +

| cust_id |

product_cd | percent_of_total |

+ + + +

|

10

|

BUS

|

0.0000

|

|

11

|

BUS

|

1.0000

|

|

1

|

CD

|

0.1538

|

|

6

|

CD

|

0.5128

|

|

7

|

CD

|

0.2564

|

|

9

|

CD

|

0.0769

|

|

1

|

CHK

|

0.0145

|

|

2

|

CHK

|

0.0309

|

|

3

|

CHK

|

0.0145

|

|

4

|

CHK

|

0.0073

|

|

5

|

CHK

|

0.0307

|

|

6

|

CHK

|

0.0017

|

|

8

|

CHK

|

0.0478

|

|

9

|

CHK

|

0.0017

|

|

10

|

CHK

|

0.3229

|

|

12

|

CHK

|

0.5281

|

|

3

|

MM

|

0.1298

|

|

4

|

MM

|

0.3219

|

|

9

|

MM

|

0.5483

|

|

1

|

SAV

|

0.2694

|

|

2

|

SAV

|

0.1078

|

|

4

|

SAV

|

0.4137

|

|

8

|

SAV

|

0.2091

|

|

13

|

SBL

|

1.0000

|

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

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

Условные обновления

При обновлении строк таблицы вам иногда придется принимать реше ния относительно того, какие значения должны быть заданы в опреде ленных столбцах. Например, после вставки новой транзакции долж ны измениться столбцы avail_balance, pending_balance и last_activi ty_date таблицы account. С обновлением двух последних столбцов про блем нет, но чтобы правильно изменить столбец avail_balance, надо проверить столбец funds_avail_date таблицы transaction и выяснить, сразу ли доступны фонды транзакции. Допустим, только что вставле на транзакция с ID 999, тогда изменить три столбца таблицы account можно с помощью следующего выражения update:

1 UPDATE account

2 SET last_activity_date = CURRENT_TIMESTAMP(), 3 pending_balance = pending_balance +

4(SELECT t.amount *

5 CASE t.txn_type_cd WHEN 'DBT' THEN 1 ELSE 1 END

6FROM transaction t

7WHERE t.txn_id = 999),

8avail_balance = avail_balance +

9(SELECT

10CASE

11WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0

12ELSE t.amount *

13CASE t.txn_type_cd WHEN 'DBT' THEN 1 ELSE 1 END

14END

15FROM transaction t

16WHERE t.txn_id = 999)

17WHERE account.account_id =

18(SELECT t.account_id

19FROM transaction t

20WHERE t.txn_id = 999);

Здесь всего три выражения case: два из них (строки 5 и 13) служат для изменения знака суммы транзакции для дебетовых транзакций, а третье выражение case (строка 10) предназначено для проверки даты доступности фондов. Если эта дата еще не наступила, к доступному ос татку добавляется нуль; в противном случае добавляется сумма тран закции.

Обработка значений Null

Хотя значения null удобны для хранения в таблицах неизвестных зна чений столбцов, они не всегда подходят для отображения или исполь зования в выражениях. Например, в окне ввода данных вы, скорее всего, предпочтете отображать слово «unknown», а не оставлять пустое поле. При извлечении данных выражение case позволяет вместо значе ния null подставлять строку:

SELECT emp_id, fname, lname,

CASE

WHEN title IS NULL THEN 'Unknown'

ELSE title

END

FROM employee;

Значения null в вычислениях часто являются причиной результата null, как показывает следующий пример:

mysql> SELECT (7 * 5) / ((3 + 14) * null);

+ + | (7 * 5) / ((3 + 14) * null) | + + | NULL | + + 1 row in set (0.08 sec)

Проводя вычисления, полезно преобразовать значения null в число (обычно 0 или 1) с помощью выражения case, чтобы обеспечить резуль тат вычисления, отличный от null. Например, при вычислении с учас тием столбца account.avail_balance можно было бы подставить 0 (при сложении или вычитании) или 1 (при умножении или делении) для тех счетов, которые уже открыты, но средства на них еще не помещены:

SELECT <some calcuation> + CASE

WHEN avail_balance IS NULL THEN 0 ELSE avail_balance

END

+ <rest of calculation>

...

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

Упражнения

Проверьте свою способность применять условную логику с помощью следующих примеров. Выполнив задание, сравните свои решения с от ветами, приведенными в приложении С.

11.1

Перепишите следующий запрос, использующий простое выражение case, таким образом, чтобы получить аналогичные результаты с помо щью выражения case с перебором вариантов. Попытайтесь свести к минимуму количество блоков when.

SELECT emp_id,

CASE title

WHEN 'President' THEN 'Management'

WHEN 'Vice President' THEN 'Management'

WHEN 'Treasurer' THEN 'Management'

WHEN 'Loan Manager' THEN 'Management'

WHEN 'Operations Manager' THEN 'Operations'

WHEN 'Head Teller' THEN 'Operations'

WHEN 'Teller' THEN 'Operations'

ELSE 'Unknown'

END

FROM employee;

11.2

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

mysql> SELECT open_branch_id, COUNT(*)

> FROM account

> GROUP BY open_branch_id;

+ + + | open_branch_id | COUNT(*) | + + +

|

1

|

8

|

|

2

|

7

|

|

3

|

3

|

|

4

|

6

|

+ + + 4 rows in set (0.00 sec)

Транзакции

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

Многопользовательские базы данных

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

К примеру, создается отчет, представляющий доступный остаток всех текущих счетов, открытых в отделении. Однако одновременно с вы полнением отчета происходит следующее:

Служащий отделения обрабатывает вклад для одного из клиентов.

Клиент заканчивает снимать деньги на банкомате в операционном зале.

Банковское приложение, выполняющееся в конце каждого месяца, начисляет процент по счетам.

Следовательно, пока создается отчет, несколько пользователей изме няют данные. Так, какие цифры должны появиться в отчете? Ответ от части зависит от того, как сервер реализовывает блокировку (locking) – механизм управления одновременным использованием ресурсов дан ных. Большинство серверов БД применяют одну из двух стратегий блокировки:

Пользователи, осуществляющие запись в БД, должны запрашивать и получать от сервера блокировку записи (write lock) для изменения данных. А пользователи, считывающие данные из БД, должны за прашивать и получать от сервера блокировку чтения (read lock) для осуществления запросов к данным. В то время как чтение мо жет осуществляться одновременно несколькими пользователями, для каждой таблицы (или ее части) одновременно выдается только одна блокировка записи, и запросы на чтение блокируются до тех пор, пока не будет снята блокировка записи.

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

контроль версий (versioning).

У обеих стратегий есть свои достоинства и недостатки. При первом под ходе время ожидания может оказаться длительным, если одновремен но поступило много запросов на чтение и запись. Второй подход может создать проблемы в случае длительных запросов, поскольку происхо дит изменение данных. В данной книге обсуждаются три сервера: Mi crosoft SQL Server использует первый подход, Oracle Database – вто рой, а MySQL – оба подхода (в зависимости от выбранного пользовате лем механизма хранения (storage engine), который обсуждается не много позже).

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

Блокирование таблицы

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

Блокирование страницы

Предотвращает одновременное изменение несколькими пользова телями данных одной страницы таблицы (страница – сегмент памя ти, обычно от 2 до 16 Кбайт).

Блокирование строки

Предотвращает одновременное изменение несколькими пользова телями одной строки таблицы.

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

к недопустимым временам ожидания. С другой стороны, в случае бло кировки строки сохраняется намного больше промежуточных резуль татов, но такая блокировка позволяет многим пользователям вносить изменения в одну таблицу, если это касается разных строк. Из трех серверов, обсуждаемых в этой книге, Microsoft SQL Server использует блокировки страницы и строки, Oracle Database – блокировку строки, а MySQL может блокировать таблицу, страницу или строку (опять же в зависимости от выбранного механизма хранения).

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

Что такое транзакция?

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

Этой дополнительной деталью пазла параллелизма является транзак ция (transaction) – механизм группировки нескольких SQL выраже ний, позволяющий успешно выполниться всем или ни одному из них. Если клиент пытается перевести 500 долларов со сберегательного счета на текущий, он немного расстроится, если деньги будут успешно сняты с первого счета, но не внесены на второй. Какой бы ни была причина сбоя (сервер был выключен для проведения работ по техническому об служиванию, истекло время ожидания запроса на блокировку страни цы таблицы account и др.), клиент захочет вернуть свои 500 долларов.

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

START TRANSACTION;

/* Снять деньги с первого счета, обеспечив достаточный остаток */ UPDATE account SET avail_balance = avail_balance 500

WHERE account_id = 9988 AND avail_balance > 500;

IF <Предыдущим выражением была изменена ровно одна строка> THEN /* Внести деньги на следующий счет */

UPDATE account SET avail_balance = avail_balance + 500 WHERE account_id = 9989;

IF <Предыдущим выражением была изменена ровно одна строка> THEN /* Все получилось, сделать изменения постоянными */

COMMIT; ELSE

/* Что то не так, отменить все изменения, сделанные в данной транзакции */ ROLLBACK;

END IF; ELSE

/* Недостаток средств на счете или при обновлении возникла ошибка */ ROLLBACK;

END IF;

Хотя предыдущий фрагмент кода может показаться похожим на один из процедурных языков программирования, предостав ляемых основными компаниями производителями БД, такими как PL/SQL от Oracle или Transact SQL от Microsoft, он написан на псевдокоде и не пытается имитировать ни один конкретный язык.

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

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

Конечно, если программе удается завершить оба выражения update, но сервер выключается до того, как смогут выполниться commit или roll back, откат транзакции произойдет, когда сервер вернется в рабочий режим. (Одна из задач, которую должен выполнить сервер перед воз вращением в нормальный режим работы, – найти все незавершенные транзакции, запущенные на момент выключения сервера, и выпол нить их откат.)

Запуск транзакции

Серверы БД обрабатывают создание транзакций одним из двух воз можных способов:

Активная транзакция всегда присутствует для каждого сеанса ра боты с БД, поэтому нет ни необходимости, ни способа для явного