Jak wyszukać konkretną wartość we wszystkich tabelach (PostgreSQL)?

Czy jest możliwe przeszukiwanie każdej kolumny każdej tabeli pod kątem określonej wartości w PostgreSQL?

Podobne pytanie jest dostępne tutaj dla Oracle.

Author: Erwin Brandstetter, 2011-03-18

7 answers

Może wrzucić zawartość bazy danych, a następnie użyć 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');

To samo narzędzie, pg_dump, może zawierać nazwy kolumn w wyjściu. Po prostu zmień --inserts na --column-inserts. W ten sposób możesz również wyszukiwać konkretne nazwy kolumn. Ale gdybym szukał nazw kolumn, pewnie wrzuciłbym schemat zamiast danych.

$ 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');
 87
Author: Mike Sherrill 'Cat Recall',
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-19 01:09:18

Oto funkcja pl / pgsql, która lokalizuje rekordy, w których każda kolumna zawiera określoną wartość. Jako argumenty przyjmuje wartość do przeszukiwania w formacie tekstowym, tablicę nazw tabel do przeszukiwania (domyślnie wszystkie tabele) oraz tablicę nazw schematów (domyślnie wszystkie nazwy schematów).

Zwraca strukturę tabeli ze schematem, nazwą tabeli, nazwą kolumny i pseudolumną ctid (nietrwałe fizyczne położenie wiersza w tabeli, zobacz System Kolumny )

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

EDIT : ten kod jest dla PG 9.1 lub nowszy. Możesz również chcieć wersję na github opartą na tej samej zasadzie, ale dodającą kilka usprawnień szybkości i raportowania.

Przykłady użycia w testowej bazie danych:

Szukaj we wszystkich tabelach w publicznym schemacie:

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)

Szukaj w konkretnej tabeli:

 select * from search_columns('foobar','{w}');
 schemaname | tablename | columnname | rowctid 
------------+-----------+------------+---------
 public     | w         | body       | (0,2)
(1 row)

Szukaj w podzbiorze tabel uzyskanych z select:

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)

Get a result row with the corresponding base tabela I i ctid:

select * from public.w where ctid='(0,2)';
 title |  body  |         tsv         
-------+--------+---------------------
 toto  | foobar | 'foobar':2 'toto':1

Aby ponownie przetestować Wyrażenie regularne zamiast ścisłej równości, jak grep, to:

SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L

Można zmienić na:

SELECT ctid FROM %I.%I WHERE cast(%I as text) ~ %L
 44
Author: Daniel Vérité,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-06 19:07:31

Jedyne narzędzie jakie znam to: SQL Workbench / J: http://www.sql-workbench.net/

Narzędzie oparte na Javie / JDBC, które oferuje specjalne (własne) "polecenie" SQL do przeszukiwania wszystkich (lub tylko wybranych) tabel w bazie danych:

Http://www.sql-workbench.eu/manual/wb-commands.html#command-search-data
http://www.sql-workbench.eu/wbgrepdata_png.html

 9
Author: a_horse_with_no_name,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-25 13:23:43

I jeśli ktoś myśli, że to może pomóc. Oto funkcja @ Daniel Vérité, z innym param, który akceptuje nazwy kolumn, które mogą być używane w wyszukiwaniu. W ten sposób skraca czas przetwarzania. Przynajmniej w moim teście znacznie się zmniejszyło.

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;

Poniżej znajduje się przykład użycia funkcji search_function utworzonej powyżej.

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')
);
 4
Author: Daniel A. Martinhao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-18 14:25:17

Bez zapisywania nowej procedury można użyć bloku kodu i wykonać, aby uzyskać tabelę zdarzeń. Wyniki można filtrować według schematu, nazwy tabeli lub kolumny.

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;
 3
Author: profimedica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-16 22:14:26

Oto funkcja @ Daniel Vérité z funkcją raportowania postępów. Raportuje postępy na trzy sposoby:

  1. by RAISE NOTICE;
  2. poprzez zmniejszenie wartości dostarczonej sekwencji {progress_seq} z {total number of colums to search in} down To 0;
  3. zapisując postęp wraz ze znalezionymi tabelami do pliku tekstowego, położony w 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;
 2
Author: alexkovelsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-22 12:12:42

Aby przeszukać każdą kolumnę każdej tabeli pod kątem określonej wartości

To nie określa, jak dokładnie dopasować.
Nie określa też dokładnie, co należy zwrócić.

Zakładając:

  • Znajdź dowolny wiersz z dowolną kolumną zawierające podaną wartość w swojej reprezentacji tekstowej-w przeciwieństwie do równania podanej wartości.
  • zwraca nazwę tabeli (regclass) i wskaźnik elementu (ctid), ponieważ to najprostsze.

Oto martwy prosty, szybki i nieco brudny sposób:

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;

Wywołanie:

SELECT * FROM search_whole_db('mypattern');

Podaj wzorzec wyszukiwania bez zamykania %.

Dlaczego lekko brudne?

Jeśli separatory i dekoratory dla wiersza w reprezentacji text mogą być częścią wzorca wyszukiwania, mogą występować fałszywe alarmy:

  • separator kolumn: , domyślnie
  • cały wiersz jest zamknięty w nawiasach: ()
  • niektóre wartości są zamknięte w podwójnych cudzysłowach "
  • \ może być dodany jako escape char

A tekstowa reprezentacja niektórych kolumn może zależeć od lokalnych ustawień - ale ta dwuznaczność jest nieodłączna dla pytania, a nie dla mojego rozwiązania.

Każdy wiersz kwalifikujący jest zwracany raz tylko, nawet jeśli pasuje kilka razy(w przeciwieństwie do innych odpowiedzi tutaj).

Przeszukuje cały DB z wyjątkiem katalogów systemowych. Będzie zazwyczaj poświęć dużo czasu, aby zakończyć. Możesz ograniczyć się do określonych schematów / tabel (a nawet kolumn), jak pokazano w innych odpowiedziach. Lub dodać ogłoszenia i wskaźnik postępu, również wykazany w innej odpowiedzi.

Typ identyfikatora obiektu regclass jest reprezentowany jako nazwa tabeli, schema-kwalifikowany w razie potrzeby do disambiguate zgodnie z bieżącym search_path:

Co to jest ctid?

Możesz chcieć uciec od znaków o specjalnym znaczeniu we wzorze wyszukiwania. Zobacz:

 2
Author: Erwin Brandstetter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-25 14:21:52