空值的大量合并

问题描述 投票:0回答:2

我在 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
设置会强制为零,但是当函数明确告诉它插入空值时它不起作用。同样,如果我使该列不可为空,它只会在我的插入中呕吐——我希望这可能会强制调用默认值。

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

sql postgresql null dynamic-sql coalesce
2个回答
1
投票

没有内置功能(我会知道)。 (对于 Postgres 15 仍然如此。)除了在任何地方拼写出

COALESCE(col, 0)
之外,您还可以编写生成查询字符串的 PL/pgSQL 代码,以在表的所有
NULL
列中用
0
替换所有
numeric
值:

DO
$do$
DECLARE
   _sql text;
BEGIN
   SELECT INTO _sql
          'UPDATE public.sales_data'
    || E'\nSET   ('      || string_agg(col, ', ')                          || ')'
    || E'\n    = ('      || string_agg('COALESCE(' || col || ', 0)', ', ') || ')'
    || E'\nWHERE  NOT (' || string_agg(col, ', ')                          || ') IS NOT NULL'
   FROM  (
      SELECT quote_ident(attname) AS col
      FROM   pg_attribute
      WHERE  attrelid = 'public.sales_data'::regclass
      AND    attnum >= 1                     -- exclude system columns
      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;

   IF _sql IS NULL THEN
      RAISE WARNING 'No numeric column found in table "sales_data"!';
   ELSE
      -- RAISE NOTICE '%',  _sql;            -- test output for debugging
      EXECUTE _sql;                          -- payload
   END IF;
END
$do$;

小提琴
sqlfiddle

连接并执行以下形式的查询:

UPDATE sales_data
SET   (qty_2012_01, qty_2012_02, qty_2012_03)
    = (COALESCE(qty_2012_01, 0), COALESCE(qty_2012_02, 0), COALESCE(qty_2012_03, 0))
WHERE  NOT (qty_2012_01, qty_2012_02, qty_2012_03) IS NOT NULL

添加的

WHERE
子句可防止不会更改任何内容的更新(按正常成本)。奇怪的语法
NOT ((x,y) IS NOT NULL)
标识具有至少一个
null
值的行。为什么是这种形式?参见:

我在 fiddle 上附上了一个演示。

适用于具有 any 列名称的 any 表。所有

numeric
列都已更新。只有实际更改的行才会被触及。

由于功能是侵入性的,所以我加了一个儿童安全装置。引用

RAISE NOTICE
行并取消引用
EXECUTE
以启动炸弹。

电话:

SELECT f_convert_numeric_null('sales_data');

将查询结果写入临时表,在临时表上运行该函数,然后然后

INSERT
到实际表中。

相关:


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

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

© www.soinside.com 2019 - 2024. All rights reserved.