¿Es posible buscar en cada columna de cada tabla un valor particular en PostgreSQL?
Una pregunta similar está disponible aquí para Oracle.
¿Es posible buscar en cada columna de cada tabla un valor particular en PostgreSQL?
Una pregunta similar está disponible aquí para Oracle.
Respuestas:
¿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 --inserts
a --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');
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_dump
comando.)
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:
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)
seleccionar * de las columnas_búsqueda ('foobar', '{w}'); schemaname | tablename | columnname | rowctid ------------ + ----------- + ------------ + --------- publico | w | cuerpo | (0,2) (1 fila)
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)
seleccione * de public.w donde ctid = '(0,2)'; titulo | cuerpo | tsv ------- + -------- + --------------------- toto | foobar | 'foobar': 2 'toto': 1
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)
~*
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.
para buscar en cada columna de cada tabla un valor particular
Esto no define cómo coincidir exactamente.
Tampoco define qué devolver exactamente.
Asumiendo:
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 text
representación pueden ser parte del patrón de búsqueda, puede haber falsos positivos:
,
por defecto()
"
\
se puede agregar como carácter de escapeY 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 regclass
tipo 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:
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')
);
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;
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 foo
en 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 xmltable
requiere 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 public
esquema. 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"}
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()
función
Aquí está la función de @Daniel Vérité con funcionalidad de informes de progreso. Informa el progreso de tres formas:
_
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;
- 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