Материал: Using_MySql,_MS_SQL_Server_and_Oracle(1)

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Пример 51: вычисление дат

месяца оказывается ≤ номера дня анализируемой даты, «номер недели» надо увеличить на единицу. Так для 2016.10.20 дни с понедельника по пятницу будут третьим понедельником месяца, третьим вторником месяца и т.д., а суббота и воскресенье будут, соответственно, четвёртой субботой и четвёртым воскресеньем месяца.

Функция WEEKDAY в 11-й строке запроса возвращает порядковый номер дня недели, где нумерация идёт так: ПН = 0, ВТ = 1, СР = 2 и т.д. Чтобы получить более привычный человеку результат, где ПН = 1, ВТ = 2, СР = 3 и т.д., мы прибавляем к результату работы функции единицу.

На этом решение для MySQL завершено, переходим к MS SQL Server.

Здесь нам придётся компенсировать эффект «начала недели с воскресенья», который будет в большинстве случаев присутствовать по умолчанию. Теоретически в MS SQL Server можно выполнить команду SET DATEFIRST 1, которая объявит понедельник началом недели, но практически во множестве источников отмечено, что это может не сработать. Также в MS SQL Server ISO-номер недели вычисляется не так, как в MySQL (первые числа года могут оказаться не нулевой неделей, а 52-й или 53-й).

Следующий код написан для случая, когда первым днём недели MS SQL Server считает воскресенье.

MS SQL Решение 7.4.1.a

1WITH [iso_week_data] AS

2(

3SELECT CASE

4

 

WHEN DATEPART(iso_week,

5

 

CONVERT(VARCHAR(6), [sb_start], 112) + '01') >

6

 

DATEPART(dy,

7

 

CONVERT(VARCHAR(6), [sb_start], 112) + '01')

8

 

THEN 0

9

 

ELSE DATEPART(iso_week,

10

 

CONVERT(VARCHAR(6), [sb_start], 112) + '01')

11END AS [real_iso_week_of_month_start],

12CASE

13WHEN DATEPART(iso_week, [sb_start]) > DATEPART(dy, [sb_start])

14THEN 0

15ELSE DATEPART(iso_week, [sb_start])

16END AS [real_iso_week_of_this_date],

17[sb_start],

18[sb_id]

19FROM [subscriptions]

20)

21SELECT [sb_id],

22[sb_start],

23CASE

24WHEN DATEPART(dw,

25

 

DATEADD(day, -1,

26

 

CONVERT(VARCHAR(6), [sb_start], 112)

27

 

+ '01')) <=

28

 

DATEPART(dw, DATEADD(day, -1, [sb_start]))

29

 

THEN [real_iso_week_of_this_date] -

30

 

[real_iso_week_of_month_start] + 1

31ELSE [real_iso_week_of_this_date] - [real_iso_week_of_month_start]

32END AS [W],

33DATEPART(dw, DATEADD(day, -1, [sb_start])) AS [D]

34FROM [iso_week_data]

Рассмотрение начнём со строк 29-31, в которых реализована та же самая идея, что и в строках 6-9 решения для MySQL: из номера в году недели, на которую приходится анализируемая дата, вычитается номер в году недели, на которую приходится первое число месяца, к которому относится анализируемая дата.

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

Пример 51: вычисление дат

В строках 24-28 реализуется та же логика, что и в строках 4-5 решения для MySQL: сравниваются номера дней недели для анализируемой даты и первого числа месяца (если номер дня первого числа месяца оказывается ≤ номера дня анализируемой даты, «номер недели» надо увеличить на единицу).

Операция CONVERT(VARCHAR(6), [sb_start], 112) позволяет получить значение года и месяца в виде шести символов, например: 2016.10.20 даст 201610. После выполнения + '01' мы получим 20161001, т.е. первое число октября. Число 112 — очередное «магическое значение» (см. подробности в документации).

Операция DATEADD(day, -1, [sb_start]), встречающаяся множество раз во всём запросе, нужна для того, чтобы обозначить понедельник как начало недели, т.к. сейчас началом недели MS SQL Server считает воскресенье, и номер дня недели получается «некорректным» (ВС = 1, СБ = 7 и т.д.).

Остаётся рассмотреть CTE в строках 1-20: этот код нужен для того, чтобы для первых дней года MS SQL Server не вернул номер недели как 52 или 53. Для этого номер недели сравнивается с номером дня в году. Если номер недели оказывается большим, его нужно превратить в 0.

На этом решение для MS SQL Server завершено, переходим к Oracle.

Здесь нам тоже придётся компенсировать эффект «начала недели с воскресенья», который будет в большинстве случаев присутствовать по умолчанию. В Oracle можно использовать команду ALTER SESSION SET NLS_TERRITORY=Russia для установки начала дня с понедельника, но мы снова будем предполагать худшее (например, переключать этот параметр нельзя) и напишем решение для ситуации, когда первым днём недели считается воскресенье.

Oracle Решение 7.4.1.a

1WITH "iso_week_data" AS

2(

3SELECT CASE

4

 

WHEN TO_NUMBER(TO_CHAR(TRUNC("sb_start",

'mm'), 'IW'))

>

5

 

 

TO_NUMBER(TO_CHAR(TRUNC("sb_start",

'mm'),

'DDD'))

6

 

THEN

0

 

 

 

7

 

ELSE

TO_NUMBER(TO_CHAR(TRUNC("sb_start",

'mm'),

'IW'))

 

8END AS "real_iso_week_of_month_start",

9CASE

10WHEN TO_NUMBER(TO_CHAR("sb_start", 'IW')) >

11

 

TO_NUMBER(TO_CHAR("sb_start", 'DDD'))

12THEN 0

13ELSE TO_NUMBER(TO_CHAR("sb_start", 'IW'))

14END AS "real_iso_week_of_this_date",

15"sb_start",

16"sb_id"

17FROM "subscriptions"

18)

19SELECT "sb_id", "sb_start",

20CASE

21WHEN (TRUNC("sb_start", 'mm') -

22

 

NEXT_DAY(TRUNC("sb_start", 'mm')

- 7, 'MON'))

23

 

<= ("sb_start" - NEXT_DAY("sb_start"

- 7, 'MON'))

24

 

THEN "real_iso_week_of_this_date" -

 

25

 

"real_iso_week_of_month_start" +

1

26

 

ELSE "real_iso_week_of_this_date" -

 

27

 

"real_iso_week_of_month_start"

 

28END AS "W",

29"sb_start" - NEXT_DAY("sb_start" - 7, 'MON') + 1 AS "D"

30FROM "iso_week_data"

ВOracle (как и в MS SQL Server) ISO-номер недели вычисляется не так, как

вMySQL (первые числа года могут оказаться не нулевой неделей, а 52-й или 53-й)

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

Пример 51: вычисление дат

— это также придётся компенсировать дополнительными вычислениями по аналогии с решением для MS SQL Server (CTE в строках 1-18 аналогична CTE в строках 1-20 решения для MS SQL Server за исключением способа конвертации типов данных и способа получения первого числа месяца — в Oracle для этого можно использовать функцию TRUNC).

Основная часть запроса в строках 20-30 также аналогична решению для MS SQL Server за исключением способа получения корректного номера дня недели. Рассмотрим это подробнее на примере 29-й строки.

Функция NEXT_DAY возвращает дату следующего за указанной датой указанного дня недели (например, NEXT_DAY(2016.10.20, суббота) вернёт дату ближайшей субботы, т.е. 2016.10.22).

Чтобы получить номер дня недели, нам нужно узнать разницу в днях между анализируемой датой и предыдущим (предшествующим ей) понедельником, дату которого мы получаем так NEXT_DAY("sb_start" - 7, 'MON') (это выражение можно прочитать как «вернуть дату ближайшего понедельника относительно дня, на неделю в прошлом от анализируемого»).

Итак, номер дня недели (почти) готов: "sb_start" - NEXT_DAY("sb_start" - 7, 'MON'). Остаётся добавить 1, чтобы нумерация не начиналась с нуля. Так получается финальное значение номера дня недели.

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

Решение 7.4.1.b{528}.

Решение данной задачи построено на логике обработки дат, представленной в решении 7.4.1.a{528}, а сам код функций специально написан не оптимальным, но максимально подробным образом и прокомментирован (все поясняющие результаты получены для даты 2016.10.20).

Код функции для MySQL выглядит следующим образом:

MySQL Решение 7.4.1.b

1DELIMITER $$

2CREATE FUNCTION GetWeekAndDay (date_var DATE)

3RETURNS CHAR(4)

4BEGIN

5

 

DECLARE day_number TINYINT;

-- номер дня недели (4)

 

6

 

DECLARE week_number TINYINT;

-- номер полной

недели

месяца (3)

7

 

DECLARE current_wn TINYINT;

-- номер в году

недели,

 

8

 

 

-- к которой относится

 

9

 

 

-- анализируемая дата (42)

10

 

DECLARE first_dom_wn TINYINT; -- номер в году

недели, к которой относится

11

 

 

-- первый день месяца,

к которому относится

12

 

 

-- анализируемая дата (39)

13DECLARE current_dom TINYINT; -- номер дня в месяце (20)

14SET day_number = WEEKDAY(date_var) + 1; -- нумерация идёт с 0,

15

 

-- потому нужен +1 (3+1 = 4)

 

 

 

16SET current_dom = DAY(date_var); -- (20)

17SET current_wn = WEEK(date_var, 5); -- про "5" см. документацию (42)

18SET first_dom_wn = WEEK(date_var - INTERVAL current_dom-1 DAY, 5); -- (39)

19SET week_number = current_wn - first_dom_wn; -- (3)

20

21IF WEEKDAY(date_var - INTERVAL current_dom-1 DAY) <= WEEKDAY(date_var)

22THEN

23SET week_number = week_number + 1;

24END IF;

25RETURN CONCAT('W', week_number, 'D', day_number); -- W3D4

26END;

27$$

28DELIMITER ;

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

Пример 51: вычисление дат

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

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

1SELECT `sb_id`,

2`sb_start`,

3GetWeekAndDay(`sb_start`) AS `DW`

4FROM `subscriptions`

Код функции для MS SQL Server выглядит следующим образом:

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

1CREATE FUNCTION GetWeekAndDay(@date_var DATETIME)

2RETURNS VARCHAR(4)

3AS

4BEGIN

5

 

DECLARE @day_number TINYINT;

-- номер дня недели (4)

6

 

DECLARE @week_number TINYINT;

-- номер полной недели месяца (3)

7

 

DECLARE @current_wn TINYINT;

-- номер в году недели,

 

 

 

 

8

 

 

-- к которой относится

9

 

 

-- анализируемая дата (42)

10

 

DECLARE @first_dom_wn TINYINT;

-- номер в году недели, к которой

11

 

 

-- относится первый день месяца,

12

 

 

-- к которому относится

13

 

 

-- анализируемая дата (39)

14

 

DECLARE @current_doy INT;

-- номер дня в году (294)

15

 

DECLARE @first_dom_doy INT;

-- номер в году первого дня месяца, к

16

 

 

-- которому относится анализируемая

17

 

 

-- дата (275)

18DECLARE @real_iso_week_of_month_start TINYINT;

19-- ISO-номер в году недели, к которой относится первое число месяца,

20-- к которому относится анализируемая дата (39), для первых чисел

21-- января будет 0, а не 52 или 53, как это вычисляет MS SQL Server

22DECLARE @real_iso_week_of_this_date TINYINT;

23-- ISO-номер в году недели, к которой относится анализируемая дата (42),

24-- для первых чисел января будет 0, а не 52 или 53, как это

25-- вычисляет MS SQL Server

26

27SET @day_number = DATEPART(dw, DATEADD(day, -1, @date_var));

28-- @day_number = DATEPART(dw, (2016.10.20 - 1)) =

29

 

--

DATEPART(dw, 2016.10.19) = 4

30

 

 

 

31SET @current_wn = DATEPART(iso_week, @date_var); -- 42

32SET @first_dom_wn = DATEPART(iso_week,

33

 

 

CONVERT(VARCHAR(6), @date_var, 112) + '01');

34

 

--

@first_dom_wn = DATEPART(iso_week,

'201610' + '01')

=

35

 

--

DATEPART(iso_week,

'20161001') = 39

 

36

 

 

 

 

 

37SET @current_doy = DATEPART(dy, @date_var); -- 294

38SET @first_dom_doy = DATEPART(dy,

39

 

CONVERT(VARCHAR(6), @date_var, 112) + '01');

40

 

-- @first_dom_doy = DATEPART(dy,

'201610' + '01') =

41

 

DATEPART(dy,

'20161001') = 275

42

 

 

 

43-- Актуально, например, для 2017.01.01

44IF (@first_dom_wn > @first_dom_doy)

45SET @real_iso_week_of_month_start = 0

46ELSE SET @real_iso_week_of_month_start = @first_dom_wn;

47

48-- Актуально, например, для 2017.01.02

49IF (@current_wn > @current_doy)

50SET @real_iso_week_of_this_date = 0

51ELSE SET @real_iso_week_of_this_date = @current_wn;

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

Пример 51: вычисление дат

 

MS SQL

 

Решение 7.4.1.b (продолжение)

 

52

 

 

SET @week_number

= @real_iso_week_of_this_date -

 

53

 

 

 

 

@real_iso_week_of_month_start; -- (3)

 

54

 

 

 

 

 

 

55

 

 

IF (DATEPART(dw,

DATEADD(day, -1,

 

56

 

 

 

 

CONVERT(VARCHAR(6), @date_var, 112) + '01'))

 

 

 

 

 

 

 

57<= @day_number)

58SET @week_number = @week_number + 1;

59

60RETURN CONCAT('W', @week_number, 'D', @day_number); -- W3D4

61END;

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

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

1SELECT [sb_id],

2[sb_start],

3dbo.GetWeekAndDay([sb_start]) AS [DW]

4FROM [subscriptions];

Код функции для Oracle выглядит следующим образом:

Oracle Решение 7.4.1.b

1CREATE OR REPLACE FUNCTION GetWeekAndDayISO(date_var IN DATE)

2RETURN VARCHAR

3IS

4

 

day_number NUMBER(1);

--

номер дня недели (4)

5

 

week_number NUMBER(1);

--

номер полной недели месяца (3)

6

 

current_wn NUMBER(3);

--

номер в году недели,

7

 

 

--

к которой относится

8

 

 

--

анализируемая дата (42)

9

 

first_dom_wn NUMBER(3);

--

номер в году недели, к которой относится

10

 

 

--

первый день месяца, к которому относится

11

 

 

--

анализируемая дата (39)

12

 

current_doy NUMBER(5);

--

номер дня в году (294)

13

 

first_dom_doy NUMBER(5); --

номер в году первого дня месяца, к

14

 

 

--

которому относится анализируемая дата (275)

15

 

 

 

 

16real_iso_week_of_month_start NUMBER(3);

17-- ISO-номер в году недели, к которой относится первое число месяца,

18-- к которому относится анализируемая дата (39), для первых чисел

19-- января будет 0, а не 52 или 53, как это вычисляет Oracle

20

21real_iso_week_of_this_date NUMBER(3);

22-- ISO-номер в году недели, к которой относится анализируемая дата (42),

23-- для первых чисел января будет 0, а не 52 или 53, как это

24-- вычисляет Oracle

25BEGIN

26day_number := date_var - NEXT_DAY(date_var - 8, 'MON') + 1;

27-- day_number := 2016.10.20 - NEXT_DAY(2016.10.20 - 8, 'MON') + 1 =

28

 

--

2016.10.20

-

NEXT_DAY(2016.10.12, 'MON') + 1

29

 

--

2016.10.20

-

2016.10.17 + 1 = 4

30

 

 

 

 

 

31current_wn := TO_NUMBER(TO_CHAR(date_var, 'IW')); -- 42

32first_dom_wn := TO_NUMBER(TO_CHAR(TRUNC(date_var, 'mm'), 'IW')); -- 39

33-- first_dom_wn := TO_NUMBER(TO_CHAR(TRUNC(2016.10.20, 'mm'), 'IW')) =

34

 

--

TO_NUMBER(TO_CHAR(2016.10.01, 'IW')) = 39

35

 

 

 

36current_doy := TO_NUMBER(TO_CHAR(date_var, 'DDD')); -- 294

37first_dom_doy := TO_NUMBER(TO_CHAR(TRUNC(date_var, 'mm'), 'DDD'));

38-- first_dom_doy := TO_NUMBER(TO_CHAR(TRUNC(2016.10.20, 'mm'), 'DDD')) =

39

 

--

TO_NUMBER(TO_CHAR(2016.10.01, 'DDD')) = 275

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

Источник: https://studfile.net/preview/16418462/