Как получить значения на основе минимальной и максимальной даты в группах в oracle?

#sql #oracle

Вопрос:

У меня есть таблица с такими данными:

Транспортное средство Начать Конец Дата
Грузовик A A B 02/01/2021 01:00:00
Грузовик A B C 02/01/2021 02:00:00
Грузовик A C D 04/01/2021 03:00:00
Грузовик B C A 05/01/2021 01:00:00
Грузовик B A B 06/01/2021 01:00:00
Грузовик C C B 07/01/2021 01:00:00
Грузовик C B C 08/01/2021 01:00:00
Грузовик C C B 09/01/2021 01:00:00
Грузовик C B A 10/01/2021 01:00:00

Мне нужно получить начальную и конечную точки каждого транспортного средства на основе даты.

Например, грузовик A стартовал из пункта A в пункт B 01.02.2010 01:00:00. Затем грузовик A переместился из пункта B в пункт C 01.02.2021 02:00:00. Грузовик A переместился из пункта C в пункт D 01.04.2021 03:00:00.

Это началось с точки A и закончилось в точке D.

Я хочу получить такой результат:

Транспортное средство Начать Конец Дата
Грузовик A A D 04/01/2021 03:00:00

Ответ №1:

Используйте GROUP BY и агрегируйте использование KEEP для получения FIRST LAST значений даты или:

 SELECT vehicle,
       MIN("START") KEEP (DENSE_RANK FIRST ORDER BY "DATE") AS "START",
       MAX("END")   KEEP (DENSE_RANK LAST  ORDER BY "DATE") AS "END",
       MAX("DATE") AS "DATE"
FROM   table_name
GROUP BY vehicle
 

Что для образца данных:

 CREATE TABLE table_name (Vehicle, "START", "END", "DATE") AS
SELECT 'Truck A', 'A', 'B', DATE '2021-01-02'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck A', 'B', 'C', DATE '2021-01-02'   INTERVAL '2' HOUR FROM DUAL UNION ALL
SELECT 'Truck A', 'C', 'D', DATE '2021-01-04'   INTERVAL '3' HOUR FROM DUAL UNION ALL
SELECT 'Truck B', 'C', 'A', DATE '2021-01-05'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck B', 'A', 'B', DATE '2021-01-06'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck C', 'C', 'B', DATE '2021-01-07'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck C', 'B', 'C', DATE '2021-01-08'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck C', 'C', 'B', DATE '2021-01-09'   INTERVAL '1' HOUR FROM DUAL UNION ALL
SELECT 'Truck C', 'B', 'A', DATE '2021-01-10'   INTERVAL '1' HOUR FROM DUAL
 

Выводит:

ТРАНСПОРТНОЕ СРЕДСТВО НАЧАТЬ КОНЕЦ Дата
Грузовик A A D 2021-01-04 03:00:00
Грузовик B C B 2021-01-06 01:00:00
Грузовик C C A 2021-01-10 01:00:00

db<>скрипка здесь

Комментарии:

1. @MTO действительно самое элегантное решение на сегодняшний день

Ответ №2:

  WITH CTE(Vehicle,  StartT,     EndD,   DateD) AS
 (

     SELECT 'Truck A', 'A' ,'B',    CAST('02/01/2021 01:00:00'AS SMALLDATETIME)  UNION ALL
     SELECT'Truck A' ,  'B',    'C',    CAST('02/01/2021 02:00:00'AS SMALLDATETIME) UNION ALL
     SELECT'Truck A',   'C' ,   'D',    '04/01/2021 03:00:00' UNION ALL
     SELECT'Truck B',   'C' ,   'A',    '05/01/2021 01:00:00' UNION ALL
     SELECT'Truck B',   'A',    'B',    '06/01/2021 01:00:00' UNION ALL
     SELECT'Truck C',   'C' ,   'B',    '07/01/2021 01:00:00' UNION ALL
     SELECT'Truck C' ,  'B',    'C',    '08/01/2021 01:00:00' UNION ALL
     SELECT'Truck C' ,  'C' ,   'B',    '09/01/2021 01:00:00' UNION ALL
     SELECT'Truck C' ,  'B',    'A',    '10/01/2021 01:00:00'  
  ),
 CTE2(Vehicle,  StartT,     EndD,   DateD,XCOL,YCOL) AS
  (
    SELECT C.Vehicle,C.STARTT,C.ENDD,C.DATED,
     ROW_NUMBER()OVER(PARTITION BY C.Vehicle ORDER BY C.DATED ASC)XCOL,
     ROW_NUMBER()OVER(PARTITION BY C.Vehicle ORDER BY C.DATED DESC)YCOL
     FROM CTE AS C
  )
  SELECT C.VEHICLE,C.StartT,X.EndD,X.DateD
   FROM CTE2 AS C
    JOIN CTE2 AS X ON C.Vehicle=X.Vehicle
     WHERE C.XCOL=1 AND X.YCOL=1
 

Извините, у меня нет Oracle, поэтому я создал запрос для MS SQL Server
CTE представляет ваши образцы данных
CTE2 вычисляет «двунаправленный» row_number. Для начальной строки (например, для ‘TruckA’) XCOL = 1. Для конечной строки YCOL = 1
Затем мы объединяем эти два CTE на Vehicle.
Не могли бы вы попробовать этот запрос, если он подходит для вас

Ответ №3:

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

 with x  ( Vehicle   , Start_Letter  ,End_Letter , Date_Letter  )              
as 
(
select 'Truck A' , 'A' , 'B' , to_date('02/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck A' , 'B' , 'C' , to_date('02/01/2021 02:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck A' , 'C' , 'D' , to_date('04/01/2021 03:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck B' , 'C' , 'A' , to_date('05/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck B' , 'A' , 'B' , to_date('06/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck C' , 'C' , 'B' , to_date('07/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck C' , 'B' , 'C' , to_date('08/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck C' , 'C' , 'B' , to_date('09/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual union all 
select 'Truck C' , 'B' , 'A' , to_date('10/01/2021 01:00:00','dd/mm/yyyy hh24:mi:ss') from dual 
) 
select vehicle , start_letter, end_letter, date_letter from 
(
select vehicle , min_start_lett as start_letter, max_start_lett as end_letter, date_letter, 
row_number() over(partition by vehicle order by date_letter desc) as ranking
from 
( 
select x.* , 
rank() over(partition by vehicle order by start_letter desc, end_letter desc, date_letter desc ) as rn , 
min(start_letter) over(partition by vehicle) as min_start_lett , 
max(end_letter) over(partition by vehicle) as max_start_lett
from x 
) d 
where rn = ( select max(rn) from x c where c.vehicle = d.vehicle group by vehicle )             
) where ranking = 1 
;
 

введите описание изображения здесь

db<>скрипка

Ответ №4:

Использование функции окна FIRST_VALUE для поиска первого и последнего для каждого транспортного средства

 SELECT Vehicle
, FirstStart AS "Start"
, LastEnd AS "End"
, MAX("Date") AS "Date"
FROM
(
  SELECT Vehicle, "Start", "End", "Date"
  , FIRST_VALUE("Start") OVER (PARTITION BY Vehicle ORDER BY "Date") AS FirstStart
  , FIRST_VALUE("End") OVER (PARTITION BY Vehicle ORDER BY "Date" DESC) AS LastEnd
  FROM yourtable
) q
GROUP BY Vehicle, FirstStart, LastEnd
ORDER BY Vehicle;