PostgreSQLの特定の値をすべてのテーブルのすべての列で検索できますか?
同様の質問が利用可能です here 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)
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
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;
EDIT:このコードはPG 9.1以降用です。また、同じ原則に基づいて github にバージョンを追加することもできますが、速度とレポートの改善が追加されます。
テストデータベースでの使用例:
select * from search_columns( 'foobar'); schemaname |テーブル名|列名| rowctid ------------ + ----------- + ------------ + ------ --- public | s3 | usename | (0,11) public | s2 | relname | (7,29) public | w |体| (0,2) (3行)
select * from search_columns( 'foobar'、 '{w}'); schemaname |テーブル名|列名| rowctid ------------ + ----------- + ------------ + ------ --- public | 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']); schemaname |テーブル名|列名| rowctid ------------ + ----------- + ------------ + ------ --- public | s2 | relname | (7,29) public | s3 | usename | (0,11) (2行)
select * from public.w where ctid = '(0,2)'; title |体| tsv ------- + -------- + --------------------- toto | foobar | '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)
私が知っている唯一のツールは、SQL Workbench/J: http://www.sql-workbench.net/ です。
データベース内のすべての(または選択したばかりの)テーブルを検索するための特別な(独自の)SQL "コマンド"を提供するJava/JDBCベースのツール:
http://www.sql-workbench.eu/manual/wb-commands.html#command-search-data
http://www.sql-workbench.eu/wbgrepdata_png.html
すべてのテーブルのすべての列で特定の値を検索する
これは、正確に一致する方法を定義しません。
Norは、何を正確に返すかを定義しません。
想定:
regclass
)とアイテムポインター(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
表現の行のセパレーターとデコレーターが検索パターンの一部になる可能性がある場合、誤検知が発生する可能性があります。
,
デフォルト()
"
\
はエスケープ文字として追加できますまた、一部の列のテキスト表現はローカル設定に依存する場合がありますが、その曖昧さは私の解決策ではなく質問に固有のものです。
修飾された各行が返されますonce(複数回一致した場合でも)(ここでの他の回答とは異なります)。
これにより、システムカタログを除くDB全体が検索されます。通常、終了するまでに長い時間がかかります。他の回答で示されているように、特定のスキーマ/テーブル(または列)に制限することができます。または、別の回答でも示されているように、通知と進行状況インジケーターを追加します。
regclass
オブジェクト識別子タイプは、現在のsearch_path
に従って明確にするために必要なスキーマ修飾されたテーブル名として表されます。
ctid
とは何ですか?
検索パターンで特別な意味を持つ文字をエスケープしたい場合があります。見る:
新しいプロシージャを保存せずに、コードブロックを使用して実行し、発生のテーブルを取得できます。スキーマ、テーブル、または列名で結果をフィルタリングできます。
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;
そして、誰かがそれが役立つと思うなら。これは@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')
);
-以下の関数は、データベース内の特定の文字列を含むすべてのテーブルをリストします
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
進行状況レポート機能を備えた@DanielVéritéの機能は次のとおりです。次の3つの方法で進捗状況を報告します。
_
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;
関数を作成したり、外部ツールを使用したりせずにこれを実現する方法があります。別のクエリ内でクエリを動的に実行できるPostgresのquery_to_xml()
関数を使用すると、多くのテーブルでテキストを検索できます。これは私の答えに基づいています すべてのテーブルの行数を取得するため :
スキーマ内のすべてのテーブルで文字列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"}