Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
11
Добавлен:
15.01.2021
Размер:
10.35 Mб
Скачать

Пример 20: все разновидности запросов на объединение в трёх СУБД

MS SQL Решение 2.2.10.n

1SELECT [r_id],

2[r_name],

3[r_space],

4[c_id],

5[c_room],

6[c_name]

7

 

FROM

[rooms]

 

8

 

 

OUTER APPLY (SELECT

TOP ([r_space])

9

 

 

 

[c_id],

10

 

 

 

[c_room],

11

 

 

 

[c_name]

12

 

 

FROM

[computers]

13

 

 

WHERE [c_room] = [r_id]

14

 

 

ORDER

BY [c_name] ASC) AS [cross_apply_data]

 

 

 

 

 

15ORDER BY [r_id],

16[c_id]

Oracle Решение 2.2.10.n

1SELECT "r_id",

2"r_name",

3"r_space",

4"c_id",

5"c_room",

6"c_name"

7

 

FROM "rooms"

8

 

LEFT JOIN (SELECT "c_id",

9

 

"c_room",

10

 

"c_name",

11

 

( CASE

12

 

WHEN "c_room" IS NULL THEN 1

13

 

ELSE ROW_NUMBER()

14

 

OVER (

15

 

PARTITION BY "c_room"

16

 

ORDER BY "c_name" ASC)

17

 

END ) AS "position"

18

 

FROM "computers") "cross_apply_data"

19ON "r_id" = "c_room"

20WHERE "position" <= "r_space" OR "position" IS NULL

21ORDER BY "r_id",

22"c_id"

Если бы мы не добавили в строки 20 запросов для MySQL и Oracle вторую часть условия (OR position IS NULL), пустые комнаты не попали бы в итоговую выборку, т.к. им нет соответствия компьютеров, т.е. «номер» любого компьютера для них равен NULL, а сравнение NULL со значением поля r_space даёт FALSE.

Поясним ещё раз на графическом примере логику работы и разницу CROSS APPLY и OUTER APPLY.

 

В задаче 2.2.10.n{175} (CROSS APPLY):

 

 

 

 

 

 

c_id

c_room

c_name

 

 

≤5

1

1

Компьютер A в комнате 1

 

 

2

1

Компьютер B в комнате 1

 

 

 

r_id

r_name

r_space

 

 

 

1

Комната с двумя компьютерами

5

c_id

c_room

c_name

 

 

 

2

Комната с тремя компьютерами

5

3

2

Компьютер A в комнате 2

 

 

≤5

4

2

Компьютер B в комнате 2

 

 

 

5

2

Компьютер C в комнате 2

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 180/545

Пример 20: все разновидности запросов на объединение в трёх СУБД

 

 

 

 

В задаче 2.2.10.m{179} (OUTER APPLY):

 

 

 

 

 

 

 

 

 

c_id

c_room

 

c_name

 

 

 

 

≤5

1

1

Компьютер A в комнате 1

 

 

 

2

1

Компьютер B в комнате 1

 

 

 

 

r_id

r_name

r_space

 

 

 

 

 

 

1

Комната с двумя компьютерами

5

 

c_id

c_room

 

c_name

 

 

 

 

 

 

 

2

Комната с тремя компьютерами

5

 

3

2

Компьютер A в комнате 2

 

 

 

 

 

 

3

Пустая комната 1

2

≤5

4

2

Компьютер B в комнате 2

4

Пустая комната 2

2

 

5

2

Компьютер C в комнате 2

5

Пустая комната 3

2

 

 

 

 

 

 

 

 

 

 

 

 

c_id

c_room

c_name

 

 

 

≤2

 

 

NULL

NULL

NULL

 

 

 

 

 

 

c_id

c_room

c_name

 

 

 

≤2

 

 

NULL

NULL

NULL

 

 

 

 

 

 

c_id

c_room

c_name

 

 

 

≤2

 

 

NULL

NULL

NULL

Задание 2.2.10.TSK.A: показать информацию о том, кто из читателей и когда брал в библиотеке книги.

Задание 2.2.10.TSK.B: показать информацию обо всех читателях и датах выдачи им в библиотеке книг.

Задание 2.2.10.TSK.C: показать информацию о читателях, никогда не бравших в библиотеке книги.

Задание 2.2.10.TSK.D: показать книги, которые ни разу не были взяты никем из читателей.

Задание 2.2.10.TSK.E: показать информацию о том, какие книги в принципе может взять в библиотеке каждый из читателей.

Задание 2.2.10.TSK.F: показать информацию о том, какие книги (при условии, что он их ещё не брал) каждый из читателей может взять в библиотеке.

Задание 2.2.10.TSK.G: показать информацию о том, какие изданные до 2010-го года книги в принципе может взять в библиотеке каждый из читателей.

Задание 2.2.10.TSK.H: показать информацию о том, какие изданные до 2010-го года книги (при условии, что он их ещё не брал) может взять в библиотеке каждый из читателей.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 181/545

Пример 21: вставка данных

2.3. Модификация данных

2.3.1. Пример 21: вставка данных

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

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

MySQL: utf8 / utf8_general_ci;

MS SQL Server: UNICODE / Cyrillic_General_CI_AS;

Oracle: AL32UTF8 / AL16UTF16.

Тема кодировок и работы с ними огромна, очень специфична для каждой СУБД и почти не будет рассмотрена в этой книге (кроме задач 7.3.2.a{525} и 7.3.2.b{525}) — обратитесь к документации по соответствующей СУБД.

Задача 2.3.1.a{182}: добавить в базу данных информацию о том, что читатель с идентификатором 4 взял 15-го января 2016-го года в библиотеке книгу с идентификатором 3 и обещал вернуть её 30-го января 2016-го года.

Задача 2.3.1.b{185}: добавить в базу данных информацию о том, что читатель с идентификатором 2 взял 25-го января 2016-го года в библиотеке книги с идентификаторами 1, 3, 5 и обещал вернуть их 30-го апреля 2016го года.

Ожидаемый результат 2.3.1.a: в таблице subscriptions должна появиться такая новая запись.

sb_id

sb_subscriber

sb_book

sb_start

sb_finish

sb_is_active

 

101

4

3

2016-01-15

2016-01-30

N

 

 

Ожидаемый результат 2.3.1.b в таблице subscriptions должны появиться

 

три таких новых записи.

 

 

 

 

sb_id

sb_subscriber

sb_book

sb_start

sb_finish

sb_is_active

102

2

1

2016-01-25

2016-04-30

N

103

2

3

2016-01-25

2016-04-30

N

104

2

5

2016-01-25

2016-04-30

N

Решение 2.3.1.a{182}.

Во всех трёх СУБД в таблице subscriptions у нас созданы автоинкрементируемые первичные ключи, потому их значение указывать не надо. Остаётся только передать известные нам данные.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 182/545

Пример 21: вставка данных

MySQL

Решение 2.3.1.a

1

INSERT INTO `subscriptions`

2

 

 

(`sb_id`,

3

 

 

`sb_subscriber`,

4

 

 

`sb_book`,

5

 

 

`sb_start`,

6

 

 

`sb_finish`,

7

 

 

`sb_is_active`)

8

VALUES

(NULL,

9

 

 

4,

10

 

 

3,

11

 

 

'2016-01-15',

12

 

 

'2016-01-30',

13

 

 

'N')

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

Если мы не хотим явно указывать значение автоинкрементируемого первичного ключа, в MySQL можно вместо его значения передать NULL или исключить это поле из списка передаваемых полей и не передавать никаких данных (т.е. убрать имя поля в строке 2 и значение NULL в строке 8).

MS SQL

Решение 2.3.1.a

1

INSERT INTO [subscriptions]

2

 

 

([sb_subscriber],

3

 

 

[sb_book],

4

 

 

[sb_start],

5

 

 

[sb_finish],

6

 

 

[sb_is_active])

7

VALUES

(4,

8

 

 

3,

9

 

 

CAST(N'2016-01-15' AS DATE),

10

 

 

CAST(N'2016-01-30' AS DATE),

11

 

 

N'N')

В MS SQL Server автоинкрементация первичного ключа осуществляется за счёт того, что соответствующее поле помечается как IDENTITY. Вставка значений NULL и DEFAULT в такое поле запрещена, потому мы обязаны исключить его из списка полей и не передавать в него никаких значений.

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

перед вставкой данных выполнить команду SET IDENTITY_INSERT [subscriptions] ON;

после вставки данных выполнить команду SET IDENTITY_INSERT [subscriptions] OFF.

Эти команды соответственно разрешают и снова запрещают вставку в IDEN- TITY-поле явно переданных значений.

В строках 9-10 запроса производится явное преобразование строки, содержащей дату, к типу данных DATE. MS SQL Server позволяет этого не делать (конвертация происходит автоматически), но из соображений надёжности рекомендуется использовать явное преобразование.

Теми же соображениями надёжности вызвана необходимость ставить букву N перед строковыми константами (строки 9-11 запроса), чтобы явно указать СУБД на то, что строки представлены в т.н. «национальной кодировке» (и юникоде как форме представления символов). Для символов английского алфавита и символов, из которых состоит дата, этим правилом можно пренебречь, но как только в строке

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 183/545

Пример 21: вставка данных

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

Интересен тот факт, что даже в нашем конкретном случае (формат поля sb_is_active CHAR(1), а не NCHAR(1)) в создаваемом средствами MS SQL Server Management Studio дампе базы данных буква N присутствует перед значениями поля sb_is_active. Краткий вывод: если есть сомнения, использовать букву N перед строковыми константами, или нет, — лучше использовать.

Oracle

 

Решение 2.3.1.a

1

INSERT INTO "subscriptions"

2

 

 

("sb_id",

3

 

 

"sb_subscriber",

4

 

 

"sb_book",

5

 

 

"sb_start",

6

 

 

"sb_finish",

7

 

 

"sb_is_active")

8

VALUES

(NULL,

9

 

 

4,

10

 

 

3,

11

 

 

TO_DATE('2016-01-15', 'YYYY-MM-DD'),

12

 

 

TO_DATE('2016-01-30', 'YYYY-MM-DD'),

13

 

 

'N')

ВOracle обязательно нужно преобразовывать строковое представление дат

ксоответствующему типу.

Вотличие от MS SQL Server здесь нет необходимости указывать букву N пе-

ред строковыми константами со значениями дат и поля sb_is_active (можно и указать — это не приведёт к ошибке). Но для большинства остальных данных (например, текста на русском языке) букву N лучше указывать.

Особый интерес представляет передача значения автоинкрементируемого первичного ключа. В Oracle такой ключ реализуется нетривиальным способом — созданием SEQUENCE как «источника чисел» и триггера, который получает очередное число из SEQUENCE и использует его в качестве значения первичного ключа вставляемой записи.

Из этого следует, что в качестве значения автоинкрементируемого ключа вы можете передавать… что угодно: NULL, DEFAULT, число — триггер всё равно заменит переданное вами значение на очередное полученное из SEQUENCE число.

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

перед вставкой данных выполнить команду ALTER TRIGGER

"TRG_subscriptions_sb_id" DISABLE;

после вставки данных выполнить команду ALTER TRIGGER

"TRG_subscriptions_sb_id" ENABLE.

Эти команды соответственно отключают и снова включают триггер, устанавливающий значение автоинкрементируемого первичного ключа.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 184/545

Пример 21: вставка данных

Решение 2.3.1.b{185}.

Формально мы можем свести решение этой задачи к выполнению трёх отдельных запросов, аналогичных представленным в решении задачи 2.3.1.a{182}, но существует более эффективный способ выполнения вставки набора данных, который включает три действия:

временную приостановку работы индексов;

вставку данных одним большим блоком;

возобновление работы индексов.

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

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

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

В MySQL есть специальная команда по отключению и повторному включе-

нию индексов (ALTER TABLE ... DISABLE KEYS и ALTER TABLE ... ENABLE

KEYS), но она действует только на таблицы, работающей с методом доступа MyISAM. На повсеместно распространённый ныне метод доступа InnoDB она не действует.

Что можно сделать в методе доступа InnoDB?

Отключить и затем включить контроль уникальности (фактически, выключить и включить уникальные индексы).

Отключить и затем включить контроль внешних ключей.

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

Можно «поиграть» с автоинкрементируемыми первичными ключами5, но здесь нет универсальных рекомендаций.

Отключение уникальных индексов и контроля внешних ключей — крайне опасная операция, которая в перспективе может привести к катастрофическим повреждениям данных. Выполняйте её только как последнее средство, если ничто другое не помогает повысить производительность, и вы на 101% уверены в том, что передаваемые вами данные полностью удовлетворяют требованиям, соблюдение которых призваны контролировать уникальные индексы и внешние ключи.

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

5 https://dev.mysql.com/doc/refman/5.6/en/innodb-auto-increment-handling.html#innodb-auto-increment-lock-mode-usage-implica- tions

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 185/545

Пример 21: вставка данных

MySQL Решение 2.3.1.b

1-- Актуально для MyISAM, не актуально для InnoDB:

2ALTER TABLE `subscriptions` DISABLE KEYS; -- Отключение индексов.

3

4-- Следующие две команды -- ОЧЕНЬ опасное решение!

5SET foreign_key_checks = 0; -- Отключение проверки внешних ключей.

6SET unique_checks = 0; -- Отключение уникальных индексов.

7

 

 

8

 

SET autocommit = 0; -- Отключение автоматической фиксации транзакций.

9

 

 

10-- Сам запрос на вставку:

11INSERT INTO `subscriptions`

12

 

 

(`sb_subscriber`,

13

 

 

`sb_book`,

14

 

 

`sb_start`,

15

 

 

`sb_finish`,

16

 

 

`sb_is_active`)

17

 

VALUES

( 2,

18

 

 

1,

19

 

 

'2016-01-25',

20

 

 

'2016-04-30',

21

 

 

'N' ),

22

 

 

( 2,

23

 

 

3,

24

 

 

'2016-01-25',

25

 

 

'2016-04-30',

26

 

 

'N' ),

27

 

 

( 2,

28

 

 

5,

29

 

 

'2016-01-25',

30

 

 

'2016-04-30',

31

 

 

'N' );

32

 

 

 

33

 

COMMIT; -- Фиксация транзакции.

34

 

 

 

35

 

SET autocommit = 1; -- Включение автоматической фиксации транзакций.

36

 

 

 

37SET unique_checks = 1; -- Включение уникальных индексов.

38SET foreign_key_checks = 1; -- Включение проверки внешних ключей.

39

40-- Актуально для MyISAM, не актуально для InnoDB:

41ALTER TABLE `subscriptions` ENABLE KEYS; -- Включение индексов.

Ещё раз отметим, что в общем случае можно и нужно ограничиться запросом, показанным в строках 10-16. Остальные идеи приведены как справочная информация.

В MS SQL Server вы тоже можете временно выключить и затем снова вклю-

чить индексы командами ALTER INDEX ALL ON ... DISABLE и ALTER INDEX ALL ON ... REBUILD.

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

6https://msdn.microsoft.com/en-us/library/ms177456.aspx

7https://msdn.microsoft.com/en-us/library/ms190645.aspx

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 186/545

Пример 21: вставка данных

MS SQL Решение 2.3.1.b

1-- ОЧЕНЬ опасное решение!

2ALTER INDEX ALL ON [subscriptions] DISABLE;

3

4-- Обязательно включите кластерный индекс

5-- (в нашем случае -- первичный ключ) перед дальнейшими

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

7ALTER INDEX [PK_subscriptions] ON [subscriptions] REBUILD;

8

9-- Сам запрос на вставку:

10INSERT INTO [subscriptions]

11

 

 

([sb_subscriber],

12

 

 

[sb_book],

13

 

 

[sb_start],

14

 

 

[sb_finish],

15

 

 

[sb_is_active])

16

 

VALUES

(2,

17

 

 

1,

18

 

 

CAST(N'2016-01-25' AS DATE),

19

 

 

CAST(N'2016-04-30' AS DATE),

20

 

 

N'N'),

21

 

 

(2,

22

 

 

3,

23

 

 

CAST(N'2016-01-25' AS DATE),

24

 

 

CAST(N'2016-04-30' AS DATE),

25

 

 

N'N'),

26

 

 

(2,

27

 

 

5,

28

 

 

CAST(N'2016-01-25' AS DATE),

29

 

 

CAST(N'2016-04-30' AS DATE),

30

 

 

N'N');

31

 

 

 

32-- Включение индексов после их отключения:

33ALTER INDEX ALL ON [subscriptions] REBUILD;

Вотличие от MySQL и MS SQL Server в Oracle нет готового решения для выключения и включения всех индексов. Существует много алгоритмических решений8 этой задачи, но подавляющее большинство авторов совершенно справедливо предупреждают о множестве потенциальных проблем, рекомендуют этого не делать и даже высказывают предположения о том, что удаление и повторное создание индекса может оказаться более выгодным, чем его выключение и включение.

Единственный индекс на таблице subscriptions — это её первичный ключ,

потому в приведённом ниже примере именно его мы будем выключать и включать.

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

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

8 http://johnlevandowski.com/oracle-disable-constraints-and-make-indexes-unusable/

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 187/545

Пример 21: вставка данных

Oracle Решение 2.3.1.b

1-- ОЧЕНЬ опасное решение!

2ALTER TABLE "subscriptions" MODIFY CONSTRAINT "PK_subscriptions" DISABLE;

3

4-- Сам запрос на вставку:

5INSERT ALL

6 INTO "subscriptions"

7("sb_subscriber",

8"sb_book",

9"sb_start",

10"sb_finish",

11"sb_is_active")

12VALUES (2,

131,

14TO_DATE('2016-01-25', 'YYYY-MM-DD'),

15TO_DATE('2016-04-30', 'YYYY-MM-DD'),

16'N')

17 INTO "subscriptions"

18("sb_subscriber",

19"sb_book",

20"sb_start",

21"sb_finish",

22"sb_is_active")

23VALUES (2,

243,

25TO_DATE('2016-01-25', 'YYYY-MM-DD'),

26TO_DATE('2016-04-30', 'YYYY-MM-DD'),

27'N')

28 INTO "subscriptions"

29("sb_subscriber",

30"sb_book",

31"sb_start",

32"sb_finish",

33"sb_is_active")

34VALUES (2,

355,

36TO_DATE('2016-01-25', 'YYYY-MM-DD'),

37TO_DATE('2016-04-30', 'YYYY-MM-DD'),

38'N')

39SELECT 1 FROM "DUAL";

40

41-- Включение индекса после его отключения:

42ALTER TABLE "subscriptions" MODIFY CONSTRAINT "PK_subscriptions" ENABLE;

Исследование 2.3.1.EXP.A: сравним скорость вставки данных, основанной на циклическом повторении (1000 итераций) запроса, вставляющего одну строку, и выполнении одного запроса, вставляющего за один раз 1000 строк. Выполним по сто раз каждый вариантов поочерёдно.

Значения медиан времени, затраченного на вставку тысячи записей каждым из вариантов, таковы:

 

MySQL

MS SQL Server

Oracle

Цикл

3.000

2.000

6.000

Один запрос

0.112

0.937

5.807

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

В каждой СУБД соотношение времени работы двух представленных решения разное, но везде по скорости выигрывает вариант со вставкой большого количества строк одним запросом.

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 188/545

Пример 21: вставка данных

В Oracle такая небольшая разница во времени работы двух представленных вариантов обусловлена тем, что, фактически, это один и тот же вариант, только записанный разными способами. В этой СУБД тоже можно добиться очень высокой производительности вставки данных, но это достигается более сложными способами9.

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

Задание 2.3.1.TSK.A: добавить в базу данных информацию о троих новых читателях: «Орлов О.О.», «Соколов С.С.», «Беркутов Б.Б.»

Задание 2.2.1.TSK.B: отразить в базе данных информацию о том, что каждый из добавленных в задании 2.3.1.TSK.A читателей 20-го января 2016го года на месяц взял в библиотеке книгу «Курс теоретической физики».

Задание 2.2.1.TSK.C: добавить в базу данных пять любых авторов и десять книг этих авторов (по две на каждого); если понадобится, добавить в базу данных соответствующие жанры. Отразить авторство добавленных книг и их принадлежность к соответствующим жанрам.

9 http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52plsql-1709862.html

Работа с MySQL, MS SQL Server и Oracle в примерах © EPAM Systems, RD Dep, 2016–2018 Стр: 189/545