Рассчитать количество дней между сменой статуса

Я не уверен, как писать код, чтобы получить желаемый результат. Я надеюсь, что сообщество сможет указать мне правильное направление. Мне нужно создать отчет по таблице «История арендной платы», и я ожидаю, что отчет будет выглядеть как окончательный результат. Подробности смотрите на изображении. В таблице «История аренды» есть три тега. Мне удалось использовать ROW_NUMBER() OVER (PARTITION BY), чтобы определить дату начала аренды в зависимости от позиции. На этом я остановился, потому что не знаю, что делать дальше. На данный момент это код, который у меня есть.

SELECT tag
,progress_complete_date_time
,create_date
,required_until_date
,modify_date
,on_rent_status
,[date_on-off_rent]
,position
,CASE 
    WHEN position = 1
        THEN [date_on-off_rent]
    END AS [first_time_on-off_rent]
,RANK() OVER(PARTITION BY tag  ORDER BY modify_date) AS [RANK]
FROM CleanUpB

Столбцы DateOnRent и DateOffRent основаны на том, как начинаются теги. Некоторые теги начинаются с DateOnRent, а некоторые — с DateOffRent. Мне нужно только зафиксировать начальный и конечный статус on_rent_status. Вот моя формула для расчета DaysOnRent и DaysOffRent.

TAG-000479 DaysOnRent DATEDIFF(ДЕНЬ, '12.08.2021', '04.04.2024') = 966 DATEDIFF(ДЕНЬ,'04.04.2024','10.06.2024') = 67

TAG-000479 Выходные дниАренда DATEDIFF(ДЕНЬ,'04.04.2024','04.04.2024') = 0 DATEDIFF(ДЕНЬ,'10.06.2024',GETDATE()) = 38

TAG-001832 DaysOnRent DATEDIFF(ДЕНЬ,'28.03.2024',GETDATE()) = 112

TAG-003420 DaysOnRent DATEDIFF(ДЕНЬ,'24.06.2024',GETDATE()) = 24

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

jarlh 18.07.2024 22:15
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
2
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Обновленная версия

На основе отзывов ОП. Это не просто ситуация с мин/максом, это проблема пробелов и островов. В этом случае нам приходится иметь дело с несколькими записями «on_rent» и, возможно, с несколькими записями «off_rent», а также с промежутками между «off_rent» и «on_rent».

На данный момент это мое решение (см. нижнюю часть ответа, чтобы заполнить таблицу образцов данных):

WITH cte AS (
    -- Collapse groups to get the start and end rent dates for each
    SELECT x.tag, x.GroupID
        , DateOnRent  = MIN(IIF(x.on_rent_status = 1, x.[date_on-off_rent], NULL)) -- Using min because we want the FIRST time  on_rent was logged
        , DateOffRent = MIN(IIF(x.on_rent_status = 0, x.[date_on-off_rent], NULL)) -- Using min because we want the FIRST time off_rent was logged
    FROM (
        SELECT x.tag, x.[date_on-off_rent], x.on_rent_status
            -- If the status changed from off_rent to on_rent, then we know a new group has started
            -- Returning 1 to the running total will generate a grouping ID that keeps sequential rent_status records together
            , GroupID = SUM(IIF(x.on_rent_status = 1 AND x.prev_on_rent_status = 0, 1, 0)) OVER (PARTITION BY x.tag ORDER BY x.position)
        FROM (
            SELECT tag, on_rent_status, [date_on-off_rent], position
                -- Getting the previous value will help us later determine whether the status changed
                , prev_on_rent_status = LAG(on_rent_status) OVER (PARTITION BY tag ORDER BY position)
            FROM #rent_history
        ) x
    ) x
    GROUP BY x.tag, x.GroupID
)
SELECT x.tag
    , FirstDateOnRent = MIN(x.DateOnRent)
    , LastDateOffRent = MAX(x.LastDateOffRent) -- NULL if still on_rent
    , DaysOnRent      = SUM(x.DaysOnRent)
    , DaysOffRent     = DATEDIFF(DAY, MAX(x.LastDateOffRent), GETDATE()) -- NULL if still on_rent
FROM (
    SELECT x.tag, x.DateOnRent, x.DateOffRent
        -- We want the most recent state of DateOffRent, rather than MAX, which means if they are still on_rent, it should return null
        , LastDateOffRent = LAST_VALUE(x.DateOffRent) OVER (PARTITION BY x.tag ORDER BY x.DateOnRent)
        -- Number of DaysOnrent needs to be calc'd at the group level so we don't accidentally include gaps
        , DaysOnRent  = DATEDIFF(DAY, x.DateOnRent, COALESCE(x.DateOffRent, GETDATE()))
    FROM cte x
) x
GROUP BY x.tag;

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

Но основная идея состоит в том, чтобы... вычислить некий идентификатор группировки, который сможет объединять последовательные события. В этом случае я использовал как LAG(), так и SUM({state change logic}) OVER() для создания этого идентификатора.

Затем для получения окончательного результата требуется просто дополнительная группировка и агрегирование.

Эта новая версия запроса будет поддерживать несколько записей on_rent, несколько записей off_rent, а также пробелы между off_rent и on_rent.


Старый/Оригинальный ответ

Судя по вашему объяснению, у вас есть таблица/набор данных под названием «История аренды», которая представляет собой верхний набор данных на вашем снимке экрана.

И вам нужен запрос к этому набору данных, выходные данные которого соответствуют второму набору данных на снимке экрана с надписью «Окончательный результат».

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

SELECT x.tag, x.DateOnRent, x.DateOffRent
    , DaysOnRent  = DATEDIFF(DAY, x.DateOnRent, COALESCE(x.DateOffRent, GETDATE()))
    , DaysOffRent = DATEDIFF(DAY, x.DateOffRent, GETDATE())
FROM (
    SELECT tag
        , DateOnRent  = MIN(IIF(on_rent_status = 1, [date_on-off_rent], NULL))
        , DateOffRent = MAX(IIF(on_rent_status = 0, [date_on-off_rent], NULL))
    FROM #rent_history
    GROUP BY tag
) x

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

Хотя концепция кажется довольно простой... возьмите самую раннюю дату «on_rent» и самую старую дату «off_rent». Затем выполните вычисления датировки с их использованием?


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

CREATE TABLE #rent_history (
    tag                         nvarchar(20) NOT NULL,
    progress_complete_date_time datetime     NOT NULL,
    create_date                 datetime     NOT NULL,
    required_until_date         datetime     NOT NULL,
    modify_date                 datetime     NOT NULL,
    on_rent_status              bit          NOT NULL,
    [date_on-off_rent]          datetime     NOT NULL,
    position                    int          NOT NULL,
    [first_time_on-off_rent]    datetime         NULL,
    [RANK]                      int          NOT NULL,
);

INSERT INTO #rent_history
VALUES ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '01/31/24 00:00', '11/06/23 19:31', 1, '08/12/21 00:00', 1, '08/12/21 00:00', 1)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '01/31/24 00:00', '11/06/23 19:31', 1, '11/06/23 00:00', 2, NULL            , 1)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '01/31/24 00:00', '11/06/23 19:31', 1, '11/06/23 00:00', 3, NULL            , 1)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '04/01/24 00:00', '04/04/24 05:00', 0, '04/04/24 00:00', 4, NULL            , 4)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '04/01/24 00:00', '04/04/24 05:00', 1, '04/04/24 00:00', 5, NULL            , 5)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '12/31/24 00:00', '04/30/24 15:11', 1, '04/30/24 00:00', 6, NULL            , 6)
     , ('TAG-000479', '08/12/21 00:00', '11/06/23 19:31', '12/31/24 00:00', '06/10/24 17:02', 0, '06/10/24 00:00', 7, NULL            , 7)
     , ('TAG-001832', '03/28/24 00:00', '01/17/24 15:41', '03/31/24 00:00', '03/28/24 03:32', 1, '03/28/24 00:00', 1, '03/28/24 00:00', 1)
     , ('TAG-003420', '06/24/24 00:00', '06/24/24 18:55', '12/15/24 00:00', '06/25/24 15:18', 1, '06/24/24 00:00', 1, '06/24/24 00:00', 1);

спасибо за помощь, код работает на 99% правильно. Вы написали: «Хотя концепция кажется довольно простой... возьмите самую раннюю дату «on_rent» и самую старую дату «off_rent». Затем выполните расчеты датировки, используя их?» Это неверно. Пользователи могут включать аренду (1) и выключать аренду (0) несколько раз. Например, в TA-000479 отсутствует 966 дней. Правильное число — 1033. Вот формула: DATEDIFF(DAY,'8/12/2021','4/4/2024') = 966 DATEDIFF(DAY,'04/04/2024','06/10 /2024') = 67

ATL-JP 19.07.2024 00:10

Я посмотрю, смогу ли я придумать новый запрос, но тем временем вам нужно обновить свой снимок экрана, чтобы он отображал то, что вы запрашиваете, и использовать более качественные образцы данных. Нет никакого разрыва во времени между тем, когда «TAG-000479» переключился на бесплатную аренду и затем снова на аренду. Поскольку пробела нет, мой код правильно рассчитал 1033 дня. Проблема, о которой вы говорите, гораздо сложнее определить и вычислить даты начала/окончания в разделах.

Chad Baldwin 19.07.2024 00:18

@ATL-JP К вашему сведению, я написал новую версию запроса, обновленную выше.

Chad Baldwin 19.07.2024 02:51

@ATL-JP Я не вижу того, что видишь ты. Я только что выполнил свой запрос, и он возвращает точно те же значения, что и в вашем образце таблицы «Окончательный результат»....

Chad Baldwin 19.07.2024 04:45

запрос был выполнен, но числа отключены. Я проверил данные, вставленные в таблицу выше, и все они верны. Единственное правильное число — 1033 из результата вашего запроса; остальные числа отличаются на единицу. Это еще один день. Они должны соответствовать данным ниже. +-------------+-----------------+ | дни в аренду | Выходные дниАренда | +-------------+-----------------+ | 1033 | 38 | | 112 | | | 24 | | +-------------+-----------------+

ATL-JP 19.07.2024 04:45

@ATL-JP Мой запрос возвращает точно такие же числа на основе примеров данных в моем ответе.

Chad Baldwin 19.07.2024 04:46

@ATL-JP Единственное, что может привести к отключению одного из них, - это проблема с часовым поясом. Это означает, что ваш сервер находится в часовом поясе впереди вас. Например, я только что запустил это на сервере, настроенном на UTC, и теперь значения равны 1033, 113, 25. Тогда как когда я запускаю его на сервере, настроенном на ET, это 1033, 112, 24.

Chad Baldwin 19.07.2024 04:49

@ATL-JP, если значения, хранящиеся в вашей таблице, относятся к определенному часовому поясу и вам нужны совершенно точные значения, вам необходимо настроить запрос так, чтобы он учитывал часовой пояс. Но это набор червей, который здесь не будет решен, потому что он должен будет учитывать летнее время и может даже меняться в зависимости от того, сохраняете ли вы местное время (то есть, каждая строка может иметь разное смещение времени) или УНИВЕРСАЛЬНОЕ ГЛОБАЛЬНОЕ ВРЕМЯ. Хотя это вам предстоит выяснить.

Chad Baldwin 19.07.2024 04:55

Ты прав, Чад. Я совсем забыл про время UTC. Спасибо, что помогли решить эту проблему.

ATL-JP 19.07.2024 05:07

Другие вопросы по теме