【问题标题】:Mass-Coalescing of Null Values空值的质量合并
【发布时间】:2016-01-23 05:54:51
【问题描述】:

我在 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。

可以修改函数并添加数百个合并语句。但是,我希望有另一种解决方法,即使这意味着事后修改值。也就是说,这意味着在函数末尾添加 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 设置会强制为零,但是当函数明确告诉它插入空值时它不起作用。同样,如果我使列不可为空,它只会在我的插入时呕吐——我希望这可能会强制调用默认值。

顺便说一句,插入然后更新策略是我责备其他人的策略,所以我知道这不太理想。这个功能有点像野兽,它确实需要一些偶尔的维护(长话短说)。我的主要目标是尽可能保持函数的可读性和可维护性——而不是让函数变得超级高效。表本身并不大 - 毕竟不到一百万条记录 - 我们运行该函数以每月填充一次或两次。

【问题讨论】:

  • "从 2012 年到 2018 年底的月度列:" - 为什么?如果您正确规范化您的数据模型,那么这只是一个 update 语句。
  • @a_horse_with_no_name -- 确实是一个公平的问题。数据实际上是以图表的形式呈现在 Excel 中。如果我们将其作为标准化数据进行,则将有数百万行,然后必须在数据透视表/图表中进行汇总。非规范化是设计使然,以便在显示工具中快速执行。我们有其他地方的数据,标准化。该函数采用这些不同的数据源并填充此表

标签: sql postgresql null dynamic-sql coalesce


【解决方案1】:

虽然 INSERT 语句本身您可以使用 COALESCE (col_name, 0) 来解决此问题。您也可以添加 NOT NULL 以保持数据完整性。

假设从临时表插入数据

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 
...
....

上述查询将在一次更新中更新所有列。

【讨论】:

  • 我认为@Hambone 表明他正在寻找另一种解决方案。
  • 我明白了,谢谢尼克,正在更新我的答案。
  • 谢谢...虽然我希望避免这种类型的事情(维护一个冗长的功能),但我对您的建议做了 +1,因为它是对我所拥有的东西的改进,一次完成事务和最小化更新。
【解决方案2】:

没有内置功能(我知道)。没有在任何地方拼写出COALESCE(col, 0),您可以编写一个函数,用0 替换表的所有numeric 列中的所有NULL 值:

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 ... ;

适用于具有 any 列名称的 any 表。所有numeric 列均已更新。仅触及实际更改的行。

由于该功能具有大规模侵入性,因此我添加了一个儿童安全装置。引用 RAISE NOTICE 行并取消引用 EXECUTE 以启动炸弹。

呼叫:

SELECT f_convert_numeric_null('sales_data');

我的主要目标是尽可能保持函数的可读性和可维护性。

应该可以的。

SQL Fiddle.

参数类型为regclass,因此传递表名,可能是模式限定的,非标准标识符必须用双引号引起来——名称如"mySchema"."0dumb tablename"

将查询结果写入临时表,在临时表上运行函数,然后INSERT 写入实际表。

相关:

【讨论】:

  • 这非常有创意,它绝对有助于保持我的函数干净,以及同时修复所有值(完成后表中的死行更少)。它也带来了另一个想法——你认为触发器可以用来拦截插入并将 null 更改为 0 吗?
  • @Hambone:当然。类似于stackoverflow.com/a/14035890/939860stackoverflow.com/a/25797129/939860。同样,您必须拼出所有列或使用(可能更昂贵)动态 SQL。
  • 这实际上是完美的......感谢您的参考。
猜你喜欢
  • 1970-01-01
  • 2017-05-15
  • 2022-08-18
  • 2016-10-03
  • 1970-01-01
  • 1970-01-01
  • 2015-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多