web-dev-qa-db-ja.com

文字型の列に空の文字列が存在するかどうかを確認します

(ロジックの一部として)文字列をトリミングし、データベースに挿入する前に空の文字列をNULLに置き換えるアプリケーションがあります。これが確実に実行されるようにするための1つの方法は、VARCHARTEXT(または同様の)列を持つすべてのテーブルにCHECKを書き込むことだと思います。

データベース内のテキスト列に空の文字列が含まれているかどうかを確認する(データベースのメタデータからテーブルと列の名前を取得する)単純で汎用的なSQLクエリを作成する方法はありますか? ?

単一テーブルの機能

空の値のカウント(_''_)と、それらが_NOT NULL_で定義されているかどうかを指定して、指定されたテーブルのすべての文字型列を返します。

_CREATE OR REPLACE FUNCTION f_tbl_empty_status(_tbl regclass)
  RETURNS TABLE (tbl text, col text, empty_ct bigint, not_null bool) AS
$func$
DECLARE
   -- basic char types, possibly extend with citext, domains or custom types:
   _typ      CONSTANT regtype[] := '{text, bpchar, varchar, "\"char\""}';
   _sql      text;
   _col_arr  text[];
   _null_arr bool[];
BEGIN

-- Build command
SELECT INTO _col_arr, _null_arr, _sql
       array_agg(s.col)
     , array_agg(s.attnotnull)
     , '
SELECT $1
      ,unnest($2)
      ,unnest(ARRAY [count('
              || string_agg(s.col, ' = '''' OR NULL), count(')
                                || ' = '''' OR NULL)])
      ,unnest($3)
FROM   ' || _tbl
FROM  (
   SELECT quote_ident(attname) AS col, attnotnull
   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               -- include columns defined NOT NULL
   AND    atttypid = ANY(_typ)         -- only character types
   ORDER  BY attnum
   ) AS s;

-- Debug
-- RAISE NOTICE '%', _sql;

-- Execute
IF _sql IS NULL THEN
   -- do nothing, nothing to return
ELSE
   RETURN QUERY EXECUTE _sql
   USING  _tbl::text, _col_arr, _null_arr;
END IF;

END
$func$  LANGUAGE plpgsql;
_

コール:

_SELECT * FROM f_tbl_empty_status('tbl_name'); -- optionally schema-qualified
_

戻り値:

_tbl   | col        | empty_ct | not_null
------+------------+----------+---------
tbl1  | txt        | 0        | f
tbl1  | vc         | 3        | f
tbl1  | "oDD name" | 7        | f
_
  • Postgres 9.1以降で機能します。

  • 現在の_search_path_に従って、出力テーブル名は必要に応じて自動的にスキーマ修飾されます。

  • 出力テーブル名と列名は、必要に応じて自動的にエスケープされます。

  • _empty_ct_は、列の値が空の文字列である行の数です

  • _not_null_は、列が定義されているかどうかをレポートします_NOT NULL_(したがって、空の文字列をNULLに変換できません!)

  • 入力テーブル名は、オプションでスキーマ修飾できます。それ以外の場合は、デフォルトで現在の_search_path_になります。

  • ロールには、指定されたテーブルから実際に読み取る権限が必要です。

  • この関数は高度に最適化されており、指定されたテーブルに対してシングルスキャンを実行するだけで、all関連する列をチェックします。

  • SQLインジェクションに対して安全でなければなりません。

  • 並列unnest()を使用して、複雑なコードを多少簡略化します。

  • SO to 実際に空の文字列を置き換える-でこの関連する回答に興味があります詳細説明

スペース文字のみの文字列にも一致します

コメントどおりtrim(s.col, ' ') = ''はうまく機能します。
しかし、ここにショートカットがあります:

_s.col::char = ''
_

どうやって?
charcharacter(1)のエイリアスであり、ほとんど使用されない空白埋めタイプです。値は、長さ指定子(この場合は_1_ですが、無関係です)まで、右側にスペース文字が埋め込まれます。末尾のスペースは、このタイプでは事実上重要ではありません。したがって、_' '_は_''_または_' '_と同じです。ボイラ。そして、はい、それもより高速です。私はテストしました。

空白文字のみの文字列も検索するには(他の空白文字ではなく!)、上記の行にキャストを追加します。

_                  || string_agg(s.col, '::char = '''' OR NULL), count(')
                                    || '::char = '''' OR NULL)])_

スキーマ全体を報告するラッパー関数

_CREATE OR REPLACE FUNCTION f_schema_empty_status(_sch text DEFAULT 'public')
  RETURNS TABLE (tbl text, col text, empty_ct bigint, not_null bool) AS
$func$
DECLARE 
   _tbl regclass;
BEGIN

FOR _tbl IN
   SELECT c.oid
   FROM   pg_class c
   JOIN   pg_namespace n ON n.oid = c.relnamespace
   WHERE  n.nspname = _sch  -- 'public' by default
   -- AND    c.relname LIKE 'my_pattern%'  -- optionally filter table names
   AND    c.relkind = 'r'  -- regular tables only
   ORDER  BY relname
LOOP
   -- Debug
   -- RAISE NOTICE 'table: %', _tbl;

   RETURN QUERY
   SELECT * FROM f_tbl_empty_status(_tbl);
END LOOP;

END
$func$  LANGUAGE plpgsql;
_

コール:

_SELECT * FROM f_schema_empty_status();  -- defaults to 'public' without parameter
_

戻り値:

_tbl   | col        | empty_ct | not_null
------+------------+----------+---------
tbl1  | txt        | 0        | f
tbl1  | vc         | 3        | f
tbl1  | "oDD name" | 7        | f
tbl2  | some_text  | 123      | t
...
_

SQLフィドル。

2

これを行う最良の方法は、あなたが指摘したように、おそらくCHECKを介してDOMAIN制約を使用することです。

CREATE DOMAIN nonempty_string AS text 
CONSTRAINT non_empty CHECK (length(VALUE) > 0);

次に、ドメインを使用するALTER既存の列。

それが不可能な場合は、INFORMATION_SCHEMAにクエリを実行して、すべてのテーブルからターゲットタイプのすべての列を検索し、各列について、チェックするクエリを動的に生成する必要があります。これには、PL/PgSQLとEXECUTEステートメントを使用できます。 Stack Overflowの他の場所にも、この方法での使用例が多数あります。

単一のクエリを記述して必要なことを行うことはできません。うまくいきません。 information_schemaからのクエリ生成を使用する必要があります。

2
Craig Ringer