เป็นไปได้ไหมที่จะค้นหา ทุกคอลัมน์ของทุกตารางเพื่อหาค่าเฉพาะใน 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 ด้วยตามหลักการเดียวกัน แต่เพิ่มการปรับปรุงความเร็วและการรายงานบางอย่าง
ตัวอย่างการใช้งานในฐานข้อมูลทดสอบ:
เลือก * จาก search_columns ('foobar'); schemaname | ชื่อตาราง | ชื่อคอลัมน์ | rowctid ------------ + ----------- + ------------ + --------- สาธารณะ | s3 | ชื่อผู้ใช้ | (0,11) สาธารณะ | s2 | relname | (7,29) สาธารณะ | w | ร่างกาย | (0,2) (3 แถว)
เลือก * จาก search_columns ('foobar', '{w}'); schemaname | ชื่อตาราง | ชื่อคอลัมน์ | rowctid ------------ + ----------- + ------------ + --------- สาธารณะ | w | ร่างกาย | (0,2) (1 แถว)
เลือก * จาก search_columns ('foobar', array (เลือก table_name :: name จาก information_schema.tables โดยที่ table_name เช่น 's%'), array ['public']); schemaname | ชื่อตาราง | ชื่อคอลัมน์ | rowctid ------------ + ----------- + ------------ + --------- สาธารณะ | s2 | relname | (7,29) สาธารณะ | s3 | ชื่อผู้ใช้ | (0,11) (2 แถว)
เลือก * จาก public.w โดยที่ ctid = '(0,2)'; ชื่อเรื่อง | ร่างกาย | 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)
~*
เพียงพอมากกว่าต่ำกว่า () แต่อย่างไรก็ตามt.*
ไม่ใช่ส่วนหนึ่งของคำตอบข้างต้น การค้นหาคอลัมน์ตามคอลัมน์ไม่เหมือนกับการค้นหาแถวเป็นค่าเนื่องจากตัวคั่นคอลัมน์
เพื่อค้นหาทุกคอลัมน์ของทุกตารางเพื่อหาค่าเฉพาะ
สิ่งนี้ไม่ได้กำหนดวิธีการจับคู่ทั้งหมด
และไม่ได้กำหนดสิ่งที่จะส่งคืนอย่างแน่นอน
สมมติว่า:
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
เป็นตัวแทนสามารถเป็นส่วนหนึ่งของรูปแบบการค้นหาอาจมีผลบวกปลอม:
,
โดยค่าเริ่มต้น()
"
\
อาจถูกเพิ่มเป็นอักขระหนีและการแสดงข้อความของคอลัมน์บางคอลัมน์อาจขึ้นอยู่กับการตั้งค่าในเครื่อง - แต่ความคลุมเครือนั้นมีอยู่ในคำถามไม่ใช่วิธีแก้ปัญหาของฉัน
แต่ละแถวที่มีคุณสมบัติจะถูกส่งคืนเพียงครั้งเดียวแม้ว่าจะตรงกันหลายครั้งก็ตาม (ตรงข้ามกับคำตอบอื่น ๆ ที่นี่)
สิ่งนี้ค้นหา DB ทั้งหมดยกเว้นแค็ตตาล็อกระบบ โดยทั่วไปจะใช้เวลานานที่จะเสร็จสิ้น คุณอาจต้องการ จำกัด เฉพาะสคีมา / ตาราง (หรือแม้แต่คอลัมน์) เหมือนที่แสดงในคำตอบอื่น ๆ หรือเพิ่มการแจ้งเตือนและตัวบ่งชี้ความคืบหน้าซึ่งแสดงให้เห็นในคำตอบอื่น
regclass
ประเภทระบุวัตถุจะแสดงเป็นชื่อตารางคีที่มีคุณสมบัติที่จำเป็นที่จะต้องกระจ่างตามที่ปัจจุบันsearch_path
:
คืออะไรctid
?
คุณอาจต้องการหลีกเลี่ยงอักขระที่มีความหมายพิเศษในรูปแบบการค้นหา ดู:
และหากมีใครคิดว่ามันสามารถช่วยได้ นี่คือฟังก์ชันของ @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 เป็นตัวอย่างของการใช้งานฟังก์ชันค้นหาที่สร้างขึ้นด้านบน
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 ที่สามารถเรียกใช้แบบสอบถามภายในแบบสอบถามอื่นแบบไดนามิกทำให้สามารถค้นหาข้อความในตารางต่างๆได้ นี่เป็นไปตามคำตอบของฉันในการดึง rowcount สำหรับตารางทั้งหมด :
หากต้องการค้นหาสตริง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%';
ตำแหน่งที่ใช้คำสั่งเพื่อให้แน่ใจว่าการสร้างเนื้อหา 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()
ฟังก์ชัน
นี่คือฟังก์ชันของ @Daniel Véritéพร้อมฟังก์ชันการรายงานความคืบหน้า รายงานความคืบหน้าในสามวิธี:
_
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