Выберите строку первых ненулевых значений в разреженной таблице

Используя следующую таблицу:

A | B    | C    | ts
--+------+------+------------------
1 | null | null | 2016-06-15 10:00
4 | null | null | 2016-06-15 11:00 
4 |    9 | null | 2016-06-15 12:00
5 |    1 |    7 | 2016-06-15 13:00

Как выбрать первое ненулевое значение каждого столбца в рабочем окне из N строк? "Первый" определяется порядком временных меток в столбцах ts. Запрос к приведенной выше таблице приведет к:

A | B | C
--+---+---
1 | 9 | 7

Ответ 1

Функция окна first_value() допускает довольно короткое и элегантное решение:

SELECT first_value(a) OVER (ORDER BY a IS NULL, ts) AS a
     , first_value(b) OVER (ORDER BY b IS NULL, ts) AS b
     , first_value(c) OVER (ORDER BY c IS NULL, ts) AS c
FROM   t
LIMIT  1;

a IS NULL оценивается как TRUE или FALSE. FALSE сортирует перед TRUE. Таким образом, ненулевые значения идут первыми. Далее следует ts по ts (столбец отметки времени, как вы прокомментировали), и вы получите его в одном SELECT.

Это было бы проще, если бы Postgres поддерживал IGNORE NULLS. Руководство:

Стандарт SQL определяет RESPECT NULLS или IGNORE NULLS вариант для lead, lag, first_value, last_value и nth_value. Это не реализовано в PostgreSQL: поведение всегда совпадает со стандартным значением по умолчанию, а именно RESPECT NULLS.

Одно из немногих упущений в отношении стандартного SQL в этой области.

дБ <> скрипка здесь
SQL Fiddle.

Ответ 2

Вы должны определить порядок (первичный ключ или что-то еще), чтобы получить значение FIRST не null. Поэтому я использовал столбец ID для упорядочивания строк в вашей таблице.

select 
(select A from t where A is not null ORDER BY id LIMIT 1),
(select b from t where b is not null ORDER BY id LIMIT 1),
(select c from t where c is not null ORDER BY id LIMIT 1)

демоверсия SQLFiddle

Ответ 3

Вы можете сделать это с помощью функций окна. Я разделил результат на 2 части, а затем использовал этот раздел для функции row_number

  • с нулевыми значениями
  • имеющий действительный действительный

Затем, используя базовый case, чтобы получить те, которые имеют row_number как 1 и которые имеют в них not null значение

SQLFIDDLE

SELECT
    max ( CASE
            WHEN a_row_num = 1 AND a IS NOT NULL THEN a
          END ) AS A,
    max ( CASE
            WHEN b_row_num = 1 AND B IS NOT NULL THEN B
          END ) AS B,
    max ( CASE
            WHEN c_row_num = 1 AND C IS NOT NULL THEN C
          END ) AS C
FROM
    (
      SELECT
          a,
          row_number ( ) over ( partition BY a IS NULL ORDER BY ID ) a_row_num,
          b,
          row_number ( ) over ( partition BY b IS NULL ORDER BY ID ) b_row_num,
          c,
          row_number ( ) over ( partition BY c IS NULL ORDER BY ID ) c_row_num
      FROM
          test
    ) AS sub_query

Вывод:

| A | B | C |
|---|---|---|
| 1 | 9 | 7 |

ПРИМЕЧАНИЕ: Я добавил поле id, которое помогает узнать, какая из записей была впервые вставлена, мы используем ее в порядке возрастания в нашей оконной функции

Ответ 4

Не уверен, правильно ли я получил вопрос как это кажется довольно простым в принципе.
Попробуйте этот запрос.
SQL Fiddle: http://sqlfiddle.com/#!11/ac585/8

WITH t0 AS
(
    SELECT A FROM
    TableName t0
    WHERE (A IS NOT NULL)
    ORDER BY ID ASC
    LIMIT 1
),

t1 AS
(
    SELECT B FROM
    TableName
    WHERE (B IS NOT NULL)
    ORDER BY ID ASC
    LIMIT 1
),

t2 AS

(
    SELECT C FROM
    TableName
    WHERE (C IS NOT NULL)
    ORDER BY ID ASC
    LIMIT 1
)

SELECT t0.A, t1.B, t2.C
FROM
t0
JOIN t1 ON 1=1
JOIN t2 ON 1=1