¿Cómo buscar un valor específico en todas las tablas (PostgreSQL)?

111

¿Es posible buscar en cada columna de cada tabla un valor particular en PostgreSQL?

Una pregunta similar está disponible aquí para Oracle.

Sandro Munda
fuente
¿Está buscando una herramienta o una implementación de los procedimientos que se muestran en la pregunta vinculada?
a_horse_with_no_name
No, solo la forma más sencilla de encontrar un valor específico en todos los campos / tablas.
Sandro Munda
¿Entonces no quieres usar una herramienta externa?
a_horse_with_no_name
1
Si es la forma más sencilla => ok para una herramienta externa :-)
Sandro Munda

Respuestas:

131

¿Qué hay de tirar el contenido de la base de datos y luego usarlo 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 misma utilidad, pg_dump, puede incluir nombres de columna en la salida. Simplemente cambie --insertsa --column-inserts. De esa forma, también puede buscar nombres de columnas específicos. Pero si estuviera buscando nombres de columnas, probablemente volcaría el esquema en lugar de los datos.

$ 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'
fuente
5
+1 gratis y simple. Y si desea una estructura, pg_dump también puede hacerlo. Además, si grep no es lo tuyo, usa cualquier herramienta de búsqueda de contenido de archivos que desees en las estructuras y / o datos descargados.
Kuberchaun
Si desea grep datos de texto (que normalmente están codificados en versiones más recientes de postgres), es posible que deba hacerlo ALTER DATABASE your_db_name SET bytea_output = 'escape';en la base de datos (o una copia de los mismos) antes de descargarlos. (No veo una forma de especificar esto solo para un pg_dumpcomando.)
phils
¿puede explicar en detalle ..? ¿Cómo buscar la cadena 'ABC' en todas las tablas?
Sr. Bhosale
1
Si está utilizando IntelliJ, puede hacer clic con el botón derecho en su base de datos y seleccionar "Volcar con 'pg_dump'" o "Volcar datos en archivo (s)"
Laurens
3
¿Cómo es esta una solución válida para cualquier base de datos que sea lo suficientemente grande como para que no pueda volcarla en su disco?
Govind Parmar
76

Aquí hay una función pl / pgsql que ubica registros donde cualquier columna contiene un valor específico. Toma como argumentos el valor para buscar en formato de texto, una matriz de nombres de tablas para buscar (por defecto todas las tablas) y una matriz de nombres de esquema (por defecto todos los nombres de esquema).

Devuelve una estructura de tabla con esquema, nombre de tabla, nombre de columna y pseudocolumna ctid(ubicación física no duradera de la fila en la tabla, consulte Columnas del 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;

Consulte también la versión en github basada en el mismo principio, pero agregando algunas mejoras de velocidad y generación de informes.

Ejemplos de uso en una base de datos de prueba:

  • Buscar en todas las tablas dentro del esquema público:
seleccione * de las columnas_de_búsqueda ('foobar');
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 publico | s3 | usename | (0,11)
 publico | s2 | relname | (7,29)
 publico | w | cuerpo | (0,2)
(3 filas)
  • Buscar en una tabla específica:
 seleccionar * de las columnas_búsqueda ('foobar', '{w}');
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 publico | w | cuerpo | (0,2)
(1 fila)
  • Busque en un subconjunto de tablas obtenidas de una selección:
select * from search_columns ('foobar', array (seleccione table_name :: name from information_schema.tables donde table_name como 's%'), array ['public']);
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 publico | s2 | relname | (7,29)
 publico | s3 | usename | (0,11)
(2 filas)
  • Obtenga una fila de resultados con la tabla base correspondiente y ctid:
seleccione * de public.w donde ctid = '(0,2)';
 titulo | cuerpo | tsv         
------- + -------- + ---------------------
 toto | foobar | 'foobar': 2 'toto': 1

Variantes

  • Para probar con una expresión regular en lugar de una igualdad estricta, como grep, esta parte de la consulta:

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

    puede cambiarse a:

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

  • Para comparaciones que no distinguen entre mayúsculas y minúsculas, puede escribir:

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

Daniel Vérité
fuente
ERROR: error de sintaxis en o cerca de "predeterminado" LÍNEA 3: haystack_tables nombre [] predeterminado '{}' (usando PostgreSQL 8.2.17 y no se puede actualizar)
Henno
@Henno: sí, requiere PG-9.1. Editado ahora para hacerlo explícito. Para usarlo con versiones anteriores, tendrás que adaptarlo.
Daniel Vérité
1
@Rajendra_Prasad: el operador de expresión regular tiene una variante que no distingue entre mayúsculas y minúsculas: ~*más adecuada que lower (). Pero de todos modos, t.*no es parte de la respuesta anterior. Buscar columna por columna no es lo mismo que buscar la fila como valor debido a los separadores de columna.
Daniel Vérité
2
Esto solo devuelve una fila por columna de tabla de esquema.
theGtknerd
1
Muchas gracias. Esta solución me funciona perfectamente. Tuve que ubicar una tabla en una lista de más de 1000 tablas que contiene una URL específica. ¡Salvaste mi día!.
Domingo
7

para buscar en cada columna de cada tabla un valor particular

Esto no define cómo coincidir exactamente.
Tampoco define qué devolver exactamente.

Asumiendo:

  • Encuentre cualquier fila con cualquier columna que contenga el valor dado en su representación de texto, en lugar de igualar el valor dado.
  • Devuelve el nombre de la tabla ( regclass) y el ID de tupla ( ctid), porque eso es lo más simple.

Aquí hay una forma sencilla, rápida y ligeramente sucia:

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;

Llamada:

SELECT * FROM search_whole_db('mypattern');

Proporcione el patrón de búsqueda sin adjuntar %.

¿Por qué un poco sucio?

Si los separadores y decoradores de la fila en la textrepresentación pueden ser parte del patrón de búsqueda, puede haber falsos positivos:

  • separador de columnas: ,por defecto
  • toda la fila está entre paréntesis:()
  • algunos valores están entre comillas dobles "
  • \ se puede agregar como carácter de escape

Y la representación del texto de algunas columnas puede depender de la configuración local, pero esa ambigüedad es inherente a la pregunta, no a mi solución.

Cada fila de calificación se devuelve solo una vez , incluso cuando coincide varias veces (a diferencia de otras respuestas aquí).

Esto busca en toda la base de datos excepto en los catálogos del sistema. Por lo general, tardará mucho en terminar . Es posible que desee restringirse a ciertos esquemas / tablas (o incluso columnas) como se muestra en otras respuestas. O agregue avisos y un indicador de progreso, también demostrado en otra respuesta.

El regclasstipo de identificador de objeto se representa como el nombre de la tabla, calificado por esquema cuando sea necesario para eliminar la ambigüedad de acuerdo con el actual search_path:

¿Qué es el ctid?

Es posible que desee escapar de los caracteres con un significado especial en el patrón de búsqueda. Ver:

Erwin Brandstetter
fuente
Esta gran solución es aún mejor con lower () - 'SELECT $ 1, ctid FROM% st WHERE lower (t :: text) ~~ lower (% L)'
Georgi Bonchev
5

Y si alguien piensa que podría ayudar. Aquí está la función de @Daniel Vérité, con otro parámetro que acepta nombres de columnas que se pueden usar en la búsqueda. De esta forma disminuye el tiempo de procesamiento. Al menos en mi prueba se redujo mucho.

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;

A continuación se muestra un ejemplo del uso de la función de búsqueda creada anteriormente.

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
fuente
5

Sin almacenar un nuevo procedimiento, puede usar un bloque de código y ejecutarlo para obtener una tabla de ocurrencias. Puede filtrar los resultados por esquema, tabla o nombre de columna.

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
fuente
¿Dónde especifica la cadena de búsqueda? ¿O se trata simplemente de descargar toda la base de datos, tabla por tabla?
jimtut
1
No creé un parámetro para la cadena. Puede codificarlo y ejecutarlo directamente como un bloque o crear un procedimiento almacenado a partir de él. En cualquier caso, su cadena de búsqueda va aquí entre los dos signos de porcentaje: WHERE UPPER (', rec1. "Column_name",') LIKE UPPER ('' ',' %% ',' '')
profimedica
5

Hay una manera de lograr esto sin crear una función o usar una herramienta externa. Al usar la query_to_xml()función de Postgres que puede ejecutar dinámicamente una consulta dentro de otra consulta, es posible buscar un texto en muchas tablas. Esto se basa en mi respuesta para recuperar el recuento de filas para todas las tablas :

Para buscar la cadena fooen todas las tablas de un esquema, se puede utilizar lo siguiente:

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

Tenga en cuenta que el uso de xmltablerequiere Postgres 10 o más reciente. Para la versión anterior de Postgres, esto también se puede hacer 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)

La expresión de tabla común ( WITH ...) solo se usa por conveniencia. Recorre todas las tablas del publicesquema. Para cada tabla, se ejecuta la siguiente consulta a través de la query_to_xml()función:

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

La cláusula where se utiliza para asegurarse de que la generación costosa de contenido XML solo se realice para las filas que contienen la cadena de búsqueda. Esto podría devolver algo como esto:

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

Se realiza la conversión de la fila completa a jsonb, de modo que en el resultado se pueda ver qué valor pertenece a qué columna.

Lo anterior podría devolver algo como esto:

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

Ejemplo en línea para Postgres 10+

Ejemplo en línea para versiones anteriores de Postgres

un caballo sin nombre
fuente
Estoy tratando de ejecutar el código para versiones anteriores de PostgreSQL y recibo el siguiente errorERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
Matt
Probablemente necesites format('%I.%I', table_schema::text, table_name::text)
lanzarlos
Ok, hecho eso, ahora lo tengoERROR: 42883: function format("unknown", character varying, character varying) does not exist
Matt
Entonces, muchas de sus versiones de Postgres son tan antiguas que la identificación ni siquiera tiene la format()función
a_horse_with_no_name
Creo que Redshift se basa en 8.3.
Matt
3

Aquí está la función de @Daniel Vérité con funcionalidad de informes de progreso. Informa el progreso de tres formas:

  1. por RAISE AVISO;
  2. al disminuir el valor de la secuencia {progress_seq} proporcionada de {número total de columnas para buscar} a 0;
  3. escribiendo el progreso junto con las tablas encontradas en un archivo de texto, ubicado en 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
fuente
3

- La siguiente función enumerará todas las tablas que contienen una cadena específica en la base de datos

 select TablesCount(‘StringToSearch’);

--Itera a través de todas las tablas de la base de datos

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

- Devuelve el recuento de tablas para las que se cumple la condición. - Por ejemplo, si el texto deseado existe en cualquiera de los campos de la tabla, - entonces el recuento será mayor que 0. Podemos encontrar las notificaciones - en la sección Mensajes del visor de resultados en la base de datos de 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;

- Obtener los campos de cada tabla. Crea la cláusula where con todas las columnas de una tabla.

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
fuente