Apakah mungkin untuk mencari setiap kolom dari setiap tabel untuk nilai tertentu di PostgreSQL?
Pertanyaan serupa tersedia di sini untuk Oracle.
Apakah mungkin untuk mencari setiap kolom dari setiap tabel untuk nilai tertentu di PostgreSQL?
Pertanyaan serupa tersedia di sini untuk Oracle.
Jawaban:
Bagaimana jika membuang konten database, lalu menggunakan 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');
Utilitas yang sama, pg_dump, dapat menyertakan nama kolom dalam output. Ganti saja --inserts
ke --column-inserts
. Dengan begitu, Anda juga dapat mencari nama kolom tertentu. Tetapi jika saya mencari nama kolom, saya mungkin akan membuang skema, bukan datanya.
$ 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';
di database (atau salinannya) sebelum membuangnya. (Saya tidak melihat cara untuk menentukan ini hanya untuk pg_dump
perintah.)
Berikut adalah fungsi pl / pgsql yang menempatkan record di mana setiap kolom berisi nilai tertentu. Dibutuhkan sebagai argumen nilai yang akan dicari dalam format teks, larik nama tabel untuk ditelusuri (default untuk semua tabel) dan larik nama skema (default semua nama skema).
Ini mengembalikan struktur tabel dengan skema, nama tabel, nama kolom dan kolom semu ctid
(lokasi fisik baris yang tidak tahan lama dalam tabel, lihat Kolom Sistem )
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;
Lihat juga versi di github berdasarkan prinsip yang sama tetapi menambahkan beberapa peningkatan kecepatan dan pelaporan.
Contoh penggunaan dalam database pengujian:
pilih * dari search_columns ('foobar'); schemaname | tablename | nama kolom | rowctid ------------ + ----------- + ------------ + --------- publik | s3 | usename | (0,11) publik | s2 | relname | (7,29) publik | w | tubuh | (0,2) (3 baris)
pilih * dari search_columns ('foobar', '{w}'); schemaname | tablename | nama kolom | rowctid ------------ + ----------- + ------------ + --------- publik | w | tubuh | (0,2) (1 baris)
pilih * dari kolom_penelusuran ('foobar', larik (pilih nama_tabel :: nama dari informasi_schema.tables dengan nama_tabel seperti 's%'), larik ['publik']); schemaname | tablename | nama kolom | rowctid ------------ + ----------- + ------------ + --------- publik | s2 | relname | (7,29) publik | s3 | usename | (0,11) (2 baris)
pilih * dari public.w dimana ctid = '(0,2)'; judul | tubuh | tsv ------- + -------- + --------------------- toto | foobar | 'foobar': 2 'toto': 1
Untuk menguji dengan ekspresi reguler alih-alih persamaan ketat, seperti grep, ini bagian dari kueri:
SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L
dapat diubah menjadi:
SELECT ctid FROM %I.%I WHERE cast(%I as text) ~ %L
Untuk perbandingan tidak peka huruf besar / kecil, Anda dapat menulis:
SELECT ctid FROM %I.%I WHERE lower(cast(%I as text)) = lower(%L)
~*
lebih memadai daripada lebih rendah (). Tapi bagaimanapun t.*
itu bukan bagian dari jawaban di atas. Mencari kolom demi kolom tidak sama dengan mencari baris sebagai nilai karena pemisah kolom.
untuk mencari setiap kolom dari setiap tabel untuk nilai tertentu
Ini tidak menentukan cara mencocokkan dengan tepat.
Juga tidak menentukan apa yang harus dikembalikan dengan tepat.
Asumsi:
regclass
) dan tuple ID ( ctid
), karena itu paling sederhana.Berikut ini cara sederhana, cepat dan sedikit kotor:
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;
Panggilan:
SELECT * FROM search_whole_db('mypattern');
Berikan pola pencarian tanpa melampirkan %
.
Kenapa agak kotor?
Jika pemisah dan dekorator untuk baris dalam text
representasi dapat menjadi bagian dari pola penelusuran, mungkin terdapat kesalahan positif:
,
secara default()
"
\
dapat ditambahkan sebagai escape charDan representasi teks dari beberapa kolom mungkin bergantung pada pengaturan lokal - tetapi ambiguitas itu melekat pada pertanyaan, bukan pada solusi saya.
Setiap baris kualifikasi dikembalikan sekali saja, meskipun cocok beberapa kali (berbeda dengan jawaban lain di sini).
Ini mencari seluruh DB kecuali katalog sistem. Biasanya akan membutuhkan waktu lama untuk menyelesaikannya . Anda mungkin ingin membatasi ke skema / tabel tertentu (atau bahkan kolom) seperti yang ditunjukkan dalam jawaban lain. Atau tambahkan pemberitahuan dan indikator kemajuan, juga ditunjukkan dalam jawaban lain.
The regclass
jenis objek identifier direpresentasikan sebagai nama tabel, skema-kualifikasi di mana diperlukan untuk disambiguate sesuai dengan saat ini search_path
:
Apa itu ctid
?
Anda mungkin ingin melepaskan karakter dengan arti khusus dalam pola pencarian. Lihat:
Dan jika seseorang berpikir itu bisa membantu. Berikut adalah fungsi @Daniel Vérité, dengan parameter lain yang menerima nama kolom yang dapat digunakan dalam pencarian. Dengan cara ini mengurangi waktu pemrosesan. Setidaknya dalam pengujian saya itu berkurang banyak.
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;
Di bawah ini adalah contoh penggunaan fungsi_penelusuran yang dibuat di atas.
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')
);
Tanpa menyimpan prosedur baru, Anda dapat menggunakan blok kode dan mengeksekusi untuk mendapatkan tabel kejadian. Anda dapat memfilter hasil berdasarkan skema, tabel atau nama kolom.
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;
Ada cara untuk mencapai ini tanpa membuat fungsi atau menggunakan alat eksternal. Dengan menggunakan query_to_xml()
fungsi Postgres yang dapat secara dinamis menjalankan kueri di dalam kueri lain, dimungkinkan untuk mencari teks di banyak tabel. Ini didasarkan pada jawaban saya untuk mengambil rowcount untuk semua tabel :
Untuk mencari string foo
di semua tabel dalam skema, berikut ini dapat digunakan:
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
Perhatikan bahwa penggunaan xmltable
membutuhkan Postgres 10 atau yang lebih baru. Untuk versi Postgres yang lebih lama, ini juga dapat dilakukan dengan menggunakan 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)
Ekspresi tabel umum ( WITH ...
) hanya digunakan untuk kenyamanan. Ini loop melalui semua tabel dalam public
skema. Untuk setiap tabel, kueri berikut ini dijalankan melalui query_to_xml()
fungsi:
select to_jsonb(t)
from some_table t
where t::text like '%foo%';
Klausa where digunakan untuk memastikan pembuatan konten XML yang mahal hanya dilakukan untuk baris yang berisi string pencarian. Ini mungkin mengembalikan sesuatu seperti ini:
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<table_row>{"id": 42, "some_column": "foobar"}</table_row>
</row>
</table>
Konversi dari baris lengkap menjadi jsonb
dilakukan, sehingga pada hasilnya dapat dilihat nilai mana yang termasuk dalam kolom mana.
Di atas mungkin mengembalikan sesuatu seperti ini:
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()
fungsi
Berikut fungsi @Daniel Vérité dengan fungsi pelaporan kemajuan. Ini melaporkan kemajuan dalam tiga cara:
_
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;
- Fungsi di bawah ini akan menampilkan semua tabel yang berisi string tertentu dalam database
select TablesCount(‘StringToSearch’);
--Iterasi melalui semua tabel di database
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
- Mengembalikan hitungan tabel yang syaratnya terpenuhi. - Misalnya, jika teks yang dimaksud ada di salah satu bidang tabel, - maka jumlahnya akan lebih besar dari 0. Kita dapat menemukan notifikasi - di bagian Pesan dari penampil hasil di database 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;
--Dapatkan bidang dari setiap tabel. Membangun klausa where dengan semua kolom tabel.
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