Заполнение пропущенных дат последним значением для пользователя в PostgreSQL - PullRequest
0 голосов
/ 27 февраля 2019

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

| id | date       | user_id | hours |
| 1  | 2019-01-27 | 1       | 4     |
| 2  | 2019-02-01 | 1       | 8     |
| 3  | 2018-06-30 | 2       | 5     |
| 4  | 2018-07-02 | 2       | 8     |

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

Например Я хотел бы знать часы для каждого пользователя и день между 2018-01-01 и 2019-02-28 , который будет

| id  | date       | user_id | hours |
| ..  | 2018-01-27 | 1       | 4     |
| ..  | 2018-01-28 | 1       | 4     |
| ..  | 2018-01-29 | 1       | 4     |
| ..  | 2018-01-30 | 1       | 4     |
| ..  | 2018-01-31 | 1       | 4     |
| ..  | 2019-02-01 | 1       | 8     |
| ..  | 2019-02-02 | 1       | 8     |
| ..  | 2019-02-03 | 1       | 8     |
| ..  | 2019-02-04 | 1       | 8     |
           ...
| ..  | 2018-06-30 | 2       | 5     |
| ..  | 2018-07-01 | 2       | 5     |
| ..  | 2018-07-02 | 2       | 8     |
| ..  | 2018-07-03 | 2       | 8     |
           ...

Я не знаю, как заполнить пробелы , как я бы это описал.Я думал о создании таблицы с полными датами между 1900 и 2100 годами, но не могу придумать, как заполнить пробелы с помощью таблицы дат.

Я читал о generate_seriesЯ попытался объединить данные различными способами, а также попытался использовать оконные функции PostgresSQL.Но я не знаю, как.

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

SELECT user_id, d.date, minutes

    FROM day d

    JOIN dayload dl

    ON dl.date = (
        SELECT MAX(date) from DAYLOAD where date <= d.date
    )
    order by d.date;

Я присоединил пользовательскую таблицу и т. Д. К этому отношению, но когда я применяю фильтрацию диапазона дат к запросу, те строки, которые имеют самую последнюю дневную нагрузку за пределамидиапазон дат.

Ответы [ 3 ]

0 голосов
/ 27 февраля 2019

Итак, мы немного поиграли и придумали следующий запрос, который, я думаю, выполнит то, что вы хотите:

with
    __users as(
        select distinct
            user_id
        from
            dayload
    )
select
    row_number() over(order by __users.user_id asc, gs.date asc) as id,
    gs.date::date,
    __users.user_id,
    coalesce(dayload.hours, max(hours) over(partition by __users.user_id order by gs.date asc), 0) as hours
from
    generate_series('2018-01-01'::date, '2019-02-28'::date, interval '1 day') as gs("date")
    cross join __users
    left join dayload using(date, user_id)
order by
    __users.user_id asc,
    gs.date asc;

Объяснение запроса:

with
    __users as(
        select distinct
            user_id
        from
            dayload
    )

Это называется CTE, или c ommon t способны e xpression, упрощенное объяснение этого состоит в том, чтобы сказать, что это в основном встроенная временная таблицав данном контексте.Будьте осторожны, используя их, так как они хранятся исключительно в памяти, поэтому большие объемы возвращаемых данных могут вызвать чрезмерное разбиение на страницы, приводя вашу базу данных к обходу.

generate_series('2018-01-01'::date, '2019-02-28'::date, interval '1 day') as gs("date")

Это создает пустые даты между первым и вторым параметрами, передаваемыми вЗдесь вы определяете диапазон дат, по которому вы хотите выполнить запрос.

coalesce(dayload.hours, max(hours) over(partition by user_id order by date asc), 0) as hours

Это выборка часов в текущей строке, к которой мы присоединились в дневной загрузке.Если это значение равно NULL, то он выбирает наибольшие часы из дневной нагрузки, к которым присоединились предыдущие строки.Если это значение равно нулю, возвращается 0.

generate_series('2018-01-01'::date, '2019-02-28'::date, interval '1 day') as gs("date")
cross join __users
left join dayload using(date, user_id)

Сначала получает каждую дату между '2018-01-01' :: date и '2019-02-28' :: date, затем он пересекает соединенияна наш CTE ранее.

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

После перекрестного соединения (с указанием каждой даты и каждого идентификатора пользователя) мы оставили присоединение к дневной загрузке.

0 голосов
/ 27 февраля 2019

Я думаю, что это то, что вы хотите:

select generate_series(date,
                       lead(date, 1, current_date) over (partition by user_id order by date) - interval '1 day',
                       interval '1 day'
                      ) as date,
       user_id, hours
from (values (1, '2019-01-27'::date, 1, 4),
             (2, '2019-02-01'::date, 1, 8),
             (3, '2018-06-30'::date, 2, 5)
     ) v(id, date, user_id, hours);

Это "простое" приложение generate_series().lead() получает следующую дату для пользователя.Сложность с вычитанием одного дня и все такое, чтобы не было совпадений по дням.

Здесь - это db <> скрипка.

0 голосов
/ 27 февраля 2019

Таким образом, похоже, что ключевая вещь здесь - это построение взаимосвязи между фактической датой и предыдущей измененной датой (назовем это целевой датой).Мои два цента строят вспомогательную таблицу, которая имеет два столбца: фактическую дату и целевую дату.Начните с заполнения вспомогательной таблицы фактическими датами, а целевую дату можно оставить пустым.Затем используйте запрос на обновление, чтобы заполнить целевые даты:

update HelperTable set TargetDate = 
(select Date from YourOriginalTable where 
HelperTable.ActualDate >= YourOriginalTable.Date 
order by YourOriginalTable.Date desc limit 1)

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

...