Проверка дня недели по имени в Oracle

Учитывая строковое представление дня недели (например, Monday, Mardi, الثلاثاء и т. д.) и NLS_DATE_LANGUAGE, есть ли способ < em>проверка того, что этот день недели действителен?

Почему это сложно спросите вы? Что ж, если бы это был просто английский язык, очевидно, что нужно было бы сделать что-то вроде следующего:

if <day_variable> in ('Monday','Tuesday', ...) then
   ...

Я пытаюсь сделать это расширяемо для нескольких стран, и, поскольку я не знаю (и не могу беспокоиться), выписывать все дни недели для всех текущих и будущих NLS_DATE_LANGUAGE, это не вариант .

Следующий вариант — явный TO_DATE(), который прекрасно работает с пятницей — я подозреваю, что это совпадение, потому что сейчас май, но с треском проваливается с остальной частью недели:

SQL> select to_date('Friday', 'Day') as d from dual;

D
----------
2015-05-01

SQL> select to_date('Monday', 'Day') as d from dual;
select to_date('Monday', 'Day') as d from dual
               *
ERROR at line 1:
ORA-01835: day of week conflicts with Julian date

Это интересная ошибка... Oracle рекомендует:

Удалите значение дня недели из спецификации даты или введите правильный день недели для даты по юлианскому календарю.

Удаление дня недели на самом деле не вариант, поскольку это все, что меня интересует, и у меня нет неправильного дня недели для моей даты по юлианскому календарю, потому что у меня нет даты по юлианскому календарю.

Обходной путь, предложенный в другом месте подтвердить и использовать альтернативные форматы даты (Dy, dy, FMDy и т. д.), очевидно, приводит к той же ошибке.

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

Как я могу проверить правильность названного дня в Oracle?


person Ben    schedule 05.05.2015    source источник
comment
P.S. Я сам ответил на это, но это никоим образом не означает, что это лучший ответ. Я ищу что-то лучшее (то есть встроенное и только в SQL), если это вообще возможно.   -  person Ben    schedule 05.05.2015


Ответы (3)


Насколько я могу судить, я застрял с запуском чего-то вроде следующего каждый раз, когда мы исправляем БД (конечно, не забудем это сделать), чтобы сгенерировать все возможные дни

select value as language
     , to_char(sysdate + l, 'FMDay', 'nls_date_language=''' || value || '') as name_of_day
  from v$nls_valid_values
 cross join ( select level as l from dual connect by level <= 7 )
 where parameter = 'LANGUAGE'
   and isdeprecated = 'FALSE'
 order by language

Выполнение этого не было бы вообще расширяемым, как если бы мне нужен был день недели (т.е. 1, 2, 3 и т. д.), значение на основе текущей NLS_TERRITORY, что означает невозможность согласованности.

to_char(sysdate + l, 'D', 'nls_date_language=''' || value || '') as day_of_week

Затем мне пришлось бы создать недетерминированную функцию :-(, чтобы проверить, является ли строка (не) правильной:

create or replace function is_day_of_week (
    PDay in varchar2
  , PDate_Language
    ) return number is

   l_ct number;

begin

   select count(*) into l_ct
     from days_of_the_week
    where name_of_day = PDay
      and language = PDate_Language;

   return l_ct;

end is_day_of_week;
person Ben    schedule 05.05.2015
comment
Что такое days_of_the_week? Это представление, которое вы создали на основе вашего первого запроса? - person Frank Schmitt; 05.05.2015

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

Таким образом, чтобы обойти ошибку, объедините строковые представления недели, начинающиеся с sysdate, и проверьте свою строку на соответствие результату:

SELECT CASE INSTR(
               to_char ( sysdate + 0, 'Day, DD.MM.YYYY' )   
            || to_char ( sysdate + 1, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 2, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 3, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 4, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 5, 'Day, DD.MM.YYYY' )
            || to_char ( sysdate + 6, 'Day, DD.MM.YYYY' )
          , '<the_string_to_test>' )
         WHEN 0 THEN 'invalid'
         ELSE        'valid'
       END  isvalid
  FROM DUAL
     ;

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

SELECT CASE INSTR(
               to_char ( sysdate + 0, '.FMDay., DD.MM.YYYY' )   
            || to_char ( sysdate + 1, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 2, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 3, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 4, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 5, '.FMDay., DD.MM.YYYY' )
            || to_char ( sysdate + 6, '.FMDay., DD.MM.YYYY' )
          , '.' || '<the_string_to_test>' || '.')
         WHEN 0 THEN 'invalid'
         ELSE        'valid'
       END  isvalid
  FROM DUAL
     ;

Правка/Примечание

Обратите внимание, что в последнем примере...

  • Не все разделители работают (проверено с . [работает] и | [не работает])
  • FMDay необходимо использовать в качестве спецификатора формата для обрезки пробелов в конце названий дней недели.

Редактировать №2

Вот то же самое (почти) без необходимости строковых операций:

select 
  case when upper(:day_name) in 
  (
    select to_char(sysdate + 0, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 1, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 2, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 3, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 4, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 5, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual union all
    select to_char(sysdate + 6, 'FMDAY', 'NLS_DATE_LANGUAGE=''' || :nls_date_lang || '''') from dual
  ) then 'valid' else 'invalid'
  end  isvalid
from dual;
person collapsar    schedule 05.05.2015
comment
Это отличное и простое решение. Как показано в моем ответе, на немецком Sonnabend это не сработает, но, тем не менее, это отличное решение. Я возьму на себя смелость отредактировать ваш ответ и добавить альтернативу, основанную на вашем запросе, поэтому создание отдельного ответа не будет оправданным. - person Thorsten Kettner; 05.05.2015
comment
Проблема в том, что нет никаких гарантий, что день недели одного языка не полностью содержится в другом. Например, индонезийский Jumat и узбекский Juma являются подстроками суахили Jumatano. Редактирование @Thorsten решает эту проблему, но все равно это будет ужасный запрос :-(. - person Ben; 05.05.2015
comment
@Ben Эта проблема с подстрокой решается моей второй версией (если только . не является частью действительного имени рабочего дня, чего не произойдет). Что касается вашего редактирования: вы профилировали и тестировали свое заявление? Я не мог заставить мою версию работать через sql plus, используя только спецификатор FMDAY, и объединение подзапросов могло потерять производительность. - person collapsar; 05.05.2015
comment
@Ben Должно быть, что-то упустил, твоя версия в основном работает. - person collapsar; 05.05.2015
comment
Я не редактировал его ... но подзапрос из 7 строк вообще не повлияет на производительность. - person Ben; 05.05.2015
comment
@ Бен Хорошо, извини, что ошибочно приписал это тебе. - person collapsar; 05.05.2015
comment
@Ben Вы правы в отношении производительности, влияние составляет менее 5% (данные собраны с использованием простого тестового сценария с использованием dbms_utility.get_time для 100000 итераций для каждого варианта). - person collapsar; 05.05.2015
comment
@collapsar: я не понимаю, почему вы вставили строковые операции в последний запрос. Прелесть вопроса в том, что они не нужны. Как вы думаете, что вы получите, найдя «.СУББОТА». вместо «СУББОТА» в массиве? - person Thorsten Kettner; 05.05.2015
comment
@ThorstenKettner Потому что у меня мертвый мозг ... ;-). Я все еще думал о решении на основе строк. Правка отменена. - person collapsar; 05.05.2015

Короткий ответ: вы не можете сделать это с Oracle.

Вы можете написать функцию PL/SQL следующим образом, используя цикл с NEXT DAY:

create or replace function is_valid_dayname(
  vi_dayname varchar2, 
  vi_nls_date_language varchar2) return integer 
as
  -- Oracle day name constants for NEXT_DAY
  type type_daynames is varray(7) of varchar2(100);
  v_daynames type_daynames := type_daynames('SUNDAY','MONDAY','TUESDAY','WEDNESDAY','THURSDAY','FRIDAY','SATURDAY');
  -- date variable for the INTO clause
  v_date date;
begin
  for i in 1 .. 7 loop
    begin 
      select to_date(vi_dayname || to_char(next_day(sysdate, v_daynames(i)), 'yyyymmdd', 'NLS_DATE_LANGUAGE=''' || vi_nls_date_language || ''''), 'day yyyymmdd') into v_date from dual;
      return 1; -- success
    exception when others then null;
    end;
  end loop;
  return 0; -- failure
end;

и используйте его так:

select is_valid_dayname('invalid dayname', 'GERMAN') from dual;

0

select is_valid_dayname('samstag', 'GERMAN') from dual;

1

select is_valid_dayname('SAMSTAG', 'GERMAN') from dual;

1

select is_valid_dayname('Samstag', 'GERMAN') from dual;

1

select is_valid_dayname('Sonnabend', 'GERMAN') from dual;

0

К сожалению, «Sonnabend» — это синоним «Samstag» на немецком языке, но Oracle этого не знает. Итак, мы приближаемся к решению, но оно не является полным.

person Thorsten Kettner    schedule 05.05.2015
comment
Я не думаю, что это хорошая идея считать Sonnabend действительным днем. Дни недели определяются ISO 8601 (и другими органами спецификации), и там 6-й день недели — Самстаг, ничего больше. - person Wernfried Domscheit; 05.05.2015
comment
@Wernfried: В Швейцарии используют Sonnabend? В Германии (по крайней мере, в той части, где я живу) и Sonnabend, и Samstag считаются действительными, и ни один из них не лучше другого (хотя Sonnabend, кажется, выходит из моды). Человек, который входит в Sonnabend и вместо этого просит ввести действительное название дня, был бы здесь весьма удивлен. Также было бы странно требовать от пользователя знания имен ISO. Но, может быть, здесь не задействован пользовательский ввод, тогда да, мы могли бы ограничиться именами ISO. - person Thorsten Kettner; 05.05.2015
comment
В Швейцарии мы говорим Samschtig (или, что еще хуже, Zischtig для вторника :-) - person Wernfried Domscheit; 05.05.2015