【问题标题】:Set empty strings ('') to NULL in the whole database在整个数据库中将空字符串 ('') 设置为 NULL
【发布时间】:2016-11-22 09:10:48
【问题描述】:

在我的数据库中有许多文本列,其中的值是空字符串 ('')。空字符串需要设置为NULL。我不知道这个数据库中的确切架构、表和列,或者我想编写一个可以重复使用的通用解决方案。

我将如何编写查询/函数来查找所有架构中所有表中的所有文本列,并将所有具有空字符串 ('') 的列更新为 NULL

【问题讨论】:

  • 您必须通过查看 information_schema.columns 中的内容来创建实际的 UPDATE 语句。 format() 函数可以使这比一堆字符串 concat 更容易。

标签: sql postgresql plpgsql dynamic-sql information-schema


【解决方案1】:

实现这一目标的最有效方法:

  • 每个表运行一个UPDATE
  • 仅使用任何实际的空字符串更新可空列(未定义 NOT NULL)。
  • 仅更新具有任何实际空字符串的行。
  • 其他值保持不变。

这个相关的答案有一个 plpgsql 函数,它使用系统目录pg_attribute 为任何给定的表自动安全地构建和运行UPDATE 命令:

使用此答案中的函数f_empty2null(),您可以像这样循环选择表:

DO
$do$
DECLARE
   _tbl regclass;
BEGIN
   FOR _tbl IN
      SELECT c.oid::regclass
      FROM   pg_class c
      JOIN   pg_namespace n ON n.oid = c.relnamespace
      WHERE  c.relkind = 'r'            -- only regular tables
      AND    n.nspname NOT LIKE 'pg_%'  -- exclude system schemas
   LOOP
      RAISE NOTICE $$PERFORM f_empty2null('%');$$, _tbl;
      -- PERFORM f_empty2null(_tbl);  -- uncomment to prime the bomb
   END LOOP;
END
$do$;

小心!这会更新数据库中所有用户表的所有列中的所有空字符串。确保这是您想要的,否则它可能会破坏您的数据库。

当然,您需要对所有选定表具有UPDATE 权限。

作为儿童安全设备,我评论了有效载荷。

您可能已经注意到我直接使用系统目录,而不是信息模式(这也可以)。关于这个:

重复使用

这是一个可重复使用的集成解决方案。不带安全装置:

CREATE OR REPLACE FUNCTION f_all_empty2null(OUT _tables int, OUT _rows int) AS
$func$
DECLARE
   _typ CONSTANT regtype[] := '{text, bpchar, varchar, \"char\"}';
   _sql text;
   _row_ct int;
BEGIN
   _tables := 0;  _rows := 0;
   FOR _sql IN
      SELECT format('UPDATE %s SET %s WHERE %s'
                  , t.tbl
                  , string_agg(format($$%1$s = NULLIF(%1$s, '')$$, t.col), ', ')
                  , string_agg(t.col || $$ = ''$$, ' OR '))
      FROM  (
         SELECT c.oid::regclass AS tbl, quote_ident(attname) AS col
         FROM   pg_namespace n
         JOIN   pg_class     c ON c.relnamespace = n.oid
         JOIN   pg_attribute a ON a.attrelid = c.oid
         WHERE  n.nspname NOT LIKE 'pg_%'   -- exclude system schemas
         AND    c.relkind = 'r'             -- only regular tables
         AND    a.attnum >= 1               -- exclude tableoid & friends
         AND    NOT a.attisdropped          -- exclude dropped columns
         AND    NOT a.attnotnull            -- exclude columns defined NOT NULL!
         AND    a.atttypid = ANY(_typ)      -- only character types
         ORDER  BY a.attnum
         ) t
      GROUP  BY t.tbl
   LOOP
      EXECUTE _sql;
      GET DIAGNOSTICS _row_ct = ROW_COUNT;  -- report nr. of affected rows
      _tables := _tables + 1;
      _rows := _rows + _row_ct;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

呼叫:

SELECT * FROM pg_temp.f_all_empty2null();

返回:

 _tables | _rows
---------+---------
 23      | 123456

注意我是如何正确转义表名和列名的!

c.oid::regclass AS tbl, quote_ident(attname)  AS col

考虑:

小心!与上述相同的警告。
还要考虑我上面链接的答案中的基本解释:

【讨论】:

  • 谢谢..这正是我想要的..当然如此大规模的更新需要谨慎实施。谢天谢地,这是用于 ETL 管道,而不是在 OLTP 主数据库上,所以错误地核对不会那么头疼:)
  • 我正在尝试与 information_schema.columns 视图类似的东西。只是好奇直接使用目录有什么好处吗?
  • @Manquer:原因之一:信息模式不包含 oid。有优点也有缺点。我添加了另一个包含详细评估的链接。
【解决方案2】:

我认为下面的代码是通用的。你可以随时随地使用它:

    Declare @Query varchar(1000)
    declare @AllDatabaseTables table(id int,Table_Name varchar(50))
    Insert into @AllDatabaseTables select Table_Name from information_schema.tables

    declare @Table_Name varchar(50)

    declare @i int=1
    While @i<=(Select Count(*) from @AllDatabaseTables)
    BEGIN
    Select @Table_Name=Table_Name from @AllDatabaseTables Where id=@i

    Declare @ColumnTable table(id int,ColumnName varchar(100))
    Insert into @ColumnTable Select COLUMN_NAME from information_schema.columns Where Table_Name=@Table_Name and DATA_TYPE='varchar' --if the datatype is varchar type
    Declare @ColumnName varchar(50)
    Declare @k int=1
    While @k<=(Select count(*) from @ColumnTable)
        BEGIN
            Select @ColumnName=ColumnName from @ColumnTable where id=@k

            Set @Query='Update '+@Table_Name+' Set '+@ColumnName+'=NULL where '+@ColumnName+'='''' ' 
            Exec(Query)

            Set @k=@k+1
        END



    Set @i=@i+1

END

【讨论】:

    【解决方案3】:

    最简单的方法是手动逐个表格。对于每个表,执行以下操作:

    START TRANSACTION;
    UPDATE tablename SET 
        stringfield1 = NULLIF(stringfield1, ''),
        stringfield2 = NULLIF(stringfield2, '');
    <do some selects to make sure everything looks right>
    COMMIT;
    

    这将重写表格中的每一行,但它只会在表格上传递一次。这对您来说可能不切实际。

    您可能希望使用 WHERE 子句逐个字段地执行此操作,以减少更新次数,如下所示。

    START TRANSACTION;
    UPDATE tablename SET stringfield1 = NULL WHERE stringfield1 = '';
    <do some selects to make sure everything looks right>
    COMMIT;
    

    这只会重写需要重写的行,但需要对每个表进行多次遍历。

    【讨论】:

    • 为了安全起见,将其包装到事务中是个好主意。但是,在事务提交/回滚之前,更新的行会被阻止并发写访问。此外,您可以拥有 both:仅更新需要在 single 传递中更新的行。几年前我为此写了一个函数(刚刚更新了一点)。我的答案中的详细信息。
    • 谢谢.. 我目前有类似的东西,但它很快变得无法维护,每次架构更改时都需要更新。
    猜你喜欢
    • 2012-01-11
    • 2013-12-28
    • 1970-01-01
    • 1970-01-01
    • 2011-11-04
    • 2020-01-14
    • 2012-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多