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

Пример 31: обновление кэширующих таблиц и полей

MySQL I

Решение 4.1.1.b (триггеры для таблицы subscriptions) (продолжение)

|

59-- Создание триггера, реагирующего на удаление выдачи книги:

60CREATE TRIGGER 'upd_avgs_on_subscriptions_del'

61AFTER DELETE

62ON 'subscriptions'

63FOR EACH ROW

64BEGIN

65UPDATE 'averages',

66

 

(SELECT COUNT('s id') AS 'subscribers count'

67

 

FROM

'subscribers') AS 'tmp subscribers count',

68

 

(SELECT COUNT('sb id') AS 'active count'

69

 

FROM

'subscriptions'

70

 

WHERE

'sb is active' = 'Y') AS 'tmp active count',

71

 

(SELECT COUNT('sb id') AS 'inactive count'

72

 

FROM

'subscriptions'

73

 

WHERE

'sb is active' = 'N') AS 'tmp inactive count',

74

 

(SELECT SUM(DATEDIFF('sb finish', 'sb start')) AS 'days sum'

75

 

FROM

'subscriptions'

76

 

WHERE

'sb is active' = 'N') AS 'tmp days sum'

77

SET

'books taken' = 'active count' / 'subscribers count',

78

 

'days to read' = 'days sum' / 'inactive count',

79

 

'books returned' = 'inactive count' / 'subscribers count';

80END;

81$$

82

83-- Восстановление разделителя завершения запросов:

84DELIMITER ;

Проверим работоспособность полученного решения. Будем изменять данные в таблицах subscribers и subscriptions и отслеживать изменения данных в таблице averages.

Исходное состояние таблицы averages таково:

books_taken

days_to_read

books_returned

1.25

 

 

 

46

1.5

 

 

Добавим читателя:

 

 

 

 

 

 

1

INSERT INTO 'subscribers'

 

 

2

 

 

 

('s_id',

 

 

3

 

 

 

's name')

 

 

4

VALUES

(500,

 

 

5

 

 

 

'Читателев

 

 

 

 

books_taken

days_to_read

books_returned

1

 

 

 

46

1.2

 

 

Теперь удалим его:

 

 

MySQL

Решение 4.1.1.b (проверка работоспособности)

 

1

 

DELETE FROM 'subscribers'

2

WHERE 's id' = 500

 

 

books_taken days_to_read books_returned

1.25

46

1.5

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

Пример 31: обновление кэширующих таблиц и полей

Добавим две выдачи книги:

MySQL I Решение 4.1.1.b (проверка работоспособности)

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

200,

9

 

1

10

 

1

11

 

'2019-01-12',

12

 

'2019-02-12',

13

 

'N'),

14

 

201,

15

 

2

16

 

1

17

 

'2020-01-12',

18

 

'2020-02-12',

19

 

'N' )

 

 

 

books_taken days_to_read books_returned

1.25

42.25

2

Изменим состояние добавленных выдач с «книга возвращена» на «книга не возвращена»:

Решение 4.1.1.b

1 UPDATE 'subscriptions'

2 SET 'sb_is_active' = 'Y

3 WHERE 'sb id' >= 200

books_taken

days_to_read

books_returned

1.75

 

 

46

 

1.5

 

 

Удалим эти две выдачи книг:

 

 

 

 

books_taken

days_to_read

books_returned

 

1.25

 

46

1.5

 

Итак, триггеры для MySQL работают корректно. Переходим к решению для

MS SQL Server.

MS SQL Решение 4.1.1 .b (создание агрегирующей

1таблицы)

2CREATE TABLE. [aVerages] ..............

3[books_taken] DOUBLE PRECISION NOT NULL,

4

[days_to_read] DOUBLE PRECISION NOT NULL,

5

[books returned] DOUBLE PRECISION NOT NULL

6

 

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

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

Пример 31: обновление кэширующих таблиц и полей

MS SQL Решение 4.1.1

и

 

1

.b

 

 

 

-- Очистка таблицы:

 

2

TRUNCATE TABLE [averages];

 

3

 

 

 

 

4

-- Инициализация данных:

 

5

INSERT INTO [averages]

 

6

 

([books taken]

 

7

 

 

[days to read],

 

8

 

 

[books returned]

 

9

SELECT ( [active count] / [subscribers count] )

AS [books taken],

10

 

( [days sum] / [inactive count] )

AS [days to read],

11

 

( [inactive count] / [subscribers count] ) AS [books returned]

12

FROM

(SELECT CAST(COUNT>[s id]) AS DOUBLE PRECISION)

13

 

AS [subscribers count]

 

14

 

FROM

[subscribers]) AS [tmp subscribers count],

15

 

(SELECT CAST(COUNT( [sb id] AS DOUBLE PRECISION)

16

 

AS [active count]

 

17

 

FROM

[subscriptions]

 

18

 

WHERE

[sb is active] = 'Y') AS [tmp active count],

19

 

(SELECT CAST(COUNT( [sb id] AS DOUBLE PRECISION)

20

 

AS [inactive count]

 

21

 

FROM

[subscriptions]

 

22

 

WHERE

[sb is active] = 'N') AS [tmp inactive count],

23

 

(SELECT CAST(SUM(DATEDIFF(day, [sb start], [sb finish] )

24

 

AS DOUBLE PRECISION) AS [days sum]

 

25

 

FROM

[subscriptions]

 

26

 

WHERE

[sb is active] = 'N') AS [tmp days sum];

 

 

 

 

 

Создадим на таблицах subscribers и subscriptions триггеры, модифицирующие данные в агрегирующей таблице averages. Напомним, что MS SQL Server позволяет указывать в триггере сразу несколько активирующих событий, что позволяет нам немного сократить количество написанного кода.

MS SQL Решение 4.1.1 .b (триггеры для таблицы subscribers) |

1CREATE TRIGGER [upd avgs on subscribers ins del]

2ON [subscribers]

3AFTER INSERT, DELETE

4AS

5UPDATE [averages]

6

SET

[books taken] = [active count] / [subscribers count]

7

 

[days to read] = [days sum] / [inactive count],

8

 

[books returned] = [inactive count] / [subscribers count]

9

FROM

(SELECT CAST(COUNT([s id] AS DOUBLE PRECISION)

10

 

AS [subscribers count]

11

 

FROM

[subscribers] AS [tmp subscribers count],

12

 

(SELECT CAST(COUNT([sb id]) AS DOUBLE PRECISION)

13

 

AS [active count]

14

 

FROM

[subscriptions]

15

 

WHERE

[sb is active] = 'Y') AS [tmp active count],

16

 

(SELECT CAST(COUNT([sb id]) AS DOUBLE PRECISION)

17

 

AS [inactive count]

18

 

FROM

[subscriptions]

19

 

WHERE

[sb is active] = 'N') AS [tmp inactive count],

20

 

(SELECT CAST(SUM(DATEDIFF(day, [sb start], [sb finish]))

21

 

AS DOUBLE PRECISION) AS [days sum]

22

 

FROM

[subscriptions]

23

 

WHERE

[sb is active] = 'N') AS [tmp days sum];

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

Пример 31: обновление кэширующих таблиц и полей

MS SQL Решение 4.1.1.b (триггеры для таблицы subscriptions)

1CREATE TRIGGER [upd_avgs_on_subscriptions_ins_upd_del]

2ON [subscriptions]

3AFTER INSERT, UPDATE, DELETE

4AS

5UPDATE [averages]

6

SET

[books_taken] = [active_count] / [subscribers_count]

 

 

[days_to_read] =

[days_sum] / [inactive_count],

8

 

[books_returned] =

[inactive_count] / [subscribers_count]

9

FROM

(SELECT CAST(COUNT([s_id]

AS DOUBLE PRECISION)

10

 

AS [subscribers_count]

 

11

 

FROM

[subscribers] AS [tmp_subscribers_count],

12

 

(SELECT CAST(COUNT([sb_id]) AS DOUBLE PRECISION)

13

 

AS [active_count]

 

 

14

 

FROM

[subscriptions]

 

15

 

WHERE

[sb_is_active] =

'Y') AS [tmp_active_count],

16

 

(SELECT CAST(COUNT([sb_id]) AS DOUBLE PRECISION)

17

 

AS [inactive_count]

 

18

 

FROM

[subscriptions]

 

19

 

WHERE

[sb_is_active] =

'N') AS [tmp_inactive_count],

20

 

(SELECT CAST(SUM(DATEDIFF(day, [sb_start], [sb_finish]))

21

 

AS DOUBLE PRECISION) AS

[days_sum]

22

 

FROM

[subscriptions]

 

23

 

WHERE

[sb is_ active]

= 'N') AS [tmp days sum];

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

MS SQL I Решение 4.1.1.b (запросы для проверки работоспособности) |

1 SET IDENTITY INSERT [subscribers] ON;

2

3-- Добавление читателя:

4INSERT INTO [subscribers]

5

 

([s id],

6

 

[s

name])

7

VALUES

500,

8

 

N'

Читателев Ч.Ч.');

9

 

 

 

10-- Удаление только что добавленного читателя:

11DELETE FROM [subscribers]

12WHERE [s id] = 500

13

14SET IDENTITY INSERT [subscribers] OFF;

15SET IDENTITY INSERT [subscriptions] ON;

17-- Добавление двух выдач книг:

18

INSERT INTO [subscriptions]

19

 

([sb id] ,

20

 

[sb

subscriber],

21

 

[sb

book]

22

 

[sb

start],

23

 

[sb

finish],

24

 

[sb

is active])

25

VALUES

200,

26

 

1

 

27

 

1

 

28

 

'2019-01-12',

29

 

'2019-02-12',

30

 

'N'),

31

 

201,

 

32

 

2

,

 

33

 

1

 

34

 

'2020-01-12',

35

 

'2020-02-12',

36

 

'N');

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

Пример 31: обновление кэширующих таблиц и полей

MS SQL I Решение 4.1.1.b (запросы для проверки работоспособности) (продолжение) |

37-- Изменение состояния добавленных выдач с «книга возвращена»

38-- на «книга не возвращена»:

39UPDATE [subscriptions]

40

SET

[sb is active] = 'Y'

41

WHERE

[sb id] >= 200;

42

 

 

43-- Удаление только что добавленных выдач книг:

44DELETE FROM [subscriptions]

45WHERE [sb id] >= 200;

46

47 SET IDENTITY_INSERT [subscriptions] OFF;

Переходим к решению для Oracle, которое отличается от решения для MS SQL Server только синтаксическими особенностями реализации той же логики:

Oracle і Решение 4.1.1.b (создание агрегирующей таблицы)

1CREATE TABLE "averages"

2(

3"books_taken" DOUBLE PRECISION NOT NULL,

4"days_to_read" DOUBLE PRECISION NOT NULL,

5"books_returned" DOUBLE PRECISION NOT NULL

6)

Oracl

і Решение 4.1.1.b (очистка таблицы и инициализация данных)

|

 

e

 

 

 

 

 

 

1

-- Очистка таблицы:

 

 

 

2

TRUNCATE TABLE "averages" ;

 

 

 

3

 

 

 

 

 

4

-- Инициализация данных:

 

 

 

5

INSERT INTO "averages"

 

 

 

6

 

"books taken"

 

 

 

7

 

"days to read"

 

 

 

8

 

"books returned")

 

 

9

SELECT ( "active count" / "subscribers count" )

AS "books taken"

10

( "days sum" / "inactive count" )

 

AS "days to read"

11

( "inactive count" / "subscribers count" ) AS "books returned"

12

FROM

COUNT("s id"

AS "subscribers count"

13

FROM

"subscribers"

"tmp subscribers count",

14

(SELECT COUNT("sb id"

AS "active count"

 

15

FROM

"subscriptions"

 

 

16

WHERE

"sb is active" = 'Y') "tmp active count"

17

(SELECT COUNT("sb id"

AS "inactive count"

 

18

FROM

"subscriptions"

 

 

19

WHERE

"sb is active" = 'N') "tmp inactive count",

20

(SELECT SUM "sb finish" - "sb start"

AS "days sum"

21

FROM

"subscriptions"

 

 

22

WHERE

"sb is active" = 'N') "tmp days sum"

Несмотря на то, что логика работы триггеров в Oracle полностью идентична подходам, использованным в MySQL и MS SQL Server, в силу синтаксических особенностей данной СУБД сам запрос на обновление данных выглядит несколько необычно. Здесь мы используем оператор MERGE, указав как условие объединения 1=1, т.е. заведомо выполняющееся равенство.

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

Пример 31: обновление кэширующих таблиц и полей

Oracle Решение 4.1.1 .b (триггеры для

1 CREATE TRIGGER "upd_avgs_on_sbrs_ins_del"

2AFTER INSERT OR DELETE

3ON "subscribers"

4BEGIN

5

MERGE INTO "averages"

6USING

7(

 

SELECT ( "active_count" / "subscribers_count" )

AS "books_taken"

9

 

( "days_sum" / "inactive_count" )

AS "days_to_read",

10

 

( "inactive_count" / "subscribers_count" ) AS"books_returned"

11

FROM

(SELECT COUNT "s_id")

AS "subscribers_count"

12

 

FROM

"subscribers"

"tmp_subscribers_count",

13

 

(SELECT COUNT "sb_id"

AS "active_count"

 

14

 

FROM

"subscriptions

"

 

15WHERE "sb_is_active" = 'Y') "tmp_active_count"

16(SELECT COUNT "sb_id" AS "inactive_count"

17

FROM

"subscriptions "

18WHERE "sb_is_active" = 'N') "tmp_inactive_count"

19(SELECT SUM("sb_finish " - "sb_start" AS "days_sum

20

 

FROM

"subscriptions "

21

 

WHERE

"sb_is_active" = 'N') " tmp_days_sum"

22

)

ON 1

1)

23

WHEN MATCHED THEN UPDATE

24

SET

"averages" "books_taken" = "tmp" "books_taken",

25"averages" "days_to_read" = "tmp" "days_to_read",

26"averages" "books_returned" = "tmp" "books_returned";

27END;

Oracle Решение 4.1.1.b (триггеры для

1 CREATE TRIGGER "upd_avgs_on_sbps_ins_upd_del"

2AFTER INSERT OR UPDATE OR DELETE

3ON "subscriptions"

4BEGIN

5 MERGE INTO "averages"

6USING

7(

 

SELECT ( "active_count" / "subscribers_count" )

AS "books_taken"

9

 

( "days_sum" / "inactive_count" )

AS "days_to_read",

10

 

( "inactive_count" / "subscribers_count" ) AS"books_returned"

11

FROM

(SELECT COUNT "s_id")

AS "subscribers_count"

12

 

FROM

"subscribers"

"tmp_subscribers_count",

13

 

(SELECT COUNT "sb_id"

AS "active_count"

 

14

 

FROM

"subscriptions

"

 

15WHERE "sb_is_active" = 'Y') "tmp_active_count"

16(SELECT COUNT "sb_id" AS "inactive_count"

17

FROM

"subscriptions "

18WHERE "sb_is_active" = 'N') "tmp_inactive_count"

19(SELECT SUM("sb_finish" - "sb_start" AS "days_sum

20

 

FROM

"subscriptions "

21

 

WHERE

"sb_is_active" = 'N') " tmp_days_sum"

22

)

ON 1

1)

23

WHEN MATCHED THEN UPDATE

24

SET

"averages" "books_taken" = "tmp" "books_taken",

25"averages" "days_to_read" = "tmp" "days_to_read",

26"averages" "books_returned" = "tmp" "books_returned";

27END;

Как и в решении{272} задачи 4.1.1.a{272}, здесь мы использовали возможность Oracle создавать т.н. «триггеры уровня выражения» (statement level triggers): такой триггер активируется после выполнения всей операции один раз, а не для каждого модифицируемого ряда отдельно, как это происходит, например, в MySQL, где поддерживаются только «триггеры уровня записи» (row level triggers), активирующиеся отдельно для каждого модифицируемого ряда.

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

Пример 31: обновление кэширующих таблиц и полей

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

 

Oracl

і Решение 4.1.1.b (запросы для проверки работоспособности) |

e

 

 

 

1

 

ALTER TRIGGER "TRG subscribers s id" DISABLE;

2

 

ALTER TRIGGER "TRG_subscriptions_sb_id" DISABLE;

3

4

 

 

-- Добавление читателя:

5

INSERT INTO "subscribers"

6

 

("s id"

7

 

"s name")

8

VALUES

(500

9

 

N'Читателев Ч.Ч. ' );

10

 

 

11-- Удаление только что добавленного читателя:

12DELETE FROM "subscribers"

13WHERE "s id" = 500;

14

15-- Добавление двух выдач книг:

16INSERT ALL

17INTO "subscriptions"

18

 

("sb id"

19

 

"sb subscriber"

20

 

"sb book",

21

 

"sb start"

22

 

"sb finish"

23

 

"sb is active"

24

VALUES

(200

25

 

1,

26

 

1,

27

 

TO DATE '2019-01-12', 'YYYY-MM-DD'),

28

 

TO DATE '2019-02-12', 'YYYY-MM-DD'),

29

 

'N' )

30

INTO "subscriptions"

31

 

("sb id"

32

 

"sb subscriber"

33

 

"sb book",

34

 

"sb start"

35

 

"sb finish"

36

 

"sb is active"

37

VALUES

(201

38

 

2,

39

 

1,

40

 

TO DATE '2020-01-12', 'YYYY-MM-DD'),

41

 

TO DATE '2020-02-12', 'YYYY-MM-DD'),

42

 

'N' )

43

SELECT 1 FROM "DUAL"

44

 

 

45-- Изменение состояния добавленных выдач с «книга возвращена»

46-- на «книга не возвращена»:

47UPDATE "subscriptions"

48

SET

"sb is active" = 'Y'

49

WHERE

"sb id" >= 200;

50

 

 

51-- Удаление только что добавленных выдач книг:

52DELETE FROM "subscriptions"

53WHERE "sb id" >= 200;

54

55ALTER TRIGGER "TRG subscribers s id" ENABLE;

56ALTER TRIGGER "TRG subscriptions sb id" ENABLE;

На этом решение данной задачи завершено.

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

Пример 31: обновление кэширующих таблиц и полей

СЗадание 4.1.1 .TSK.A: модифицировать схему базы данных «Библиотека» таким образом, чтобы таблица authors хранила актуальную информацию о дате последней выдачи книги автора читателю.

ОЗадание 4.1.1 .TSK.B: создать кэширующую таблицу best_averages, содержащую в любой момент времени следующую актуальную информацию: а) сколько в среднем книг находится на руках у читателей, за время работы с библиотекой прочитавших более 20 книг; б) за сколько в среднем по времени (в днях) прочитывает книгу читатель,

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

Задание 4.1.1.TSK.C: оптимизировать MySQL-триггеры из решения{281} задачи 4.1.1.b{272} так, чтобы не выполнять лишних действий там, где в них нет необходимости (подсказка: не в каждом случае нам нужны все собираемые имеющимися запросами данные).

 

Задание 4.1.1.TSK.D: доработать решение{272} задачи 4.1.1.a{272} для

(

MySQL таким образом, чтобы оно учитывало изменения в таблице sub-

scriptions, вызванные операцией каскадного удаления (при удалении

книг). Убедиться, что решения для MS SQL Server и Oracle не требуют такой

 

доработки.

 

Задание 4.1.1.TSK.E: доработать решение{281} задачи 4.1.1.b{272} для

 

MySQL таким образом, чтобы оно учитывало изменения в таблице sub-

 

scriptions, вызванные операцией каскадного удаления (при удалении

 

книг). Убедиться, что решения для MS SQL Server и Oracle не требуют

 

такой доработки.

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

Пример 32: обеспечение консистентности данных

4.1.2.Пример 32: обеспечение консистентности данных

ОЗадача 4.1.2.a{292}: модифицировать схему базы данных «Библиотека» таким образом, чтобы таблица subscribers хранила информацию о том, сколько

в настоящий момент книг выдано каждому из читателей.

О Задача 4.1.2.b{305}: модифицировать схему базы данных «Библиотека» таким образом, чтобы таблица genres хранила информацию о том, сколько в

настоящий момент книг относится к каждому жанру.

Ожидаемый результат 4.1.2.a.

Таблица subscribers содержит дополнительное поле, хранящее актуальную информацию о количестве выданных каждому читателю книг:

s_id

s_name

s_books

1

Иванов И.И.

0

2

Петров П.П.

0

3

Сидоров С.С.

3

4

Сидоров С.С.

2

Ожидаемый результат 4.1.2.b.

Таблица genres содержит дополнительное поле, хранящее актуальную информацию о количестве относящихся к каждому жанру книг:

g_id

g_name

g_books

1

Поэзия

2

2

Программирование

3

3

Психология

1

4

Наука

0

5

Классика

4

6

Фантастика

1

уЦ7

Решение 4.1.2.a{292}.

Как и в решении{272} задачи 4.1.1.a{272} здесь нужно будет выполнить три шага:

модифицировать таблицу subscribers (добавив туда поле для хранения количества выданных читателю книг);

проинициализировать значения количества выданных книг для всех читателей;

создать триггеры для поддержания этой информации в актуальном состоянии.

Вотличие от решения{272} задачи 4.1.1.a{272} здесь значение по умолчанию для нового поля представляет собой не NULL, а 0 (потому что «читатель ни разу не

приходил в библиотеку» — это «неизвестность», т.е. NULL, а «у читателя нет книг»

— это вполне чёткое и понятное значение, т.е. 0). Следуя той же логике, в инициализирующем запросе мы используем JOIN, а не LEFT JOIN — нет никакого смысла обновлять данные для читателей, ни разу не бравших книги.

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

Пример 32: обеспечение консистентности данных

Итак, для MySQL первые два шага выполняются с помощью следующих запросов.

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