如何在所有表中搜索特定的值(PostgreSQL) ?

是否可以在 PostgreSQL 中搜索 每个表的每一列以获得特定的值?

Oracle 也可以使用类似的问题 给你

185305 次浏览

如何转储数据库的内容,然后使用 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;

另请参阅基于相同原则的 < strong > version on github ,但增加了一些速度和报告改进。

在测试数据库中使用的示例:

  • 搜索公共架构中的所有表:
select * from search_columns('foobar');
schemaname | tablename | columnname | rowctid
------------+-----------+------------+---------
public     | s3        | usename    | (0,11)
public     | s2        | relname    | (7,29)
public     | w         | body       | (0,2)
(3 rows)
  • 在特定表中搜索:
select * from search_columns('foobar','{w}');
schemaname | tablename | columnname | rowctid
------------+-----------+------------+---------
public     | w         | body       | (0,2)
(1 row)
  • 搜索从选择中获得的表的子集:
select * from search_columns('foobar', array(select table_name::name from information_schema.tables where table_name like 's%'), array['public']);
schemaname | tablename | columnname | rowctid
------------+-----------+------------+---------
public     | s2        | relname    | (7,29)
public     | s3        | usename    | (0,11)
(2 rows)
  • 获取一个包含相应基表和 ctid 的结果行:
select * from public.w where ctid='(0,2)';
title |  body  |         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)

下面是@Daniel Vérité 的功能和进度报告功能。 它以三种方式报告进展情况:

  1. 加价通知;
  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;

如果有人觉得这有用。下面是@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;

Bellow 是上面创建的 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;

在每个表的每一列中搜索特定的值

这并没有定义如何精确匹配。
它也没有确切地定义返回什么。

假设:

  • 找到任何一行中的任何一列 < em > 包含 ,在其文本表示中找到给定的值——与 相等中的给定值相反。
  • 返回表名(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表示的行的分隔符和修饰符可以作为搜索模式的一部分,那么可能存在误报:

  • 列分隔符: 默认为 ,
  • 整行用括号括起来: ()
  • 有些值用双引号 "括起来
  • 可以添加 \作为转义字符

某些列的文本表示形式可能取决于本地设置——但这种模糊性是问题的固有特征,而不是我的解决方案。

每个符合条件的行只返回 一次,即使它匹配多次(与此处的其他答案相反)。

这将搜索除系统目录以外的整个数据库。通常是 要花很长时间才能完成。您可能希望像在其他答案中演示的那样限制到某些模式/表(甚至列)。或者添加通知和进度指示器,也可以在另一个答案中演示。

regclass对象标识符类型表示为表名,在需要根据当前 search_path消除歧义的地方使用模式限定:

什么是 ctid

您可能希望在搜索模式中转义具有特殊含义的字符。参见:

——下面的函数将列出数据库中包含特定字符串的所有表

 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 数据库的结果查看器的 Messages 部分。

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

有一种方法可以在不创建函数或使用外部工具的情况下实现这一点。通过使用 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"}

Postgres 10 + 的在线示例

旧版 Postgres 的在线示例

如果您使用 IntelliJ 添加您的数据库到数据库视图,然后右键单击数据库并选择全文搜索,它将列出所有表和所有字段为您的特定文本。