База данных: выберите последние ненулевые записи - PullRequest
12 голосов
/ 23 января 2012

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

 part | timestamp | x-pos | y-pos | status
------+-----------+-------+-------+--------
 a5   |       151 |     5 |    15 |      g
 a5   |       153 |  NULL |    17 |   NULL

(part, timestamp) является первичным ключом. NULL s во второй записи указывают значения, которые не изменились с момента первой записи.

Что я хочу сделать, это выбрать самые последние значения для каждого поля, сгруппированного по детали. Например, учитывая приведенные выше записи, результаты будут 153,5,17, г для части а5.

На данный момент у меня есть этот взломанный запрос.

    ((SELECT x-pos FROM part_changes WHERE x-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)

    UNION

    (SELECT y-pos FROM part_changesWHERE y-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)

    UNION

    (SELECT status FROM part_changes WHERE status IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1))

Но это возвращает один столбец, что означает, что я могу использовать группирование для организации.

Должен быть более элегантный способ сделать что-то, например, использовать COALESCE или IS NULL творчески. Но я застрял и не могу понять это. У кого-нибудь есть идея?

И нет, я не могу изменить структуру базы данных.

РЕДАКТИРОВАТЬ: ruakh имеет правильную идею. Единственная проблема сейчас - группировка по частям. Я не могу обойти LIMIT 1 для группировки по нескольким частям. Есть идеи?

mdahlman, я не слишком знаком с аналитическими функциями в postgresql. Так что, если это решение будет проще, чем сложный запрос, то обязательно опубликуйте свою идею.

РЕДАКТИРОВАТЬ 2: Спасибо всем за помощь. Мне кажется, я достаточно хорошо понимаю, что мне нужно делать.

Ответы [ 4 ]

5 голосов
/ 23 января 2012

Вместо использования UNION звучит так, будто вам действительно нужны подзапросы в списке полей. То есть вместо (SELECT ...) UNION (SELECT ...) UNION (SELECT ...) вы хотите SELECT (SELECT ...), (SELECT ...), (SELECT ...).


Например:

SELECT part,
       ( SELECT x_pos
           FROM part_changes
          WHERE part = pc.part
            AND x_pos IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS x_pos,
       ( SELECT y_pos
           FROM part_changes
          WHERE part = pc.part
            AND y_pos IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS y_pos,
       ( SELECT status
           FROM part_changes
          WHERE part = pc.part
            AND status IS NOT NULL
          ORDER
             BY timestamp DESC
          LIMIT 1
       ) AS status
  FROM ( SELECT DISTINCT
                part
           FROM part_changes
       ) AS pc
;

Но на этом этапе я бы действительно подумал о написании хранимой процедуры.


В качестве альтернативы:

SELECT DISTINCT
       part,
       FIRST_VALUE(x_pos) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN x_pos IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS x_pos,
       FIRST_VALUE(y_pos) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN y_pos IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS y_pos,
       FIRST_VALUE(status) OVER
         ( PARTITION BY part
               ORDER BY CASE WHEN status IS NULL
                             THEN NULL
                             ELSE TIMESTAMP
                         END DESC NULLS LAST
         ) AS status
  FROM part_changes
;
2 голосов
/ 24 января 2012

Только для одной части это должно дать вам ответ .. спасибо ruakh

Но мне не нравится эта версия ..

SELECT 
    (SELECT timestamp  FROM part_changes WHERE part = $part 
    ORDER BY timestamp DESC
    LIMIT 1) as timestamp,

    (SELECT x-pos FROM part_changes WHERE part = $part and x-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1) as xpos,

    (SELECT y-pos FROM part_changes WHERE part = $part and  y-pos IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1) as ypos,

    (SELECT status FROM part_changes WHERE part = $part and status IS NOT NULL
    ORDER BY timestamp DESC
    LIMIT 1)) as status
1 голос
/ 24 января 2012

список соответствующих временных отметок:

select max timestamp from part_changes where x_POS is not null group by part

Вы можете сделать это представление: давайте назовем это представление1

SELECT part_changes.part, part_changes.x-pos 
FROM part_changes left join view1 on part_changes.part = view1.part
WHERE x-pos IS NOT NULL 
 AND part_changes.timestamp = view1.timestamp 
GROUP BY part_changes.part

Смотрите, куда я иду?Это должно дать вам полный список для x-pos.

0 голосов
/ 23 января 2012

руах прав. Альтернатива: написать пользовательский агрегат с использованием SQL-CLR. Этот агрегат может работать снизу вверху сверху над строками и запоминать первое ненулевое значение каждого столбца.

Вы также можете сделать это в приложении. Создайте в вашей программе строки запроса в отсортированном виде, скажем, по 10 строк. Объедините эти партии, как описано выше. Если после выполнения текущего пакета есть пустой столбец, получите следующий пакет.

...