Come cercare un valore specifico in tutte le tabelle (PostgreSQL)?


111

È possibile cercare in ogni colonna di ogni tabella un valore particolare in PostgreSQL?

Una domanda simile è disponibile qui per Oracle.


Cerchi uno strumento o un'implementazione delle procedure riportate nella domanda collegata?
a_horse_with_no_name

No, solo il modo più semplice per trovare un valore specifico in tutti i campi / tabelle.
Sandro Munda

Quindi non vuoi usare uno strumento esterno?
a_horse_with_no_name

1
Se è il modo più semplice => ok per uno strumento esterno :-)
Sandro Munda

Risposte:


131

Che ne dici di scaricare il contenuto del database, quindi utilizzarlo 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');

La stessa utilità, pg_dump, può includere nomi di colonne nell'output. Basta passare --insertsa --column-inserts. In questo modo puoi anche cercare nomi di colonne specifici. Ma se stavo cercando i nomi delle colonne, probabilmente scaricherò lo schema invece dei dati.

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

5
+1 gratuito e semplice. E se vuoi la struttura, pg_dump può farlo anche tu. Inoltre, se grep non fa per te, usa lo strumento di ricerca del contenuto dei file che desideri sulle strutture e / o sui dati scaricati.
Kuberchaun

Se si desidera eseguire il grep dei dati di testo (che in genere sono codificati nelle versioni più recenti di postgres), potrebbe essere necessario farlo ALTER DATABASE your_db_name SET bytea_output = 'escape';sul database (o su una sua copia) prima di scaricarlo. (Non vedo un modo per specificarlo solo per un pg_dumpcomando.)
phils

puoi spiegare in dettaglio ..? Come cercare la stringa "ABC" in tutte le tabelle?
Mr. Bhosale

1
Se stai usando IntelliJ puoi semplicemente fare clic con il pulsante destro del mouse sul tuo db e selezionare "Dump with 'pg_dump'" o "Dump data to file (s)"
Laurens

3
In che modo questa è una soluzione valida per qualsiasi database sufficientemente grande da non poterlo scaricare sul disco?
Govind Parmar

76

Ecco una funzione pl / pgsql che individua i record in cui ogni colonna contiene un valore specifico. Prende come argomenti il ​​valore da cercare in formato testo, un array di nomi di tabelle in cui cercare (il valore predefinito è tutte le tabelle) e un array di nomi di schema (predefinito tutti i nomi di schema).

Restituisce una struttura di tabella con schema, nome della tabella, nome della colonna e pseudo-colonna ctid(posizione fisica non durevole della riga nella tabella, vedere Colonne di sistema )

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;

Vedi anche la versione su GitHub basata sullo stesso principio ma con l'aggiunta di alcuni miglioramenti di velocità e reportistica.

Esempi di utilizzo in un database di test:

  • Cerca in tutte le tabelle all'interno dello schema pubblico:
seleziona * da search_columns ('foobar');
 schemaname | tablename | nomecolonna | rowctid
------------ + ----------- + ------------ + ---------
 pubblico | s3 | nome utente | (0,11)
 pubblico | s2 | relname | (7,29)
 pubblico | w | corpo | (0,2)
(3 righe)
  • Cerca in una tabella specifica:
 seleziona * da search_columns ('foobar', '{w}');
 schemaname | tablename | nomecolonna | rowctid
------------ + ----------- + ------------ + ---------
 pubblico | w | corpo | (0,2)
(1 riga)
  • Cerca in un sottoinsieme di tabelle ottenute da una selezione:
select * from search_columns ('foobar', array (select table_name :: name from information_schema.tables dove table_name like 's%'), array ['public']);
 schemaname | tablename | nomecolonna | rowctid
------------ + ----------- + ------------ + ---------
 pubblico | s2 | relname | (7,29)
 pubblico | s3 | nome utente | (0,11)
(2 righe)
  • Ottieni una riga dei risultati con la tabella di base corrispondente e e ctid:
seleziona * da public.w dove ctid = '(0,2)';
 titolo | corpo | TSV         
------- + -------- + ---------------------
 toto | foobar | 'foobar': 2 'toto': 1

varianti

  • Per eseguire il test su un'espressione regolare anziché sull'uguaglianza rigorosa, come grep, questa parte della query:

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

    può essere modificato in:

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

  • Per confronti senza distinzione tra maiuscole e minuscole, potresti scrivere:

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


ERRORE: errore di sintassi in corrispondenza o vicino a "default" LINEA 3: haystack_tables name [] default '{}' (utilizzo di PostgreSQL 8.2.17 e impossibile eseguire l'aggiornamento)
Henno

@Henno: sì, richiede PG-9.1. Modificato ora per renderlo esplicito. Per usarlo con le versioni precedenti, dovrai adattarlo.
Daniel Vérité

1
@Rajendra_Prasad: l'operatore di espressione regolare ha una variante senza distinzione tra maiuscole e minuscole: ~*più adeguata di lower (). Ma comunque t.*non fa parte della risposta di cui sopra. La ricerca colonna per colonna non equivale alla ricerca della riga come valore a causa dei separatori di colonna.
Daniel Vérité

2
Restituisce solo una riga per colonna tabella-schema.
theGtknerd

1
Molte grazie. Questa soluzione funziona perfettamente per me. Ho dovuto individuare una tabella in un elenco di oltre 1000 tabelle che contiene un URL specifico. Mi hai salvato la giornata!
Sunil

7

per cercare in ogni colonna di ogni tabella un valore particolare

Questo non definisce come abbinare esattamente.
Né definisce esattamente cosa restituire.

assumendo:

  • Trova qualsiasi riga con qualsiasi colonna contenente il valore dato nella sua rappresentazione testuale, invece di eguagliare il valore dato.
  • Restituisce il nome della tabella ( regclass) e l'ID della tupla ( ctid), perché è il più semplice.

Ecco un modo semplice, veloce e leggermente sporco:

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;

Chiamata:

SELECT * FROM search_whole_db('mypattern');

Fornire il modello di ricerca senza racchiudere %.

Perché leggermente sporco?

Se separatori e decoratori per la riga nella textrappresentazione possono far parte del modello di ricerca, possono esserci falsi positivi:

  • separatore di colonna: ,per impostazione predefinita
  • l'intera riga è racchiusa tra parentesi:()
  • alcuni valori sono racchiusi tra virgolette doppie "
  • \ può essere aggiunto come carattere di escape

E la rappresentazione testuale di alcune colonne può dipendere dalle impostazioni locali, ma questa ambiguità è inerente alla domanda, non alla mia soluzione.

Ogni riga di qualificazione viene restituita una sola volta , anche quando corrisponde più volte (al contrario di altre risposte qui).

In questo modo viene eseguita la ricerca in tutto il DB ad eccezione dei cataloghi di sistema. Normalmente ci vorrà molto tempo per finire . Potresti voler limitare a determinati schemi / tabelle (o anche colonne) come dimostrato in altre risposte. Oppure aggiungi avvisi e un indicatore di avanzamento, dimostrato anche in un'altra risposta.

Il regclasstipo di identificatore oggetto è rappresentato come nome tabella, qualificato dallo schema dove necessario per disambiguare in base all'attuale search_path:

Qual è il ctid?

Potresti voler eseguire l'escape di caratteri con un significato speciale nel modello di ricerca. Vedere:


Questa ottima soluzione è ancora migliore con lower () - 'SELECT $ 1, ctid FROM% st WHERE lower (t :: text) ~~ lower (% L)'
Georgi Bonchev

5

E se qualcuno pensa che potrebbe aiutare. Ecco la funzione di @Daniel Vérité, con un altro parametro che accetta nomi di colonne che possono essere utilizzati nella ricerca. In questo modo diminuisce il tempo di elaborazione. Almeno nel mio test si è ridotto molto.

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 seguito è riportato un esempio di utilizzo della funzione di ricerca creata sopra.

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

5

Senza memorizzare una nuova procedura è possibile utilizzare un blocco di codice ed eseguire per ottenere una tabella delle occorrenze. Puoi filtrare i risultati per schema, tabella o nome di colonna.

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;

Dove si specifica la stringa di ricerca? O questo sta solo scaricando l'intero DB, tabella per tabella?
jimtut

1
Non ho creato un parametro per la stringa. Puoi codificarlo ed eseguirlo direttamente come un blocco o creare una procedura memorizzata da esso. In ogni caso, la stringa da cercare va qui tra i due segni di percentuale: WHERE UPPER (', rec1. "Column_name",') LIKE UPPER ('' ',' %% ',' '')
profimedica

5

C'è un modo per ottenere ciò senza creare una funzione o utilizzare uno strumento esterno. Utilizzando la query_to_xml()funzione di Postgres che può eseguire dinamicamente una query all'interno di un'altra query, è possibile cercare un testo in molte tabelle. Questo si basa sulla mia risposta per recuperare il conteggio delle righe per tutte le tabelle :

Per cercare la stringa fooin tutte le tabelle in uno schema, è possibile utilizzare quanto segue:

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

Nota che l'uso di xmltablerichiede Postgres 10 o più recente. Per le versioni precedenti di Postgres, questo può essere fatto anche usando 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)

L'espressione di tabella comune ( WITH ...) viene utilizzata solo per comodità. Passa attraverso tutte le tabelle nello publicschema. Per ogni tabella viene eseguita la seguente query tramite la query_to_xml()funzione:

select to_jsonb(t)
from some_table t
where t::text like '%foo%';

La clausola where viene utilizzata per assicurarsi che la costosa generazione di contenuto XML venga eseguita solo per le righe che contengono la stringa di ricerca. Questo potrebbe restituire qualcosa del genere:

<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
  <table_row>{"id": 42, "some_column": "foobar"}</table_row>
</row>
</table>

jsonbViene eseguita la conversione della riga completa in , in modo che nel risultato si possa vedere quale valore appartiene a quale colonna.

Quanto sopra potrebbe restituire qualcosa del genere:

table_name   |   table_row
-------------+----------------------------------------
public.foo   |  {"id": 1, "some_column": "foobar"}
public.bar   |  {"id": 42, "another_column": "barfoo"}

Esempio online per Postgres 10+

Esempio online per versioni precedenti di Postgres


Sto cercando di eseguire il codice per le versioni precedenti di PostgreSQL e ricevo il seguente erroreERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
Matt

Probabilmente dovrai lanciarli:format('%I.%I', table_schema::text, table_name::text)
a_horse_with_no_name

Ok, fatto, ora hoERROR: 42883: function format("unknown", character varying, character varying) does not exist
Matt

Quindi molte delle tue versioni di Postgres sono così vecchie che l'id non ha nemmeno la format()funzione
a_horse_with_no_name

Penso che Redshift sia basato su 8.3?
Matt

3

Ecco la funzione di @Daniel Vérité con funzionalità di report sui progressi. Segnala i progressi in tre modi:

  1. di RAISE NOTICE;
  2. diminuendo il valore della sequenza {progress_seq} fornita da {numero totale di colonne da cercare} fino a 0;
  3. scrivendo lo stato di avanzamento insieme alle tabelle trovate in un file di testo, situato in 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;

3

- La funzione sottostante elencherà tutte le tabelle che contengono una specifica stringa nel database

 select TablesCount(‘StringToSearch’);

- Itera attraverso tutte le tabelle nel 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

- Restituisce il conteggio delle tabelle per le quali la condizione è soddisfatta. - Ad esempio, se il testo desiderato esiste in uno dei campi della tabella, - il conteggio sarà maggiore di 0. Possiamo trovare le notifiche - nella sezione Messaggi del visualizzatore dei risultati nel database di 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;

- Ottieni i campi di ogni tabella. Crea la clausola where con tutte le colonne di una tabella.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.