Записи, наиболее близкие по времени

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

Например, это таблица (реальная таблица содержит больше данных)

CREATE TABLE t1 (time DATE, some_data NUMBER);

И в этой таблице будут такие записи

INSERT INTO t1 VALUES(TO_DATE('1.1.2024 08:08:08', 'dd.mm.yyyy hh24:mi:ss'), 1);
INSERT INTO t1 VALUES(TO_DATE('1.1.2024 10:10:10', 'dd.mm.yyyy hh24:mi:ss'), 2);
INSERT INTO t1 VALUES(TO_DATE('1.1.2024 15:15:15', 'dd.mm.yyyy hh24:mi:ss'), 3);
INSERT INTO t1 VALUES(TO_DATE('1.1.2024 20:20:20', 'dd.mm.yyyy hh24:mi:ss'), 4);
INSERT INTO t1 VALUES(TO_DATE('2.1.2024 09:09:09', 'dd.mm.yyyy hh24:mi:ss'), 5);
INSERT INTO t1 VALUES(TO_DATE('2.1.2024 12:12:12', 'dd.mm.yyyy hh24:mi:ss'), 6);
INSERT INTO t1 VALUES(TO_DATE('2.1.2024 16:16:16', 'dd.mm.yyyy hh24:mi:ss'), 7);

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

1.1.2024 08:08:08       1
1.1.2024 20:20:20       4
2.1.2024 09:09:09       5
2.1.2024 16:16:16       7

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

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

Ответы 2

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

Вы можете определить разницу между каждым временем и 08:00 и 20:00 в один и тот же день:

trunc(t1.time) + interval '8' hour - time
trunc(t1.time) + interval '20' hour - time

который может быть положительным или отрицательным, поэтому получите абсолютное значение:

abs(trunc(t1.time) + interval '8' hour - time)
abs(trunc(t1.time) + interval '20' hour - time)

затем проранжируйте их по порядку для каждого дня:

rank() over (partition by trunc(time)
  order by abs(trunc(t1.time) + interval '8' hour - time))
rank() over (partition by trunc(time)
  order by abs(trunc(t1.time) + interval '20' hour - time))

что дает вам:

select t1.time, t1.some_data,
  rank() over (partition by trunc(time)
    order by abs(trunc(t1.time) + interval '8' hour - time)) as rnk8,
  rank() over (partition by trunc(time)
    order by abs(trunc(t1.time) + interval '20' hour - time)) as rnk20
from t1
ВРЕМЯ НЕКОТОРЫЕ_ДАННЫЕ РНК8 РНК20 2024-01-01 08:08:08 1 1 4 2024-01-01 10:10:10 2 2 3 2024-01-01 15:15:15 3 3 2 2024-01-01 20:20:20 4 4 1 2024-01-02 09:09:09 5 1 3 2024-01-02 12:12:12 6 2 2 2024-01-02 16:16:16 7 3 1

и, наконец, выберите только те строки, которые имеют наивысший рейтинг:

select time, some_data
from (
  select t1.time, t1.some_data,
    rank() over (partition by trunc(time)
      order by abs(trunc(t1.time) + interval '8' hour - time)) as rnk8,
    rank() over (partition by trunc(time)
      order by abs(trunc(t1.time) + interval '20' hour - time)) as rnk20
  from t1
)
where rnk8 = 1
or rnk20 = 1
order by time
ВРЕМЯ НЕКОТОРЫЕ_ДАННЫЕ 2024-01-01 08:08:08 1 2024-01-01 20:20:20 4 2024-01-02 09:09:09 5 2024-01-02 16:16:16 7

рабочий пример

Если у вас есть две строки с одинаковой (абсолютной) разницей, вы увидите обе, потому что rank() даст один и тот же ответ. Если вы не хотите, чтобы это произошло, вам нужно будет решить, как разорвать связи, добавив условия заказа (например, разница без пресса, а затем, возможно, какой-то другой столбец). Или вы можете использовать dense_rank() или row_number(), которые оба будут иметь значение 1 только для одной строки, но какая из строк будет возвращена, будет неопределенным. Лучше быть явным, чтобы получить последовательные результаты.

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

select dt, time, some_data
  from (select q.*, rank() over(partition by q.dt, q.target_time order by q.dist asc) rnk
          from (select 8 / 24 target_time
                      ,abs(time - trunc(time) - 8 / 24) dist
                      ,trunc(time) dt
                      ,time
                      ,some_data
                  from t1
                union all
                select 20 / 24 target_time
                      ,abs(time - trunc(time) - 20 / 24) dist
                      ,trunc(time) dt
                      ,time
                      ,some_data
                  from t1) q)
 where rnk = 1
 order by dt, time;

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