142

PostgreSQL の特定の値について、すべてのテーブルのすべての列を検索することは可能ですか?

同様の質問がOracle についてはこちらにあります。

4

9 に答える 9

155

データベースの内容をダンプしてから、 を使用するのはgrepどうですか?

$ pg_dump --data-only --inserts -U postgres your-db-name > a.tmp
$ grep United a.tmp
INSERT INTO countries VALUES ('US', 'United States');
INSERT INTO countries VALUES ('GB', 'United Kingdom');

同じユーティリティ pg_dump で、出力に列名を含めることができます。に変更--insertsするだけ--column-insertsです。そうすれば、特定の列名も検索できます。しかし、列名を探していた場合、おそらくデータではなくスキーマをダンプするでしょう。

$ pg_dump --data-only --column-inserts -U postgres your-db-name > a.tmp
$ grep country_code a.tmp
INSERT INTO countries (iso_country_code, iso_country_name) VALUES ('US', 'United  States');
INSERT INTO countries (iso_country_code, iso_country_name) VALUES ('GB', 'United Kingdom');
于 2011-03-18T11:55:59.880 に答える
93

これは、任意の列に特定の値が含まれているレコードを見つけるpl/pgsql 関数です。テキスト形式で検索する値、検索するテーブル名の配列 (デフォルトはすべてのテーブル)、およびスキーマ名の配列 (デフォルトはすべてのスキーマ名) を引数として取ります。

スキーマ、テーブルの名前、列の名前、および疑似列を含むテーブル構造を返しますctid(テーブル内の行の永続的ではない物理的な場所。システム列を参照してください)

CREATE OR REPLACE FUNCTION search_columns(
    needle text,
    haystack_tables name[] default '{}',
    haystack_schema name[] default '{}'
)
RETURNS table(schemaname text, tablename text, columnname text, rowctid text)
AS $$
begin
  FOR schemaname,tablename,columnname IN
      SELECT c.table_schema,c.table_name,c.column_name
      FROM information_schema.columns c
        JOIN information_schema.tables t ON
          (t.table_name=c.table_name AND t.table_schema=c.table_schema)
        JOIN information_schema.table_privileges p ON
          (t.table_name=p.table_name AND t.table_schema=p.table_schema
              AND p.privilege_type='SELECT')
        JOIN information_schema.schemata s ON
          (s.schema_name=t.table_schema)
      WHERE (c.table_name=ANY(haystack_tables) OR haystack_tables='{}')
        AND (c.table_schema=ANY(haystack_schema) OR haystack_schema='{}')
        AND t.table_type='BASE TABLE'
  LOOP
    FOR rowctid IN
      EXECUTE format('SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L',
       schemaname,
       tablename,
       columnname,
       needle
      )
    LOOP
      -- uncomment next line to get some progress report
      -- RAISE NOTICE 'hit in %.%', schemaname, tablename;
      RETURN NEXT;
    END LOOP;
 END LOOP;
END;
$$ language plpgsql;

同じ原則に基づいていますが、速度とレポートの改善がいくつか追加されているgithubのバージョンも参照してください。

テスト データベースでの使用例:

  • パブリック スキーマ内のすべてのテーブルを検索します。
select * from search_columns('foobar');
 スキーマ名 | テーブル名 | 列名 | 行ctid
------------+----------+------------+---------
 公開 | s3 | ユーザー名 | (0,11)
 公開 | s2 | relname | (7,29)
 公開 | w | ボディ | (0,2)
(3列)
  • 特定のテーブルを検索します。
select * from search_columns('foobar','{w}');
 スキーマ名 | テーブル名 | 列名 | 行ctid
------------+----------+------------+---------
 公開 | w | ボディ | (0,2)
(1行)
  • select から取得したテーブルのサブセットを検索します。
select * from search_columns('foobar', array(select table_name::name from information_schema.tables where table_name like 's%'), array['public']);
 スキーマ名 | テーブル名 | 列名 | 行ctid
------------+----------+------------+---------
 公開 | s2 | relname | (7,29)
 公開 | s3 | ユーザー名 | (0,11)
(2行)
  • 対応するベース テーブルと ctid を含む結果行を取得します。
select * from public.w where ctid='(0,2)';
 タイトル | ボディ | tsv         
-------+--------------------+---------------------
 トト | フーバー | 'foobar':2 'toto':1

バリアント

  • grep のような厳密な等価性ではなく、正規表現に対してテストするには、クエリの次の部分を実行します。

    SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L

    次のように変更できます。

    SELECT ctid FROM %I.%I WHERE cast(%I as text) ~ %L

  • 大文字と小文字を区別しない比較の場合、次のように記述できます。

    SELECT ctid FROM %I.%I WHERE lower(cast(%I as text)) = lower(%L)

于 2014-04-12T21:37:58.793 に答える
9

関数を作成したり、外部ツールを使用したりせずにこれを実現する方法があります。query_to_xml()別のクエリ内で動的にクエリを実行できるPostgres の機能を使用すると、多数のテーブルにわたってテキストを検索することができます。これは、すべてのテーブルの行数を取得するという私の答えに基づいています:

fooスキーマ内のすべてのテーブルで文字列を検索するには、次を使用できます。

with found_rows as (
  select format('%I.%I', table_schema, table_name) as table_name,
         query_to_xml(format('select to_jsonb(t) as table_row 
                              from %I.%I as t 
                              where t::text like ''%%foo%%'' ', table_schema, table_name), 
                      true, false, '') as table_rows
  from information_schema.tables 
  where table_schema = 'public'
)
select table_name, x.table_row
from found_rows f
  left join xmltable('//table/row' 
                     passing table_rows
                       columns
                         table_row text path 'table_row') as x on true

xmltableの使用には Postgres 10 以降が必要であることに注意してください。古いバージョンの Postgres の場合、これは xpath() を使用して行うこともできます。

with found_rows as (
  select format('%I.%I', table_schema, table_name) as table_name,
         query_to_xml(format('select to_jsonb(t) as table_row 
                              from %I.%I as t 
                              where t::text like ''%%foo%%'' ', table_schema, table_name), 
                      true, false, '') as table_rows
  from information_schema.tables 
  where table_schema = 'public'
)
select table_name, x.table_row
from found_rows f
   cross join unnest(xpath('/table/row/table_row/text()', table_rows)) as r(data)

共通テーブル式 ( WITH ...) は、便宜上のみ使用されます。publicスキーマ内のすべてのテーブルをループします。各テーブルに対して、次のクエリがquery_to_xml()関数を介して実行されます。

select to_jsonb(t)
from some_table t
where t::text like '%foo%';

where 句を使用して、コストのかかる XML コンテンツの生成が、検索文字列を含む行に対してのみ行われるようにします。これは次のようなものを返すかもしれません:

<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
  <table_row>{"id": 42, "some_column": "foobar"}</table_row>
</row>
</table>

行全体の変換jsonbが行われるため、結果でどの値がどの列に属しているかを確認できます。

上記は次のようなものを返す場合があります。

table_name   |   table_row
-------------+----------------------------------------
public.foo   |  {"id": 1, "some_column": "foobar"}
public.bar   |  {"id": 42, "another_column": "barfoo"}

Postgres 10+ のオンライン例

古い Postgres バージョンのオンライン例

于 2019-07-01T15:04:29.050 に答える
5

そして、誰かがそれが助けになると思うなら。これは@Daniel Véritéの関数で、検索で使用できる列の名前を受け入れる別のパラメーターがあります。このようにして、処理時間を短縮します。少なくとも私のテストでは、大幅に減少しました。

CREATE OR REPLACE FUNCTION search_columns(
    needle text,
    haystack_columns name[] default '{}',
    haystack_tables name[] default '{}',
    haystack_schema name[] default '{public}'
)
RETURNS table(schemaname text, tablename text, columnname text, rowctid text)
AS $$
begin
  FOR schemaname,tablename,columnname IN
      SELECT c.table_schema,c.table_name,c.column_name
      FROM information_schema.columns c
      JOIN information_schema.tables t ON
        (t.table_name=c.table_name AND t.table_schema=c.table_schema)
      WHERE (c.table_name=ANY(haystack_tables) OR haystack_tables='{}')
        AND c.table_schema=ANY(haystack_schema)
        AND (c.column_name=ANY(haystack_columns) OR haystack_columns='{}')
        AND t.table_type='BASE TABLE'
  LOOP
    EXECUTE format('SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L',
       schemaname,
       tablename,
       columnname,
       needle
    ) INTO rowctid;
    IF rowctid is not null THEN
      RETURN NEXT;
    END IF;
 END LOOP;
END;
$$ language plpgsql;

上記で作成した search_function の使用例を以下に示します。

SELECT * FROM search_columns('86192700'
    , array(SELECT DISTINCT a.column_name::name FROM information_schema.columns AS a
            INNER JOIN information_schema.tables as b ON (b.table_catalog = a.table_catalog AND b.table_schema = a.table_schema AND b.table_name = a.table_name)
        WHERE 
            a.column_name iLIKE '%cep%' 
            AND b.table_type = 'BASE TABLE'
            AND b.table_schema = 'public'
    )

    , array(SELECT b.table_name::name FROM information_schema.columns AS a
            INNER JOIN information_schema.tables as b ON (b.table_catalog = a.table_catalog AND b.table_schema = a.table_schema AND b.table_name = a.table_name)
        WHERE 
            a.column_name iLIKE '%cep%' 
            AND b.table_type = 'BASE TABLE'
            AND b.table_schema = 'public')
);
于 2014-09-18T14:25:17.907 に答える