PostgreSQL の特定の値について、すべてのテーブルのすべての列を検索することは可能ですか?
同様の質問がOracle についてはこちらにあります。
PostgreSQL の特定の値について、すべてのテーブルのすべての列を検索することは可能ですか?
同様の質問がOracle についてはこちらにあります。
データベースの内容をダンプしてから、 を使用するのは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');
これは、任意の列に特定の値が含まれているレコードを見つける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 * 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行)
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)
関数を作成したり、外部ツールを使用したりせずにこれを実現する方法があります。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"}
そして、誰かがそれが助けになると思うなら。これは@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')
);