Возврат значений MIN и MAX и игнорирование нулей - заполните нулевые значения предшествующим ненулевым значением - PullRequest
0 голосов
/ 26 апреля 2019

Используя таблицу событий, мне нужно вернуть дату и тип для:

  • первое событие
  • самое последнее (ненулевое) событие

Самое последнее событие может иметь нулевые значения, которые в этом случае должны возвращать самое последнее ненулевое значение

Я нашел несколько статей, а также посты здесь на SO, которые похожи (возможно, даже идентичны), но не в состоянии расшифровать или понять решение - то есть

Заполнить нулевые значения последней ненулевой суммой - Oracle SQL

https://www.itprotoday.com/sql-server/last-non-null-puzzle

https://koukia.ca/common-sql-problems-filling-null-values-with-preceding-non-null-values-ad538c9e62a6

Таблица выглядит следующим образом - есть дополнительные столбцы, но я включаю только 3 для простоты. Также обратите внимание, что first Type and Date могут быть нулевыми. В этом случае возврат нуля является желательным.

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ Create ║ 2019-04-01 ║
║ A     ║ Update ║ 2019-04-02 ║
║ A     ║ null   ║ null       ║
╚═══════╩════════╩════════════╝

Вывод должен быть:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ Create    ║ 2019-04-01 ║ Update   ║ 2019-04-02 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

Первый метод, который я попробовал, состоял в том, чтобы присоединить таблицу к себе, используя подзапрос, который находит даты MIN и MAX, используя операторы case:

select
  Email,
  max(case when T1.Date = T2.Min_Date then T1.Type end) as FirstType,
  max(case when T1.Date = T2.Min_Date then T1.Date end) as FirstDate,
  max(case when T1.Date = T2.Max_Date then T1.Type end) as LastType,
  max(case when T1.Date = T2.Max_Date then T1.Date end) as LastDate,
from
  T1
join
  (select
    EmailAddress,
    max(Date) as Max_Date,
    min(Date) as Min_Date
  from
    Table1
  group by 
    Email
  ) T2
on
  T1.Email = T2.Email
group by
  T1.Email

Казалось бы, это работает для значений MIN, но значения MAX будут возвращать ноль.

Чтобы решить проблему возврата последнего не-значения, я попытался это сделать:

select
   EmailAddress,
   max(Date) over (partition by EmailAddress rows unbounded preceding) as LastDate,
   max(Type) over (partition by EmailAddress rows unbounded preceding) as LastType
from
   T1
group by
   EmailAddress,
   Date,
   Type

Однако, это дает 3 строки вместо 1.

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

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

Пример:

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ Create ║ 2019-04-01 ║
║ A     ║ null   ║ null       ║
╚═══════╩════════╩════════════╝

Желаемый результат:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ Create    ║ 2019-04-01 ║ Create   ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

Дополнительный вариант использования:

╔═══════╦════════╦════════════╗
║ Email ║  Type  ║    Date    ║
╠═══════╬════════╬════════════╣
║ A     ║ null   ║ null       ║
║ A     ║ Create ║ 2019-04-01 ║
╚═══════╩════════╩════════════╝

Желаемый результат:

╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate  ║ LastType ║  LastDate  ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A     ║ null      ║ null       ║ Create   ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝

Ответы [ 2 ]

0 голосов
/ 28 апреля 2019

Поскольку оконные функции Spark SQL поддерживают синтаксис NULLS LAST|FIRST, вы можете использовать его, а затем указать сводную диаграмму с несколькими агрегатами для значений rn 1 и 2. Я мог бы увидеть еще несколько примеров данных, но эта работа для вашего набора данных:

%sql
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp;

;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date), MAX(type) FOR rn In ( 1, 2 ) )

Переименуйте столбцы, указав в запросе необходимые детали, например,

-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) ) 

Поочередно укажите список столбцов, например,

-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
), cte2 AS
(
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) )
) 
SELECT *
FROM cte2 AS (Email, FirstDate, FirstType, LastDate, LastType)

Этот простой запрос использует ROW_NUMBER для назначения номера строки в наборе данных, упорядоченного по столбцу даты, но с использованием синтаксиса NULLS LAST, чтобы гарантировать, что нулевые строки будут последними в нумерации. Затем PIVOT преобразует строки в столбцы.

0 голосов
/ 26 апреля 2019

Использовать оконные функции и условное агрегирование:

select t.email,
       max(case when seqnum = 1 then type end) as first_type,
       max(case when seqnum = 1 then date end) as first_date,
       max(case when seqnum_nonull = 1 and type is not null then type end) as last_type,
       max(case when seqnum_nonull = 1 and type is not null then date end) as last_date
from (select t.*,
             row_number() over (partition by email order by date) as seqnum,
             row_number() over (partition by email, (case when type is null then 1 else 2 end) order by date) as seqnum_nonull
      from t
     ) t
group by t.email;
...