Có thể tìm kiếm mọi cột của mọi bảng cho một giá trị cụ thể trong PostgreSQL không?
Một câu hỏi tương tự có sẵn ở đây cho Oracle.
Có thể tìm kiếm mọi cột của mọi bảng cho một giá trị cụ thể trong PostgreSQL không?
Một câu hỏi tương tự có sẵn ở đây cho Oracle.
Câu trả lời:
Làm thế nào về việc kết xuất nội dung của cơ sở dữ liệu, sau đó sử dụng 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');
Tiện ích tương tự, pg_dump, có thể bao gồm tên cột trong đầu ra. Chỉ cần thay đổi --inserts
thành --column-inserts
. Bằng cách đó, bạn cũng có thể tìm kiếm các tên cột cụ thể. Nhưng nếu tôi đang tìm kiếm tên cột, có lẽ tôi sẽ kết xuất lược đồ thay vì dữ liệu.
$ 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';
vào cơ sở dữ liệu (hoặc một bản sao của nó) trước khi kết xuất nó. (Tôi không nhìn thấy một cách để xác định điều này chỉ dành riêng cho một pg_dump
lệnh.)
Đây là một hàm pl / pgsql định vị các bản ghi trong đó bất kỳ cột nào chứa một giá trị cụ thể. Nó nhận các đối số là giá trị để tìm kiếm ở định dạng văn bản, một mảng tên bảng để tìm kiếm (mặc định cho tất cả các bảng) và một mảng tên lược đồ (mặc định cho tất cả các tên lược đồ).
Nó trả về cấu trúc bảng với giản đồ, tên bảng, tên cột và cột giả ctid
(vị trí vật lý không bền của hàng trong bảng, xem Cột Hệ thống )
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;
Xem thêm phiên bản trên github dựa trên nguyên tắc tương tự nhưng bổ sung một số cải tiến về tốc độ và báo cáo.
Ví dụ về việc sử dụng trong cơ sở dữ liệu thử nghiệm:
select * from search_columns ('foobar'); tên lược đồ | tên bảng | tên cột | rowctid ------------ + ----------- + ------------ + --------- công khai | s3 | tên sử dụng | (0,11) công khai | s2 | tên đăng nhập | (7,29) công khai | w | cơ thể | (0,2) (3 hàng)
select * from search_columns ('foobar', '{w}'); tên lược đồ | tên bảng | tên cột | rowctid ------------ + ----------- + ------------ + --------- công khai | w | cơ thể | (0,2) (1 hàng)
select * from search_columns ('foobar', array (chọn table_name :: name từ information_schema.tables nơi tên_bảng như 's%'), array ['public']); tên lược đồ | tên bảng | tên cột | rowctid ------------ + ----------- + ------------ + --------- công khai | s2 | tên đăng nhập | (7,29) công khai | s3 | tên sử dụng | (0,11) (2 hàng)
select * from public.w where ctid = '(0,2)'; tiêu đề | cơ thể | tsv ------- + -------- + --------------------- toto | foobar | 'foobar': 2 'toto': 1
Để kiểm tra biểu thức chính quy thay vì bình đẳng nghiêm ngặt, như grep, phần này của truy vấn:
SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L
có thể được thay đổi thành:
SELECT ctid FROM %I.%I WHERE cast(%I as text) ~ %L
Đối với so sánh không phân biệt chữ hoa chữ thường, bạn có thể viết:
SELECT ctid FROM %I.%I WHERE lower(cast(%I as text)) = lower(%L)
~*
đầy đủ hơn so với low (). Nhưng dù sao thì đó t.*
không phải là một phần của câu trả lời trên. Tìm kiếm theo cột không giống như tìm kiếm hàng dưới dạng giá trị vì các dấu phân cách cột.
để tìm kiếm mọi cột của mọi bảng cho một giá trị cụ thể
Điều này không xác định cách đối sánh chính xác.
Nó cũng không xác định chính xác những gì sẽ trả về.
Giả định:
regclass
) và ID tuple ( ctid
), vì đó là cách đơn giản nhất.Đây là một cách đơn giản, nhanh chóng và hơi bẩn:
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;
Gọi:
SELECT * FROM search_whole_db('mypattern');
Cung cấp mẫu tìm kiếm mà không cần kèm theo %
.
Tại sao hơi bẩn?
Nếu dấu phân tách và dấu trang trí cho hàng trong text
biểu diễn có thể là một phần của mẫu tìm kiếm, thì có thể có kết quả dương tính giả:
,
theo mặc định()
"
\
có thể được thêm vào như thoát charVà việc biểu diễn văn bản của một số cột có thể phụ thuộc vào cài đặt cục bộ - nhưng sự không rõ ràng đó là cố hữu đối với câu hỏi, không phải giải pháp của tôi.
Mỗi hàng đủ điều kiện chỉ được trả lại một lần , ngay cả khi nó khớp nhiều lần (trái ngược với các câu trả lời khác ở đây).
Điều này tìm kiếm toàn bộ DB ngoại trừ danh mục hệ thống. Thông thường sẽ mất nhiều thời gian để hoàn thành . Bạn có thể muốn giới hạn đối với một số lược đồ / bảng nhất định (hoặc thậm chí là các cột) như được trình bày trong các câu trả lời khác. Hoặc thêm thông báo và chỉ báo tiến độ, cũng được trình bày trong một câu trả lời khác.
Các regclass
loại định danh đối tượng được biểu diễn dưới dạng tên bảng, lược đồ trình độ khi cần thiết để disambiguate theo hiện tại search_path
:
Là ctid
gì?
Bạn có thể muốn thoát các ký tự có ý nghĩa đặc biệt trong mẫu tìm kiếm. Xem:
Và nếu ai đó nghĩ rằng nó có thể giúp ích. Đây là chức năng của @Daniel Vérité, với một tham số khác chấp nhận tên của các cột có thể được sử dụng trong tìm kiếm. Bằng cách này, nó làm giảm thời gian xử lý. Ít nhất trong thử nghiệm của tôi, nó đã giảm đi rất nhiều.
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;
Dưới đây là một ví dụ về cách sử dụng hàm tìm kiếm được tạo ở trên.
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')
);
Nếu không lưu trữ một thủ tục mới, bạn có thể sử dụng một khối mã và thực thi để có được một bảng các lần xuất hiện. Bạn có thể lọc kết quả theo tên lược đồ, bảng hoặc cột.
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;
Có một cách để đạt được điều này mà không cần tạo một hàm hoặc sử dụng một công cụ bên ngoài. Bằng cách sử dụng query_to_xml()
chức năng của Postgres có thể chạy động một truy vấn bên trong một truy vấn khác, bạn có thể tìm kiếm một văn bản trên nhiều bảng. Điều này dựa trên câu trả lời của tôi để truy xuất số lượng hàng cho tất cả các bảng :
Để tìm kiếm chuỗi foo
trên tất cả các bảng trong một lược đồ, có thể sử dụng cách sau:
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
Lưu ý rằng việc sử dụng xmltable
yêu cầu Postgres 10 hoặc mới hơn. Đối với phiên bản Postgres cũ hơn, điều này cũng có thể được thực hiện bằng cách sử dụng 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)
Biểu thức bảng chung ( WITH ...
) chỉ được sử dụng để thuận tiện. Nó lặp qua tất cả các bảng trong public
lược đồ. Đối với mỗi bảng, truy vấn sau được chạy thông qua query_to_xml()
hàm:
select to_jsonb(t)
from some_table t
where t::text like '%foo%';
Mệnh đề where được sử dụng để đảm bảo việc tạo nội dung XML đắt tiền chỉ được thực hiện cho các hàng có chứa chuỗi tìm kiếm. Điều này có thể trả về một cái gì đó như thế này:
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<table_row>{"id": 42, "some_column": "foobar"}</table_row>
</row>
</table>
Việc chuyển đổi hàng hoàn chỉnh thành jsonb
được thực hiện để trong kết quả, người ta có thể thấy giá trị nào thuộc về cột nào.
Ở trên có thể trả lại một cái gì đó như thế này:
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()
chức năng
Đây là chức năng của @Daniel Vérité với chức năng báo cáo tiến độ. Nó báo cáo tiến trình theo ba cách:
_
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;
- Hàm dưới đây sẽ liệt kê tất cả các bảng có chứa một chuỗi cụ thể trong cơ sở dữ liệu
select TablesCount(‘StringToSearch’);
--Chuyển qua tất cả các bảng trong cơ sở dữ liệu
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
- Trả về số bảng mà điều kiện được đáp ứng. - Ví dụ: nếu văn bản dự định tồn tại trong bất kỳ trường nào của bảng, - thì số lượng sẽ lớn hơn 0. Chúng ta có thể tìm thấy thông báo - trong phần Tin nhắn của trình xem kết quả trong cơ sở dữ liệu 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;
--Nhận các trường của mỗi bảng. Tạo mệnh đề where với tất cả các cột của bảng.
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