Выберите активных сотрудников в месяц с датами в формате дд / мм / гггг

Мне сложно объяснить это в письменной форме, поэтому проявите терпение.

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

Итак, если у меня есть сотрудник, который проработал 4 месяца, например. с 01.01.2013 по 05.01.2013 я его получу через четыре месяца. Мне нужно, чтобы он появлялся в 4 таблицах (по одной на каждый активный месяц) с другими сотрудниками, которые активны в течение этих месяцев. В данном случае это будут: январь, февраль, март и апрель 2013 года.

Проблема в том, что я понятия не имею, как сделать здесь запрос или обработать PHP для этого.

Все, что я могу придумать, это что-то вроде (я бы запускал этот запрос для каждого месяца, передавая год и месяц в качестве аргумента)

pg_query= "SELECT employee_name FROM employees
           WHERE month_and_year between start_date AND finish_date"

Но это невозможно сделать, главным образом потому, что month_and_year должен быть столбцом, а не переменной.
Кто-нибудь придумает?

ОБНОВЛЕНИЕ

Да, мне очень жаль, что я забыл сказать, что использовал DATE в качестве типа данных.

Самым простым решением, которое я нашел, было использование ЭКСТРАКТА

select * from employees where extract (year FROM start_date)>='2013'
AND extract (month FROM start_date)='06' AND extract (month FROM finish_date)<='07'

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


person Jesus Rodriguez    schedule 04.07.2013    source источник
comment
Пожалуйста, подтвердите тип данных в полях даты начала и окончания. Это свидание или знак?   -  person Dan Bracuk    schedule 04.07.2013
comment
В общем, укажите свою версию Postgres и определение таблицы.   -  person Erwin Brandstetter    schedule 05.07.2013
comment
Представленное вами решение будет очень медленным с большими таблицами, поскольку условия WHERE не подлежат маржированию. Я бы рассмотрел либо свой ответ, либо ответ Клодоальдо. (Не Гордона, который также не работает в этом отношении.) Протестируйте с помощью EXPLAIN ANALYZE < / a> чтобы убедиться.   -  person Erwin Brandstetter    schedule 08.07.2013
comment
Я согласен. мое решение далеко от оптимального, поэтому я отметил Клодоальдо как правильный   -  person Jesus Rodriguez    schedule 09.07.2013


Ответы (3)


Нет необходимости создавать диапазон для перекрытия:

select to_char(d, 'YYYY-MM') as "Month", e.name
from
    (
        select generate_series(
            '2013-01-01'::date, '2013-05-01', '1 month'
        )::date
    ) s(d)
    inner join
    employee e on
        date_trunc('month', e.start_date)::date <= s.d
        and coalesce(e.finish_date, 'infinity') > s.d
order by 1, 2

SQL Fiddle

Если вы хотите, чтобы отображались месяцы без активных сотрудников, измените inner на left join


Эрвин, о вашем комментарии:

второе выражение должно быть coalesce(e.finish_date, 'infinity') >= s.d

Обратите внимание на требование:

Итак, если у меня есть сотрудник, проработавший 4 месяца, например. с 01.01.2013 по 05.01.2013 он будет через четыре месяца

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

Если я воспользуюсь вашим "исправлением", я включу сотрудника f в месяц 05 из моего примера. Он закончил в 2013-05-01:

('f', '2013-04-17', '2013-05-01'),

SQL Fiddle с вашим исправлением

person Clodoaldo Neto    schedule 04.07.2013
comment
Диапазон & OVERLAPS это более элегантное решение, которое хорошо работает в сочетании с предложенным индексом - в отличие от этого запроса, для которого потребуется более специализированный индекс с двумя функциональными выражениями. Это также неверно: второе выражение должно быть coalesce(e.finish_date, 'infinity') >= s.d. А еще лучше сделать это: ON e.start_date < s.d + interval '1 month' AND coalesce(e.finish_date, 'infinity') >= s.d. Это было бы еще одно хорошее решение, как указано в соответствующем ответе. - person Erwin Brandstetter; 05.07.2013
comment
@Erwin Я не считаю более сложным, поскольку создание диапазона для использования оператора перекрытия более элегантно. На самом деле я вижу элегантность только в простоте. Ваш индекс с единственным функциональным выражением лучше, но не потому, что он менее специализирован, поскольку вы уже потеряли общность с одним единственным функциональным выражением, а потому, что функциональное выражение является вторым термином, что означает, что он потерял только некоторую общность, поскольку первый термин по-прежнему будет обычно полезный. И да, и здесь это применимо, это проще и элегантнее. - person Clodoaldo Neto; 05.07.2013
comment
Хорошо, я вижу. > или >= действительно зависит от значения базовых данных: должен ли последний день диапазона быть включен или исключен. OVERLAPS по умолчанию включает нижнюю границу и исключает верхнюю. - person Erwin Brandstetter; 05.07.2013

Предполагая, что вы действительно не храните даты в виде символьных строк, а только выводите их таким образом, вы можете:

SELECT employee_name
FROM employees
WHERE start_date <= <last date of month> and
      (finish_date >= <first date of month> or finish_date is null)

Если вы храните их в этом формате, вы можете немного поиграть с годами и месяцами.
Эта версия превращает «даты» в строки вида «ГГГГММ». Просто укажите месяц, который вы хотите, вот так, и вы можете провести сравнение:

select employee_name
from employees e
where right(start_date, 4)||substr(start_date, 4, 2) <= 'YYYYMM' and
      (right(finish_date, 4)||substr(finish_date, 4, 2) >= 'YYYYMM' or finish_date is null)

ПРИМЕЧАНИЕ: выражение 'YYYYMM' означает месяц / год, который вы ищете.

person Gordon Linoff    schedule 04.07.2013

Во-первых, вы можете легко создать несколько интервалов дат с помощью _1 _ . Чтобы получить нижнюю и верхнюю границы, добавьте к началу интервал в 1 месяц:

SELECT g::date                       AS d_lower
    , (g + interval '1 month')::date AS d_upper
FROM  generate_series('2013-01-01'::date, '2013-04-01', '1 month') g;

Производит:

  d_lower   |  d_upper
------------+------------
 2013-01-01 | 2013-02-01
 2013-02-01 | 2013-03-01
 2013-03-01 | 2013-04-01
 2013-04-01 | 2013-05-01

Верхняя граница временного диапазона - первое число следующего месяца. Это специально, поскольку мы собираемся использовать стандартный SQL OVERLAPS оператор ниже. Цитата из руководства в указанном месте:

Считается, что каждый период времени представляет начало полуоткрытого интервала ‹= время‹ конец [...]

Затем вы используете LEFT [OUTER] JOIN для подключения сотрудников в эти диапазоны дат:

SELECT to_char(m.d_lower, 'YYYY-MM') AS month_and_year, e.*
FROM  (
   SELECT g::date                       AS d_lower
       , (g + interval '1 month')::date AS d_upper
   FROM   generate_series('2013-01-01'::date, '2013-04-01', '1 month') g
   ) m
LEFT   JOIN employees e ON (m.d_lower, m.d_upper)
                  OVERLAPS (e.start_date, COALESCE(e.finish_date, 'infinity'))
ORDER  BY 1;
  • LEFT JOIN включает диапазоны дат, даже если подходящих сотрудников не найдено.

  • Используйте COALESCE(e.finish_date, 'infinity')) для сотрудников без finish_date. Считается, что они до сих пор работают. Или, может быть, используйте current_date вместо infinity.

  • Используйте to_char(), чтобы получить красиво отформатированное значение month_and_year.

  • Вы можете легко выбрать любые нужные столбцы из employees. В моем примере я беру все столбцы с e. *.

  • 1 в ORDER BY 1 - это позиционный параметр для упрощения кода. Заказы по первому столбцу month_and_year.

  • Чтобы сделать это быстро, создайте многоколоночный индекс для этих выражения. Нравится

    CREATE INDEX employees_start_finish_idx
    ON employees (start_date, COALESCE(finish_date, 'infinity') DESC);
    

    Обратите внимание на порядок убывания во втором столбце индекса.

  • Если вы совершили глупость, сохранив временные данные в виде строковых типов (text или varchar) с шаблоном 'DD/MM/YYYY' вместо date или timestamp или timestamptz , преобразуйте строку в текущую дату с помощью to_date(). Пример:

    SELECT to_date('01/03/2013'::text, 'DD/MM/YYYY')
    

    Измените последнюю строку запроса на:

    ...
    OVERLAPS (to_date(e.start_date, 'DD/MM/YYYY')
             ,COALESCE(to_date(e.finish_date, 'DD/MM/YYYY'), 'infinity'))
    

    У вас даже может быть такой функциональный индекс. Но действительно вам следует использовать столбец date или timestamp.

person Erwin Brandstetter    schedule 04.07.2013
comment
Очень всеобъемлющий! Пара мелочей с образцом запроса: использование однобуквенных псевдонимов таблиц (что означает m?) И пропуск ключевого слова AS затрудняют отслеживание неспециалистами; некоторые лишние разрывы строк тоже не повредят. Я также не поклонник ORDER BY 1 для ясности (на первый взгляд это выглядит так, как будто вы заказываете по константе!), Но я знаю, что это больше вопрос мнения. - person IMSoP; 05.07.2013
comment
@IMSoP: Ну, у меня есть причины для каждого из этих синтаксических решений, но ответ и так достаточно длинный. Для ясности я предпочитаю короткие псевдонимы таблиц (m - месяц) и предпочитаю использовать позиционные параметры вместо повторения более длинных выражений. Я всегда включаю ключевое слово AS для псевдонимов столбцов (как рекомендовано в руководстве), но сокращаю шумовое слово для псевдонимов таблиц. Я вижу, что ваша точка зрения требует более подробного синтаксического стиля, но, как вы сказали, это вопрос вашего мнения или вкуса и стиля. Я предпочитаю меньше шума для ясности. - person Erwin Brandstetter; 05.07.2013
comment
Согласен с вами во всем, кроме псевдонимов таблиц. Псевдонимы подобны именам переменных, и так же, как $m - плохое имя для переменной PHP, m - плохой псевдоним для производной таблицы. e для employees не так уж и плохо, хотя в таком сложном запросе мне хотелось бы использовать emp для большей ясности (а не просто краткости). - person IMSoP; 05.07.2013
comment
@IMSoP: для ограниченного объема одного запроса (в отличие от более длинной функции PHP) я предпочитаю короткие псевдонимы таблиц. Если зверь крупнее (включая SQL-запросы), можно использовать более описательные идентификаторы ... - person Erwin Brandstetter; 05.07.2013