2015-10-23 3 views
1

У меня есть таблица в базе данных Postgres с ежемесячными колоннами с 2012 до конца 2018 года:Массы-Коалесцентные неопределенных значения

create table sales_data (
    part_number text not null, 
    customer text not null, 
    qty_2012_01 numeric, 
    qty_2012_02 numeric, 
    qty_2012_03 numeric, 
    ... 
    qty_2018_10 numeric, 
    qty_2018_11 numeric, 
    qty_2018_12 numeric, 
    constraint sales_data_pk primary key (part_number, customer) 
); 

Данных заполняются из большой функции, которая извлекает данные из чрезвычайно широких различных источников. Он включает в себя много левых объединений - например, в сочетании истории с будущими данными, где один элемент может иметь историю, но не будущий спрос или наоборот. Или некоторые клиенты могут не иметь данных так далеко или вперед, как мы хотим.

Проблема, с которой я сталкиваюсь, связана с левыми объединениями (и характером данных, которые я вытягиваю), значительное количество значений, которые я вытягиваю, равно null. Я хотел бы, чтобы любой нуль просто был равен нулю, чтобы упростить любые запросы к этой таблице, а именно агрегировать функции, которые говорят 1 + null + 2 = null.

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

update sales_data set qty_2012_01 = 0 where qty_2012_01 is null; 
update sales_data set qty_2012_02 = 0 where qty_2012_02 is null; 
update sales_data set qty_2012_03 = 0 where qty_2012_03 is null; 
... 78 more like this... 
update sales_data set qty_2018_10 = 0 where qty_2018_10 is null; 
update sales_data set qty_2018_11 = 0 where qty_2018_11 is null; 
update sales_data set qty_2018_12 = 0 where qty_2018_12 is null; 

Мне что-то не хватает, правда? Есть ли более простой способ?

Я надеялся, что установка default в столбце заставит нуль, но она не работает, когда функция явно сообщает ей вставить нуль. Точно так же, если я делаю столбец не-nullable, он просто pukes на моей вставке - я надеялся, что это может заставить вызвать значение по умолчанию.

Кстати, стратегия insert-then-update - это то, о чем я наказываю других, поэтому я понимаю, что это меньше, чем идеально. Эта функция немного зверя, и она требует некоторого периодического обслуживания (длинный рассказ). Моя основная цель - сохранить функцию как читаемую и поддерживаемую, насколько это возможно - НЕ сделать функцию uber-эффективной. Сама таблица не огромна - менее миллиона записей после того, как все сказано и сделано, и мы запускаем функцию для ее заполнения один или два раза в месяц.

+1

"* с месячными столбцов, начиная 2012 до конца 2018 года: *" - почему? Если вы правильно нормализуете свою модель данных, то это всего лишь одна инструкция 'update'. –

+0

@a_horse_with_no_name - честный вопрос. Данные фактически отображаются в Excel в виде диаграммы. Если бы мы сделали это как нормализованные данные, это были бы миллионы строк, которые затем должны были бы суммироваться в сводной таблице/диаграмме. Денормализация была разработана, для быстрой работы в инструменте отображения. У нас есть данные в другом месте, нормированные. Функция принимает эти различные источники данных и заполняет эту таблицу. – Hambone

ответ

1

Не существует встроенной функции (я бы знал). Короткие орфографических из COALESCE(col, 0) везде вы можете написать функцию, чтобы заменить все NULL значения с 0 во всех numeric столбцах таблицы:

CREATE OR REPLACE FUNCTION f_convert_numeric_null(_tbl regclass) 
    RETURNS void AS 
$func$ 
BEGIN 
    RAISE NOTICE '%', -- test output for debugging 
    -- EXECUTE   -- payload 
    (SELECT 'UPDATE ' || _tbl 
     || ' SET ' || string_agg(format('%1$s = COALESCE(%1$s, 0)', col), ', ') 
     || ' WHERE ' || string_agg(col || ' IS NULL', ' OR ') 
    FROM (
     SELECT quote_ident(attname) AS col 
     FROM pg_attribute 
     WHERE attrelid = _tbl    -- valid, visible, legal table name 
     AND attnum >= 1     -- exclude tableoid & friends 
     AND NOT attisdropped    -- exclude dropped columns 
     AND NOT attnotnull     -- exclude columns defined NOT NULL 
     AND atttypid = 'numeric'::regtype -- only numeric columns 
     ORDER BY attnum 
    ) sub 
    ); 
END 
$func$ LANGUAGE plpgsql; 

сцепляются и выполняет запрос вида:

UPDATE sales_data 
SET qty_2012_01 = COALESCE(qty_2012_01, 0) 
    , qty_2012_02 = COALESCE(qty_2012_02, 0) 
    , qty_2012_03 = COALESCE(qty_2012_03, 0) 
     ... 
WHERE qty_2012_01 IS NULL OR 
     qty_2012_02 IS NULL OR 
     qty_2012_03 IS NULL ... ; 

Works для любой стол с любой названия колонок. Все столбцы numeric обновляются. Потрогаются только строки, которые фактически меняются.

Поскольку функция является массово-инвазивной, я добавил устройство защиты от детей. Подпишитесь на строку RAISE NOTICE и введите недопустимое значение EXECUTE, чтобы запустить бомбу.

Вызов:

SELECT f_convert_numeric_null('sales_data'); 

Моя главная цель состоит в том, чтобы сохранить функцию чтения и сопровождаемым, поскольку это возможно.

Это должно быть сделано.

SQL Fiddle.

Параметр типа regclass, поэтому передать имя таблицы, возможно, схемы квалифицированных, нестандартные идентификаторы должны быть в двойных кавычках - имена, как "mySchema"."0dumb tablename".

Написать результаты запроса во временную таблицу, запустить функцию на временную таблицу и затемINSERT в реальной таблице.

Похожие:

+0

Это довольно креативно, и это определенно помогает сохранить мою функцию в чистоте, а также фиксировать все значения в одно и то же время (меньше мертвых строк в таблице, когда это будет сделано). Это также приводит к другой идее - считаете ли вы, что триггер можно использовать для перехвата вставки и изменения нуля в 0? – Hambone

+1

@ Хамбоне: Конечно. Аналогично http://stackoverflow.com/a/14035890/939860 или http://stackoverflow.com/a/25797129/939860. Опять же, вы должны указать все столбцы или использовать (возможно, более дорогой) динамический SQL. –

+0

Это на самом деле отлично ... Спасибо за эту ссылку. – Hambone

1

В самом начале инструкции INSERT вы можете COALESCE (col_name, 0) исправить проблему. Вы также можете добавить NOT NULL для сохранения целостности данных.

Предполагая, что вставки данных из таблицы Temp

INSERT INTO sales_data (qty_2012_01, qty_2012_02) 
SELECT COALESCE(qty_2012_01, 0), COALESCE(qty_2012_01, 0) 
FROM temp_sales_data; 

Одно обновление

UPDATE sales_date SET 
qty_2012_01 = COALESCE(qty_2012_01, 0), 
qty_2012_02 = COALESCE(qty_2012_02, 0) 
.. 
.. 
WHERE qty_2012_01 IS NULL 
OR qty_2012_02 IS NULL 
... 
.... 

Этот запрос будет обновлять все столбцы в одном обновлении.

+0

Я думаю, что @Hambone уточнил, что он ищет другое решение. – Nick

+0

Я получил Спасибо, Ник, обновляя свой ответ. – Shankar

+0

Спасибо ... в то время как я надеялся избежать этого типа вещей (длительная функция для поддержания), я сделал +1 ваше предложение, поскольку это улучшение по сравнению с тем, что у меня было, делая это в одной транзакции и сводя к минимуму обновления. – Hambone

Смежные вопросы