すべてのテーブルのすべての列で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');
ALTER DATABASE your_db_name SET bytea_output = 'escape';
データベース(またはそのコピー)をダンプしてからダンプする必要がある場合があります。(これをpg_dump
コマンドだけに指定する方法は
これは、任意の列に特定の値が含まれているレコードを検索する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列)
select * from public.w where ctid = '(0,2)'; タイトル| 体| TSV ------- + -------- + --------------------- トト| 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)
~*
()よりも適切です。しかし、とにかくそれt.*
は上記の答えの一部ではありません。列区切りのため、列ごとの検索は、行を値として検索することとは異なります。
すべてのテーブルのすべての列で特定の値を検索するには
これは、正確に一致させる方法を定義していません。
また、何を返すかを正確に定義していません。
仮定:
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
?
検索パターンで特別な意味を持つ文字をエスケープしたい場合があります。見る:
そして誰かがそれが助けになると思ったら。@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')
);
新しいプロシージャを格納せずに、コードブロックを使用して実行し、発生のテーブルを取得できます。スキーマ、テーブル、または列名で結果をフィルタリングできます。
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;
関数を作成したり、外部ツールを使用したりせずにこれを実現する方法があります。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"}
ERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
format('%I.%I', table_schema::text, table_name::text)
ERROR: 42883: function format("unknown", character varying, character varying) does not exist
format()
機能さえ持っていません
@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;
-以下の関数は、データベース内の特定の文字列を含むすべてのテーブルをリストします
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