Bagaimana cara mencari nilai tertentu di semua tabel (PostgreSQL)?

111

Apakah mungkin untuk mencari setiap kolom dari setiap tabel untuk nilai tertentu di PostgreSQL?

Pertanyaan serupa tersedia di sini untuk Oracle.

Sandro Munda
sumber
Apakah Anda mencari alat atau untuk implementasi dari prosedur yang ditunjukkan dalam pertanyaan terkait?
a_horse_with_no_name
Tidak, hanya cara termudah untuk menemukan nilai tertentu di semua bidang / tabel.
Sandro Munda
Jadi Anda tidak ingin menggunakan alat eksternal?
a_horse_with_no_name
1
Jika itu cara termudah => ok untuk alat eksternal :-)
Sandro Munda

Jawaban:

131

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 --insertske --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');
Mike Sherrill 'Cat Recall'
sumber
5
+1 gratis dan sederhana. Dan jika Anda ingin struktur pg_dump dapat melakukannya juga. Juga jika grep bukan milik Anda, gunakan alat pencarian konten apa pun yang Anda inginkan pada struktur dan / atau data yang dibuang.
Kuberchaun
Jika Anda ingin grep data teks (yang biasanya dikodekan dalam versi postgres yang lebih baru), Anda mungkin perlu 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_dumpperintah.)
phils
bisa dijelaskan secara detail ..? Bagaimana cara mencari string 'ABC' ke semua tabel?
Tn. Bhosale
1
Jika Anda menggunakan IntelliJ, cukup klik kanan db Anda dan pilih "Dump with 'pg_dump'" atau "Dump data to file (s)"
Laurens
3
Bagaimana solusi ini valid untuk database apa pun yang cukup besar sehingga Anda tidak dapat membuangnya ke disk?
Govind Parmar
76

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:

  • Cari di semua tabel dalam skema publik:
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)
  • Cari di tabel tertentu:
 pilih * dari search_columns ('foobar', '{w}');
 schemaname | tablename | nama kolom | rowctid
------------ + ----------- + ------------ + ---------
 publik | w | tubuh | (0,2)
(1 baris)
  • Cari dalam subset tabel yang diperoleh dari pilihan:
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)
  • Dapatkan baris hasil dengan tabel dasar dan dan ctid yang sesuai:
pilih * dari public.w dimana ctid = '(0,2)';
 judul | tubuh | tsv         
------- + -------- + ---------------------
 toto | foobar | 'foobar': 2 'toto': 1

Varian

  • 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)

Daniel Vérité
sumber
EROR: kesalahan sintaks pada atau dekat "default" LINE 3: haystack_tables name [] default '{}' (Menggunakan PostgreSQL 8.2.17 dan tidak dapat memutakhirkan)
Henno
@Henno: ya itu membutuhkan PG-9.1. Diedit sekarang untuk membuatnya eksplisit. Untuk menggunakannya dengan versi yang lebih lama, Anda harus menyesuaikannya.
Daniel Vérité
1
@Rajendra_Prasad: operator ekspresi reguler memiliki varian tidak peka huruf besar / kecil: ~*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.
Daniel Vérité
2
Ini hanya mengembalikan satu baris per skema-tabel-kolom.
theGtknerd
1
Terima kasih banyak. Solusi ini bekerja dengan sempurna untuk saya. Saya harus menemukan tabel dalam daftar lebih dari 1000 tabel yang berisi url tertentu. Anda menyelamatkan hari saya !.
Sunil
7

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:

  • Temukan baris apa pun dengan kolom apa pun yang berisi nilai yang diberikan dalam representasi teksnya - sebagai lawan dari menyamakan nilai yang diberikan.
  • Kembalikan nama tabel ( 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 textrepresentasi dapat menjadi bagian dari pola penelusuran, mungkin terdapat kesalahan positif:

  • pemisah kolom: ,secara default
  • seluruh baris diapit tanda kurung:()
  • beberapa nilai diapit tanda kutip ganda "
  • \ dapat ditambahkan sebagai escape char

Dan 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 regclassjenis 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:

Erwin Brandstetter
sumber
Solusi hebat ini bahkan lebih baik dengan lower () - 'SELECT $ 1, ctid FROM% st WHERE lower (t :: text) ~~ lower (% L)'
Georgi Bonchev
5

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')
);
Daniel A. Martinhao
sumber
5

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;
profimedica
sumber
Di mana Anda menentukan string pencarian? Atau apakah ini hanya membuang seluruh DB, tabel demi tabel?
jimtut
1
Saya tidak membuat parameter untuk string tersebut. Anda dapat melakukan hardcode dan menjalankannya secara langsung sebagai blok atau membuat prosedur tersimpan darinya. Bagaimanapun, string Anda yang akan dicari berada di sini di antara tanda dua persen: WHERE UPPER (', rec1. "Column_name",') LIKE UPPER ('' ',' %% ',' '')
profimedica
5

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 foodi 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 xmltablemembutuhkan 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 publicskema. 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 jsonbdilakukan, 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"}

Contoh online untuk Postgres 10+

Contoh online untuk versi Postgres yang lebih lama

seekor kuda tanpa nama
sumber
Saya mencoba menjalankan kode untuk versi PostgreSQL yang lebih lama dan saya mendapatkan kesalahan berikutERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
Matt
Anda mungkin perlu mentransmisikannya:format('%I.%I', table_schema::text, table_name::text)
a_horse_with_no_name
Oke, lakukan itu, sekarang saya punyaERROR: 42883: function format("unknown", character varying, character varying) does not exist
Matt
Maka banyak versi Postgres Anda yang sangat tua, bahkan id itu tidak memiliki format()fungsi
a_horse_with_no_name
Saya pikir Redshift didasarkan pada 8.3?
Matt
3

Berikut fungsi @Daniel Vérité dengan fungsi pelaporan kemajuan. Ini melaporkan kemajuan dalam tiga cara:

  1. dengan PEMBERITAHUAN NAIK;
  2. dengan mengurangi nilai urutan {progress_seq} yang disediakan dari {total jumlah kolom yang akan dicari} turun menjadi 0;
  3. dengan menuliskan progres beserta tabel yang ditemukan ke dalam file teks, terletak di 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;
alexkovelsky
sumber
3

- 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
Ganesh
sumber