Как получить год с дробной частью от даты в Oracle?

Мне нужна функция PL/SQL, которая принимает дату и возвращает год в виде числа, включая дробную часть дней. Например, 1995-01-01 будет 1995.000, а 2015-11-24 будет 2015.897. Вот что мне удалось произвести:

CREATE OR REPLACE FUNCTION year_fraction (in_date DATE)
RETURN NUMBER
IS
  year NUMBER;
  days NUMBER;
  total_days NUMBER;
BEGIN
  year := EXTRACT(YEAR FROM in_date);
  days := in_date - TRUNC(in_date, 'YEAR');
  total_days := ADD_MONTHS(TRUNC(in_date, 'YEAR'), 12) - TRUNC(in_date, 'YEAR');
  RETURN year + days / total_days;
END year_fraction;

Это работает, но кажется, что нужно много работать, чтобы решить довольно простую проблему. Можно ли это сделать более аккуратно?


person Anders    schedule 24.11.2015    source источник
comment
вы можете получить день года с помощью to_number(to_char(in_date, 'ddd'))   -  person pablomatico    schedule 24.11.2015
comment
Вы можете получить день в виде числа с помощью extract(day from in_date), но я думаю, что это настолько просто, насколько это возможно, но при этом понятно, какие шаги нужно выполнить.   -  person David Aldridge    schedule 24.11.2015
comment
@DavidAldridge Это дает мне день месяца, а не день года.   -  person Anders    schedule 24.11.2015
comment
О, так оно и есть. Что ж, я думаю, у тебя все хорошо — иди с тем, что у тебя есть. Если вы беспокоитесь о производительности, изучите кэширование результатов функции.   -  person David Aldridge    schedule 25.11.2015


Ответы (2)


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

SQL> SELECT EXTRACT(YEAR FROM SYSDATE)
  2    ||'.'
  3    ||TRUNC(TO_NUMBER(TO_CHAR(SYSDATE, 'DDD'))/
  4    (add_months(TRUNC(SYSDATE,'year'), 12) - TRUNC(SYSDATE,'year'))*1000) my_format
  5  FROM DUAL;

MY_FORMAT
--------------------------------------------------------------------------------
2015.898
person Lalit Kumar B    schedule 24.11.2015
comment
@Anders Хорошо, смотрите обновленный ответ. Если быть точным, это будет 89.8%, а не 89.7. Пожалуйста, отметьте это как ответ, это поможет и другим! - person Lalit Kumar B; 24.11.2015
comment
Моя цель здесь не в том, чтобы избавиться от PL/SQL, а в том, чтобы сделать преобразование чище и читабельнее. Для меня это очень похоже на решение, которое я уже предложил в своем вопросе, но с использованием конкатенации строк вместо сложения чисел для объединения целых и дробных частей. - person Anders; 24.11.2015
comment
Я думаю, что более аккуратным и лучшим способом было сделать это за один шаг на чистом SQL. Что там в ответе, - person Lalit Kumar B; 24.11.2015

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

CREATE OR REPLACE FUNCTION fractional_year (in_date DATE)
RETURN NUMBER
IS
  year DATE;
BEGIN
  year := TRUNC(in_date, 'YEAR');
  -- Year + Days passed so far / Total number of days in year
  RETURN EXTRACT(YEAR FROM year) + (in_date - year) / (ADD_MONTHS(year, 12) - year);
END fractional_year;
person Anders    schedule 26.11.2015