すべてのテーブル(PostgreSQL)で特定の値を検索するにはどうすればよいですか?


111

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

Oracle については、同様の質問がこちらから入手できます。


リンクされた質問に示されているツールまたは手順の実装をお探しですか?
a_horse_with_no_name 2011年

いいえ、すべてのフィールド/テーブルで特定の値を見つける最も簡単な方法です。
Sandro Munda

それで、外部ツールを使いたくないのですか?
a_horse_with_no_name 2011年

1
それが最も簡単な方法である場合=>外部ツールに問題はありません:-)
Sandro Munda

回答:


131

データベースの内容をダンプしてから使用するのは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');

5
+1は無料で簡単です。そして、もしあなたが構造が欲しいなら、pg_dumpはそれもすることができます。また、grepを使用しない場合は、ダンプされた構造やデータに対して必要なファイルコンテンツ検索ツールを使用してください。
Kuberchaun 2011年

テキストデータをgrepしたい場合(通常、より新しいバージョンのpostgresでエンコードされています)、ALTER DATABASE your_db_name SET bytea_output = 'escape';データベース(またはそのコピー)をダンプしてからダンプする必要がある場合があります。(これをpg_dumpコマンドだけに指定する方法は
わかり

詳細に説明できますか?すべてのテーブルで文字列「ABC」を検索する方法は?
Bhosale氏2017年

1
IntelliJを使用している場合は、dbを右クリックして、['pg_dumpでダンプ]または[データをファイルにダンプ]を選択できます
Laurens

3
これは、ディスクにダンプできないほど十分に大きいデータベースに対する有効な解決策ですか?
Govind Parmar

76

これは、任意の列に特定の値が含まれているレコードを検索する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');
 スキーマ名| テーブル名| 列名| rowctid
------------ + ----------- + ------------ + ---------
 公開| s3 | usename | (0,11)
 公開| s2 | relname | (7,29)
 公開| w | 体| (0、2)
(3列)
  • 特定のテーブルを検索:
 select * from search_columns( 'foobar'、 '{w}');
 スキーマ名| テーブル名| 列名| rowctid
------------ + ----------- + ------------ + ---------
 公開| w | 体| (0、2)
(1列)
  • 選択から取得したテーブルのサブセットを検索します。
select * from search_columns( 'foobar'、array(select table_name :: name from information_schema.tables where table_name like 's%')、array ['public']);
 スキーマ名| テーブル名| 列名| rowctid
------------ + ----------- + ------------ + ---------
 公開| s2 | relname | (7,29)
 公開| s3 | usename | (0,11)
(2列)
  • 対応するベーステーブルとctidを含む結果行を取得します。
select * from public.w where ctid = '(0,2)';
 タイトル| 体| TSV         
------- + -------- + ---------------------
 トト| foob​​ar | '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)


エラー:「デフォルト」またはその近くでの構文エラー3行目:haystack_tables name [] default '{}'(PostgreSQL 8.2.17を使用しており、アップグレードできません)
Henno

@辺野:はい、PG-9.1が必要です。これを明示するために編集されました。古いバージョンで使用するには、それを適応させる必要があります。
DanielVérité2014年

1
@Rajendra_Prasad:正規表現演算子には、大文字と小文字を区別しないバリアントがあります。lower ~*()よりも適切です。しかし、とにかくそれt.*は上記の答えの一部ではありません。列区切りのため、列ごとの検索は、行を値として検索することとは異なります。
ダニエルベリテ

2
これはschema-table-columnごとに1行のみを返します。
theGtknerd

1
どうもありがとう。この解決策は私にとって完璧に機能します。特定のURLを含む1000以上のテーブルのリストからテーブルを探す必要がありました。あなたは私の日を救った!
スニル

7

すべてのテーブルのすべての列で特定の値を検索するには

これは、正確に一致させる方法を定義していません。
また、何を返すかを正確に定義していません。

仮定:

  • 指定された値と等しいのではなく、テキスト表現に指定された値を含む任意の列持つ任意の行を検索します。
  • テーブル名(regclass)とタプルID(ctid)を返します。これが最も簡単だからです。

これは、非常にシンプルで、高速で、やや汚い方法です。

CREATE OR REPLACE FUNCTION search_whole_db(_like_pattern text)
  RETURNS TABLE(_tbl regclass, _ctid tid) AS
$func$
BEGIN
   FOR _tbl IN
      SELECT c.oid::regclass
      FROM   pg_class c
      JOIN   pg_namespace n ON n.oid = relnamespace
      WHERE  c.relkind = 'r'                           -- only tables
      AND    n.nspname !~ '^(pg_|information_schema)'  -- exclude system schemas
      ORDER BY n.nspname, c.relname
   LOOP
      RETURN QUERY EXECUTE format(
         'SELECT $1, ctid FROM %s t WHERE t::text ~~ %L'
       , _tbl, '%' || _like_pattern || '%')
      USING _tbl;
   END LOOP;
END
$func$  LANGUAGE plpgsql;

コール:

SELECT * FROM search_whole_db('mypattern');

囲まずに検索パターンを指定し%ます。

なぜ少し汚れているのですか?

text表現の行のセパレーターとデコレーターが検索パターンの一部である可能性がある場合、誤検知が発生する可能性があります。

  • 列セパレーター:,デフォルト
  • 行全体が括弧で囲まれています。()
  • 一部の値は二重引用符で囲まれています "
  • \ エスケープ文字として追加できます

また、一部の列のテキスト表現はローカル設定に依存する場合がありますが、そのあいまいさは質問に固有のものであり、私の解決策に固有のものではありません。

(他の回答とは対照的に)複数回一致する場合でも、条件を満たす行はそれぞれ1回だけ返されます。

これにより、システムカタログを除くDB全体が検索されます。通常、完了までに長い時間がかかります。他の回答で示されているように、特定のスキーマ/テーブル(または列)に制限したい場合があります。または、通知と進捗インジケーターを追加します。これも別の回答で示されています。

regclassオブジェクト識別子タイプは、テーブル名、スキーマ修飾電流に応じて明確にするために必要なように表されますsearch_path

とはctid

検索パターンで特別な意味を持つ文字をエスケープしたい場合があります。見る:


この素晴らしいソリューションは、lower()-'SELECT $ 1、ctid FROM%st WHERE lower(t :: text)~~ lower(%L)'でさらに優れています
Georgi Bonchev

5

そして誰かがそれが助けになると思ったら。@DanielVé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')
);

5

新しいプロシージャを格納せずに、コードブロックを使用して実行し、発生のテーブルを取得できます。スキーマ、テーブル、または列名で結果をフィルタリングできます。

DO $$
DECLARE
  value int := 0;
  sql text := 'The constructed select statement';
  rec1 record;
  rec2 record;
BEGIN
  DROP TABLE IF EXISTS _x;
  CREATE TEMPORARY TABLE _x (
    schema_name text, 
    table_name text, 
    column_name text,
    found text
  );
  FOR rec1 IN 
        SELECT table_schema, table_name, column_name
        FROM information_schema.columns 
        WHERE table_name <> '_x'
                AND UPPER(column_name) LIKE UPPER('%%')                  
                AND table_schema <> 'pg_catalog'
                AND table_schema <> 'information_schema'
                AND data_type IN ('character varying', 'text', 'character', 'char', 'varchar')
        LOOP
    sql := concat('SELECT ', rec1."column_name", ' AS "found" FROM ',rec1."table_schema" , '.',rec1."table_name" , ' WHERE UPPER(',rec1."column_name" , ') LIKE UPPER(''','%my_substring_to_find_goes_here%' , ''')');
    RAISE NOTICE '%', sql;
    BEGIN
        FOR rec2 IN EXECUTE sql LOOP
            RAISE NOTICE '%', sql;
            INSERT INTO _x VALUES (rec1."table_schema", rec1."table_name", rec1."column_name", rec2."found");
        END LOOP;
    EXCEPTION WHEN OTHERS THEN
    END;
  END LOOP;
  END; $$;

SELECT * FROM _x;

検索文字列はどこに指定しますか?それとも、これはDB全体をテーブルごとにダンプするだけですか?
jimtut 2017

1
文字列のパラメータを作成していません。それをハードコードしてブロックとして直接実行するか、そこからストアドプロシージャを作成できます。いずれの場合でも、検索する文字列は2つのパーセント記号の間にあります:WHERE UPPER( '、rec1。 "column_name"、')LIKE UPPER( '' '、' %% '、' '')
profimedica

5

関数を作成したり、外部ツールを使用したりせずにこれを実現する方法があります。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のオンライン例


古いPostgreSQLバージョンのコードを実行しようとすると、次のエラーが発生しますERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
Matt

おそらくそれらをキャストする必要がありますformat('%I.%I', table_schema::text, table_name::text)
。– a_horse_with_no_name

[OK]を、今私が持っている、ということで行わERROR: 42883: function format("unknown", character varying, character varying) does not exist
マット・

それからあなたの多くのPostgresバージョンはとても古く、そのidはformat()機能さえ持っていません
a_horse_with_no_name

Redshiftは8.3に基づいていると思いますか?
Matt

3

@DanielVéritéの進行状況レポート機能を備えた関数は次のとおりです。3つの方法で進行状況を報告します。

  1. RAISE NOTICE;
  2. 提供された{progress_seq}シーケンスの値を{検索する列の合計数}から0に減らす。
  3. 検出されたテーブルと共に進行状況をテキストファイルに書き込みます。テキストファイルは、c:\ windows \ temp \ {progress_seq} .txtにあります。

_

CREATE OR REPLACE FUNCTION search_columns(
    needle text,
    haystack_tables name[] default '{}',
    haystack_schema name[] default '{public}',
    progress_seq text default NULL
)
RETURNS table(schemaname text, tablename text, columnname text, rowctid text)
AS $$
DECLARE
currenttable text;
columnscount integer;
foundintables text[];
foundincolumns text[];
begin
currenttable='';
columnscount = (SELECT count(1)
      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 t.table_type='BASE TABLE')::integer;
PERFORM setval(progress_seq::regclass, columnscount);

  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 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;
      foundintables = foundintables || tablename;
      foundincolumns = foundincolumns || columnname;
      RAISE NOTICE 'FOUND! %, %, %, %', schemaname,tablename,columnname, rowctid;
    END IF;
         IF (progress_seq IS NOT NULL) THEN 
        PERFORM nextval(progress_seq::regclass);
    END IF;
    IF(currenttable<>tablename) THEN  
    currenttable=tablename;
     IF (progress_seq IS NOT NULL) THEN 
        RAISE NOTICE 'Columns left to look in: %; looking in table: %', currval(progress_seq::regclass), tablename;
        EXECUTE 'COPY (SELECT unnest(string_to_array(''Current table (column ' || columnscount-currval(progress_seq::regclass) || ' of ' || columnscount || '): ' || tablename || '\n\nFound in tables/columns:\n' || COALESCE(
        (SELECT string_agg(c1 || '/' || c2, '\n') FROM (SELECT unnest(foundintables) AS c1,unnest(foundincolumns) AS c2) AS t1)
        , '') || ''',''\n''))) TO ''c:\WINDOWS\temp\' || progress_seq || '.txt''';
    END IF;
    END IF;
 END LOOP;
END;
$$ language plpgsql;

3

-以下の関数は、データベース内の特定の文字列を含むすべてのテーブルをリストします

 select TablesCount(‘StringToSearch’);

-データベース内のすべてのテーブルを反復処理します

CREATE OR REPLACE FUNCTION **TablesCount**(_searchText TEXT)
RETURNS text AS 
$$ -- here start procedural part
   DECLARE _tname text;
   DECLARE cnt int;
   BEGIN
    FOR _tname IN SELECT table_name FROM information_schema.tables where table_schema='public' and table_type='BASE TABLE'  LOOP
         cnt= getMatchingCount(_tname,Columnames(_tname,_searchText));
                                RAISE NOTICE 'Count% ', CONCAT('  ',cnt,' Table name: ', _tname);
                END LOOP;
    RETURN _tname;
   END;
$$ -- here finish procedural part
LANGUAGE plpgsql; -- language specification

-条件が満たされたテーブルの数を返します。-たとえば、目的のテキストがテーブルのいずれかのフィールドに存在する場合、-カウントは0より大きくなります。通知は-postgresデータベースの結果ビューアのメッセージセクションにあります。

CREATE OR REPLACE FUNCTION **getMatchingCount**(_tname TEXT, _clause TEXT)
RETURNS int AS 
$$
Declare outpt text;
    BEGIN
    EXECUTE 'Select Count(*) from '||_tname||' where '|| _clause
       INTO outpt;
       RETURN outpt;
    END;
$$ LANGUAGE plpgsql;

-各テーブルのフィールドを取得します。テーブルのすべての列でwhere句を作成します。

CREATE OR REPLACE FUNCTION **Columnames**(_tname text,st text)
RETURNS text AS 
$$ -- here start procedural part
DECLARE
                _name text;
                _helper text;
   BEGIN
                FOR _name IN SELECT column_name FROM information_schema.Columns WHERE table_name =_tname LOOP
                                _name=CONCAT('CAST(',_name,' as VarChar)',' like ','''%',st,'%''', ' OR ');
                                _helper= CONCAT(_helper,_name,' ');
                END LOOP;
                RETURN CONCAT(_helper, ' 1=2');

   END;
$$ -- here finish procedural part
LANGUAGE plpgsql; -- language specification
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.