TO_CHAR
зависит от настроек NLS, включая территорию и язык. Если вы хотите выполнить расчет независимо от настроек NLS, вы можете использовать тот факт, что неделя ISO всегда начинается в понедельник, и рассчитать разницу в количестве дней между первым днем месяца и началом (понедельником) ISO. неделя, содержащая первый день месяца:
WITH months ( first_day ) AS (
SELECT CASE LEVEL
WHEN 1 THEN TRUNC( SYSDATE, 'YYYY' ) + INTERVAL '1' DAY
ELSE ADD_MONTHS( TRUNC( SYSDATE, 'YYYY' ), LEVEL - 1 )
END
FROM DUAL
CONNECT BY LEVEL <= 12
)
SELECT first_day
+ CASE first_day - TRUNC( first_day, 'IW' )
WHEN 5 THEN 2 -- Saturday
WHEN 6 THEN 1 -- Sunday
ELSE 0 -- Weekday
END
AS first_business_day
FROM months;
Что выводит:
| FIRST_BUSINESS_DAY |
| :----------------- |
| 2020-01-02 (THU) |
| 2020-02-03 (MON) |
| 2020-03-02 (MON) |
| 2020-04-01 (WED) |
| 2020-05-01 (FRI) |
| 2020-06-01 (MON) |
| 2020-07-01 (WED) |
| 2020-08-03 (MON) |
| 2020-09-01 (TUE) |
| 2020-10-01 (THU) |
| 2020-11-02 (MON) |
| 2020-12-01 (TUE) |
db <> скрипка здесь
Есть идеи, что я сделал неправильно?
Запрос слишком сложный. Вы генерируете все дни года в условии факторинга подзапроса (WITH
), когда вас интересуют только первые дни месяца, а это означает, что во второй части запроса вы заменяете каждый день на первое число месяца с использованием:
ADD_MONTHS (LAST_DAY (mydate) + 1, -1)
Что можно упростить до:
TRUNC( mydate, 'MM' )
Или, если вы только что сгенерировали в инкрементных месяцах, чтобы начать с, вы бы даже не нужно усечь, так как у вас будут только строки для первого дня месяца:
WITH months ( first_day ) AS (
SELECT ADD_MONTHS( TRUNC( SYSDATE, 'YYYY' ), LEVEL - 1 )
FROM DUAL
CONNECT BY LEVEL <= 12
)
Но, оставив это в стороне, ваш оператор add_days
проверяет, является ли дата первым месяца, а затем применяется два различных набора логик c дней, которые являются или не являются первыми месяца:
Этот раздел вашего запроса можно записать проще:
CASE
WHEN TO_CHAR (mydate, 'DD.MM') = '01.01'
THEN CASE TO_CHAR( TRUNC( mydate, 'MM' ), 'fmD' )
WHEN '7' THEN 2
WHEN '6' THEN 3
ELSE 1
END
ELSE CASE TO_CHAR( TRUNC( mydate, 'MM' ), 'fmD' )
WHEN '7' THEN 2
WHEN '1' THEN 1 -- Different to the above
ELSE 0 -- Again, different.
END
END AS add_days
You будет добавлено различное количество дней в зависимости от того, является ли введенная дата первым днем месяца или нет, поэтому в течение нескольких месяцев вы получаете повторяющиеся строки.
все должно (опять же, игнорируя, что вы генерируете слишком много строк) быть примерно таким:
WITH dateparam ( mydate ) AS (
SELECT TRUNC(SYSDATE, 'YYYY') + LEVEL - 1
FROM DUAL
CONNECT BY LEVEL <= ADD_MONTHS( TRUNC( SYSDATE, 'YYYY' ), 12 ) - TRUNC( SYSDATE, 'YYYY' )
)
SELECT DISTINCT
TRUNC( mydate, 'MM' ) bd_date,
TO_CHAR( TRUNC( mydate, 'MM' ) , 'fmD') day_num,
CASE TO_CHAR( TRUNC( mydate, 'MM' ), 'fmD')
WHEN '7' THEN 2
WHEN '1' THEN 1
ELSE 0
END add_days,
TRUNC( mydate, 'MM' )
+ CASE TO_CHAR( TRUNC( mydate, 'MM' ), 'fmD')
WHEN '7' THEN 2
WHEN '1' THEN 1
ELSE 0
END date_calc
FROM dateparam
ORDER BY 1;
, который будет работать на любой территории, где первый день недели - воскресенье ... который невелик полосы мира. В других местах ваш запрос даст неправильный ответ, и вместо этого, если понедельник является первым днем недели, смена воскресенья и понедельника на вторник.
Например:
ALTER SESSION SET NLS_TERRITORY = 'America';
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD "("DY")"';
и приведенный выше запрос выводит:
BD_DATE | DAY_NUM | ADD_DAYS | DATE_CALC
:--------------- | :------ | -------: | :---------------
2020-01-01 (WED) | 4 | 0 | 2020-01-01 (WED)
2020-02-01 (SAT) | 7 | 2 | 2020-02-03 (MON)
2020-03-01 (SUN) | 1 | 1 | 2020-03-02 (MON)
2020-04-01 (WED) | 4 | 0 | 2020-04-01 (WED)
2020-05-01 (FRI) | 6 | 0 | 2020-05-01 (FRI)
2020-06-01 (MON) | 2 | 0 | 2020-06-01 (MON)
2020-07-01 (WED) | 4 | 0 | 2020-07-01 (WED)
2020-08-01 (SAT) | 7 | 2 | 2020-08-03 (MON)
2020-09-01 (TUE) | 3 | 0 | 2020-09-01 (TUE)
2020-10-01 (THU) | 5 | 0 | 2020-10-01 (THU)
2020-11-01 (SUN) | 1 | 1 | 2020-11-02 (MON)
2020-12-01 (TUE) | 3 | 0 | 2020-12-01 (TUE)
но:
ALTER SESSION SET NLS_TERRITORY = 'France';
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD "("DY")"';
и выводит:
BD_DATE | DAY_NUM | ADD_DAYS | DATE_CALC
:--------------- | :------ | -------: | :---------------
2020-01-01 (WED) | 3 | 0 | 2020-01-01 (WED)
2020-02-01 (SAT) | 6 | 0 | 2020-02-01 (SAT)
2020-03-01 (SUN) | 7 | 2 | 2020-03-03 (TUE)
2020-04-01 (WED) | 3 | 0 | 2020-04-01 (WED)
2020-05-01 (FRI) | 5 | 0 | 2020-05-01 (FRI)
2020-06-01 (MON) | 1 | 1 | 2020-06-02 (TUE)
2020-07-01 (WED) | 3 | 0 | 2020-07-01 (WED)
2020-08-01 (SAT) | 6 | 0 | 2020-08-01 (SAT)
2020-09-01 (TUE) | 2 | 0 | 2020-09-01 (TUE)
2020-10-01 (THU) | 4 | 0 | 2020-10-01 (THU)
2020-11-01 (SUN) | 7 | 2 | 2020-11-03 (TUE)
2020-12-01 (TUE) | 2 | 0 | 2020-12-01 (TUE)
db <> fiddle здесь
Необходимо либо убедиться, что параметр NLS_TERRITORY
никогда не изменится (однако, ANY пользователь может установите его на любое значение, которое они хотят в своем сеансе в любое время) или используйте что-то, что agnosti c, к настройкам NLS (как мой пример вверху). Вы не можете исправить запрос с моделью формата D
, используя третий аргумент TO_CHAR
, поскольку он позволяет только установить NLS_DATE_LANGUAGE
(который не действует в первый день недели) и не принимает NLS_TERRITORY
(который контролирует первый день недели), но вы можете использовать модель формата DY
, если вы действительно зациклены на использовании TO_CHAR
. Т.е.:
CASE TO_CHAR( TRUNC( mydate, 'MM' ), 'DY', 'NLS_DATE_LANGUAGE=American' )
WHEN 'SAT' THEN 2
WHEN 'SUN' THEN 1
ELSE 0
END add_days