Выбор сотрудников с днями рождения в заданном диапазоне с использованием Oracle SQL - PullRequest
4 голосов
/ 25 марта 2012

Для выбора дней рождения между двумя месяцами, где FROMDATE и TODATE являются некоторыми параметрами в подготовленном утверждении, я вычислил что-то вроде этого:

select
  p.id as person_id,
   ...
   ...
  where e.active = 1
        and extract(month from TODATE) >= extract(month from e.dateOfBirth)
        and extract(month from e.dateOfBirth) >= extract(month from FROMDATE)
        order by extract(month from e.dateOfBirth) DESC,
              extract(day from e.dateOfBirth) DESC

Как это можно улучшить, чтобы работать и с днями?

Ответы [ 8 ]

2 голосов
/ 26 марта 2012

В Oracle существует несколько способов поиска диапазонов дат. Для вашего сценария я предлагаю вам превратить элементы месяца и дня всех задействованных дат в числа.

select
  p.id as person_id,
   ...
   ...
  where e.active = 1
  and to_number (to_char( e.dateOfBirth, 'MMDD')) 
      between to_number (to_char( FROMDATE, 'MMDD'))
              and to_number (to_char( TODATE, 'MMDD')) 
  order by extract(month from e.dateOfBirth) DESC,
          extract(day from e.dateOfBirth) DESC

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


@ AdeelAnsari комментарии:

"Мне не нравится to_char предикат, чтобы использовать Индекс "

Какой индекс? Обычный индекс на dateOfBirth не будет полезен, потому что записи индекса будут начинаться с элемента year. Так что это не поможет вам найти все записи людей, родившихся любого 23 декабря.

Индекс на основе функций - или в 11g виртуальный столбец с индексом (в основном то же самое) - это единственный способ индексации частей столбца даты.

1 голос
/ 17 декабря 2016

Это запрос, который я использую для дат рождения в следующие 20 дней:

SELECT A.BIRTHDATE, 
    CEIL(ABS (MONTHS_BETWEEN(A.BIRTHDATE, SYSDATE) / 12)) AGE_NOW,  
    CEIL(ABS (MONTHS_BETWEEN(A.BIRTHDATE, SYSDATE + 20) / 12)) AGE_IN_20_DAYS
FROM USERS A
WHERE 
    CEIL(ABS (MONTHS_BETWEEN(A.BIRTHDATE, SYSDATE) / 12)) <> CEIL(ABS (MONTHS_BETWEEN(A.BIRTHDATE, SYSDATE + 20) / 12));
  • ABS (MONTHS_BETWEEN(A.BIRTHDATE, SYSDATE) / 12)) вернуть возраст в формате 38,9, 27,2 и т. Д.
  • Применение ceil() даст нам разницу в годах, которую мы должны определить, если этот человек близок к дате рождения. Например.

    1. Возраст: 29,9
    2. 29,9 + (20 дней) = 30,2
    3. ceil (30,1) - ceil (29,9) = 1

Это результат запроса на december 16:

BIRTHDATE    AGE_NOW    AGE_IN_20_DAYS

12/29/1981    35           36
12/29/1967    49           50
1/3/1973      44           45
1/4/1968      49           50
1 голос
/ 25 марта 2012

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

Самый простой и быстрый способ сделать это - сохранить второе поле данных о рождении, но «без» года. Я помещаю цитаты вокруг «без», потому что у даты не может быть года. Так что вместо этого вы просто основываетесь на другом году.

Повторное датирование каждого DoB к 2000 году - мой хороший выбор. Потому что он включает високосный год и является хорошим круглым числом. Каждый DoB и FromDate и ToDate будут работать в 2000 году ...

WHERE
      DoB2000 >= FromDate
  AND DoB2000 <= ToDate

(Предполагается, что вы также индексируете новое поле для ускорения поиска, в противном случае вы все равно получаете сканирование, хотя оно МОЖЕТ быть быстрее, чем следующая альтернатива.)

1012 *
*

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

WHERE
  (   EXTRACT(month FROM e.DateOfBirth) > EXTRACT(month FROM FromDate)
      OR  (      EXTRACT(month FROM e.DateOfBirth)  = EXTRACT(month FROM FromDate)
             AND EXTRACT(day   FROM e.DateOfBirth) >= EXTRACT(day   FROM FromDate)
          )
  )
  AND
  (   EXTRACT(month FROM e.DateOfBirth) < EXTRACT(month FROM ToDate)
      OR  (      EXTRACT(month FROM e.DateOfBirth)  = EXTRACT(month FROM ToDate)
             AND EXTRACT(day   FROM e.DateOfBirth) <= EXTRACT(day   FROM ToDate)
          )
  )
1 голос
/ 25 марта 2012

вы можете использовать

SELECT * FROM mytbale
where dateofbirth between start_dt and end_dt

альтернатива:

вы можете конвертировать даты в день года, используя:

to_char( thedate, 'ddd' )

, затем проверьтедиапазон (обратите внимание, что это та же проблема, что и в ответе @Dems, когда вы не должны охватывать конец года, как в период с 25 декабря по 10 января.)

0 голосов
/ 19 октября 2015

Это решение !!!

SELECT                                  
    *                           
FROM                                    
    PROFILE  prof                          
where                        
      to_date(to_char(prof.BIRTHDATE, 'DDMM'), 'DDMM') BETWEEN sysdate AND sysdate+5

или

add_months(to_date(to_char(prof.BIRTHDATE, 'DDMM'), 'DDMM'),12) BETWEEN sysdate AND sysdate+5  
0 голосов
/ 25 июня 2014

Ребята У меня есть более простое решение этой проблемы

  • шаг 1. преобразовать месяц в число,
  • шаг 2. объединить день (из двух цифр) с месяцем
  • шаг 3. затем преобразуйте результат в число, выполнив to_number (результат)
  • шаг 4. Как только у вас есть это для даты начала и окончания, сделайте для него функцию между иВы сделали.

пример кода:

SELECT      date_of_birth            
       FROM xxxtable
       where to_number(to_number(to_char(to_date(substr(date_of_birth,4,3),'mon'),'mm'))||substr(date_of_birth,1,2)) between
        to_number(to_number(to_char(to_date(substr(:start_date_variable,4,3),'mon'),'mm'))||(substr(:start_date_variable,1,2)))  and  
        to_number(to_number(to_char(to_date(substr(:end_date_variable,4,3),'mon'),'mm'))||(substr(:end_date_variable,1,2)));
0 голосов
/ 31 марта 2012

В конце мы выбрали другое решение для мусора, где мы добавили первую дату создания годовщины:

where 
...
and (to_char(sysdate,'yyyy') - to_char(e.dateofbirth,'yyyy')) > 0
and add_months(e.dateofbirth,
              (to_char(sysdate,'yyyy') - to_char(e.dateofbirth,'yyyy')) * 12)
              >:fromDate:
and :toDate: > add_months(e.dateofbirth,
              (to_char(sysdate,'yyyy') - to_char(e.dateofbirth,'yyyy')) * 12)
order by extract(month from e.dateofbirth) DESC,
              extract(day from e.dateofbirth) DESC)
0 голосов
/ 25 марта 2012

Я не знаю, как это ведет себя с точки зрения производительности, но я бы попробовал использовать обычное вычитание даты. Скажем, например, вы хотите дни рождения между 1 января и 1 июля. Любой, кто между 25 и 25,5 будет квалифицирован. То есть, любой, чей неполный возраст составляет менее 1/2 года, будет иметь право. Математика проста в 1/2, но она одинакова независимо от окна. Другими словами, «в течение одного месяца» означает +/- 1/12 года, в течение 1 дня - +/- 1/365 года и т. Д.

Год не имеет значения в этом методе, поэтому я буду использовать текущий год при создании переменной.

Кроме того, я бы подумал о центре диапазона, а затем сделал бы абсолютные значения (или это, или всегда использовал будущую дату).

Например

select personid
from mytable
where abs(mod(dob - target_birth_date)) < 1/52

даст вам всех с днем ​​рождения в течение недели.

Я понимаю, что этот код - всего лишь псевдокод, но, похоже, он позволяет вам это делать, и вы все равно можете использовать индексы, если немного его подправили. Просто пытаюсь мыслить нестандартно.

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