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

9

Я использую SQL Server 2016, и данные, которые я использую, имеют следующую форму.

CREATE TABLE #tab (cat CHAR(1), t CHAR(2), val1 INT, val2 CHAR(1));

INSERT INTO #tab VALUES 
    ('A','Q1',2,NULL),('A','Q2',NULL,'P'),('A','Q3',1,NULL),('A','Q3',NULL,NULL),
    ('B','Q1',5,NULL),('B','Q2',NULL,'P'),('B','Q3',NULL,'C'),('B','Q3',10,NULL);

SELECT *
FROM    #tab;

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

Я хотел бы получить последние ненулевые значения по столбцам val1и val2сгруппированы по catи упорядочены по t. Результат, который я ищу,

cat  val1 val2
A    1    P
B    10   C

Самое близкое, что я пришел, - это использование LAST_VALUEпри игнорировании того, ORDER BYчто не будет работать, так как мне нужно упорядоченное последнее ненулевое значение.

SELECT DISTINCT 
        cat, 
        LAST_VALUE(val1) OVER(PARTITION BY cat ORDER BY (SELECT NULL) ) AS val1,
        LAST_VALUE(val2) OVER(PARTITION BY cat ORDER BY (SELECT NULL) ) AS val2
FROM    #tab
cat  val1 val2
A    NULL NULL
B    10   NULL

Фактическая таблица имеет больше столбцов для cat( столбцы даты и строки) и больше столбцов val (столбцы даты, строки и числа) для выбора последнего ненулевого значения.

Любые идеи, как сделать этот выбор.

Эдмунд
источник
1
@ Vérace Сгруппировано по catзаказу t.
Эдмунд
1
@ ypercubeᵀᴹ Нет, пропущенное значение Q4 отсутствует, tзначения повторяются. Это не очень хорошие данные.
Эдмунд
4
Хорошо, но в этом случае вы должны предоставить заказ, который определяет идеальный порядок. PARTITION BY cat ORDER BY t, idнапример. В противном случае один и тот же запрос (любой запрос) может дать разные результаты при отдельных выполнениях. Если столбцы в таблице - это только те, которые вы показываете, я не понимаю, каким образом мы можем иметь определенный порядок!
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ В этом и заключается проблема. В данных нет столбца идентификатора. Существует несколько столбцов группировки, строковый столбец, который можно использовать для упорядочения групп, а затем столбцы с несколькими значениями с добавлением нулей.
Эдмунд
1
Если вы не можете точно определить SQL Server, в каком порядке должны быть строки, как любой потребитель этих данных узнает разницу?
Аарон Бертран

Ответы:

10

Используя технику конкатенации из «Последней ненулевой головоломки » Ицик Бен Ган, вы могли бы выглядеть так с типами данных таблицы и столбца.

select T.cat,
       cast(substring(
                     max(cast(T.t as binary(2)) + cast(T.val1 as binary(4))),
                     3,
                     4
                     ) as int),
       cast(substring(
                     max(cast(T.t as binary(2)) + cast(T.val2 as binary(1))),
                     3,
                     1
                     ) as char(1))
from #tab as T
group by T.cat;

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

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

with C1 as
(
  -- Concatenate the ordering column with the value column
  select T.cat,
        cast(T.t as binary(2)) + cast(T.val1 as binary(4)) as val1,
        cast(T.t as binary(2)) + cast(T.val2 as binary(1)) as val2
  from #tab as T
),
C2 as
(
  -- Get the max concatenated value per group
  select C1.cat,
         max(C1.val1) as val1,
         max(C1.val2) as val2
  from C1
  group by C1.cat
)
-- Extract the value from the concatenated column
select C2.cat,
       cast(substring(C2.val1, 3, 4) as int) as val1,
       cast(substring(C2.val2, 3, 1) as char(1)) as val2
from C2;

Это решение использует тот факт, что объединение нулевого значения с чем-либо приводит к нулевому значению. SET CONCAT_NULL_YIELDS_NULL (Transact-SQL)

Микаэль Эрикссон
источник
Очень хорошо дистиллированный Микаэль. Это решение спасло меня несколько раз, хотя сначала я обнаружил, что конец статьи Ицик смущает.
Тем,
2

Просто добавьте проверку на NULL в разделе, сделайте

SELECT DISTINCT 
        cat, 
        FIRST_VALUE(val1) OVER(PARTITION BY cat ORDER BY CASE WHEN val1 is NULL then 0 else 1 END DESC, t desc) AS val1,
        FIRST_VALUE(val2) OVER(PARTITION BY cat ORDER BY CASE WHEN val2 is NULL then 0 else 1 END DESC, t desc) AS val2
FROM    #tab
кельвин
источник
0

Это должно сделать это. row_number () и объединение

Если у вас нет хорошего сорта, вы должны надеяться, что только один из Q3 не является нулевым.

declare @t TABLE (cat CHAR(1), t CHAR(2), val1 INT, val2 CHAR(1));
INSERT INTO @t VALUES 
    ('A','Q1',2,NULL),('A','Q2',NULL,'P'),('A','Q3',1,NULL),('A','Q3',NULL,NULL),
    ('B','Q1',5,NULL),('B','Q2',NULL,'P'),('B','Q3',NULL,'C'),('B','Q3',10,NULL);

--SELECT *
--     , row_number() over (partition by cat order by t) as rn
--FROM   @t
--where val1 is not null or val2 is not null;

select t1.cat, t1.val1, t2.val2 
from  ( SELECT t.cat, t.val1
             , row_number() over (partition by cat order by t desc) as rn
        FROM   @t t
        where val1 is not null 
       ) t1
join   ( SELECT t.cat, t.val2
             , row_number() over (partition by cat order by t desc) as rn
        FROM   @t t
        where val2 is not null 
       ) t2
   on t1.cat = t2.cat
  and t1.rn = 1
  and t2.rn = 1
папараццо
источник