Как искать конкретное значение во всех таблицах (PostgreSQL)?

112

Можно ли в PostgreSQL искать в каждом столбце каждой таблицы определенное значение?

Аналогичный вопрос доступен здесь для Oracle.

Сандро Мунда
источник
Вы ищете инструмент или реализацию процедур, указанных в связанном вопросе?
a_horse_with_no_name
Нет, это самый простой способ найти конкретное значение во всех полях / таблицах.
Sandro Munda
Значит, вы не хотите использовать внешний инструмент?
a_horse_with_no_name
1
Если это самый простой способ => хорошо для внешнего инструмента :-)
Sandro Munda

Ответы:

132

Как насчет того, чтобы выгрузить содержимое базы данных, а затем использовать 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');

Та же утилита, pg_dump, может включать имена столбцов в вывод. Просто смени --insertsна --column-inserts. Таким образом, вы также можете искать определенные имена столбцов. Но если бы я искал имена столбцов, я бы, вероятно, сбросил схему вместо данных.

$ 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 бесплатно и просто. И если вам нужна структура, pg_dump тоже может это сделать. Также, если grep вам не подходит, используйте любой инструмент поиска содержимого файла, который вы хотите, в выгруженных структурах и / или данных.
Kuberchaun
Если вы хотите использовать grep для текстовых данных (которые, как правило, кодируются в более поздних версиях postgres), вам может потребоваться ALTER DATABASE your_db_name SET bytea_output = 'escape';добавить их в базу данных (или ее копию), прежде чем сбросить их. (Я не вижу способа указать это только для pg_dumpкоманды.)
phils
можешь подробно объяснить ..? Как искать строку "ABC" во всех таблицах?
Мистер Босейл
1
Если вы используете IntelliJ, вы можете просто щелкнуть правой кнопкой мыши свой БД и выбрать «Дамп с 'pg_dump'» или «Дамп данных в файл (ы)»
Лоренс
3
Насколько это подходящее решение для любой базы данных, которая настолько велика, что ее нельзя выгрузить на диск?
Govind
76

Вот функция pl / pgsql, которая находит записи, в которых любой столбец содержит определенное значение. Он принимает в качестве аргументов значение для поиска в текстовом формате, массив имен таблиц для поиска (по умолчанию для всех таблиц) и массив имен схем (по умолчанию все имена схем).

Он возвращает структуру таблицы со схемой, именем таблицы, именем столбца и псевдостолбцом ctid(недолговечное физическое расположение строки в таблице, см. Системные столбцы )

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;

Смотрите также версию на github основанную на том же принципе, но с добавлением некоторых улучшений скорости и отчетов.

Примеры использования в тестовой базе данных:

  • Искать во всех таблицах публичной схемы:
выберите * из search_columns ('foobar');
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 общественный | s3 | имя пользователя | (0,11)
 общественный | s2 | relname | (7,29)
 общественный | w | тело | (0,2)
(3 ряда)
  • Искать в конкретной таблице:
 выберите * из search_columns ('foobar', '{w}');
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 общественный | w | тело | (0,2)
(1 ряд)
  • Искать в подмножестве таблиц, полученных в результате выбора:
выберите * из search_columns ('foobar', array (выберите table_name :: name из information_schema.tables, где table_name как 's%'), array ['public']);
 schemaname | tablename | columnname | rowctid
------------ + ----------- + ------------ + ---------
 общественный | s2 | relname | (7,29)
 общественный | s3 | имя пользователя | (0,11)
(2 ряда)
  • Получите строку результата с соответствующей базовой таблицей и ctid:
выберите * из public.w, где ctid = '(0,2)';
 название | тело | цв         
------- + -------- + ---------------------
 toto | foobar | 'foobar': 2 'toto': 1

Варианты

  • Чтобы проверить регулярное выражение вместо строгого равенства, такого как grep, эта часть запроса:

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

    может быть изменено на:

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

  • Для сравнений без учета регистра вы можете написать:

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

Даниэль Верите
источник
ОШИБКА: синтаксическая ошибка на уровне «по умолчанию» или рядом с ним. СТРОКА 3: haystack_tables name [] default '{}' (Используется PostgreSQL 8.2.17, обновление невозможно)
Henno
@Henno: да, требуется PG-9.1. Отредактировано сейчас, чтобы сделать это явным. Чтобы использовать его со старыми версиями, вам придется адаптировать его.
Даниэль Верите
1
@Rajendra_Prasad: оператор регулярного выражения имеет нечувствительный к регистру вариант: ~*более адекватный, чем lower (). Но в любом случае t.*это не часть приведенного выше ответа. Поиск столбец за столбцом - это не то же самое, что поиск строки как значения из-за разделителей столбцов.
Даниэль Верите
2
Это возвращает только одну строку для каждого столбца таблицы-схемы.
theGtknerd
1
Большое спасибо. Это решение отлично работает для меня. Мне пришлось найти таблицу в списке из более чем 1000 таблиц, которые содержат определенный URL-адрес. Ты спас мне день !.
Сунил,
7

для поиска в каждом столбце каждой таблицы определенного значения

Это не определяет, как точно соответствовать.
Он также не определяет, что именно возвращать.

Предполагая:

  • Найдите любую строку с любым столбцом, содержащим данное значение в его текстовом представлении, в отличие от равного данному значению.
  • Верните имя таблицы ( regclass) и идентификатор кортежа ( ctid), потому что это проще всего.

Вот мертвый простой, быстрый и немного грязный способ:

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;

Вызов:

SELECT * FROM search_whole_db('mypattern');

Предоставьте шаблон поиска без заключений %.

Почему немного грязно?

Если разделители и декораторы для строки в textпредставлении могут быть частью шаблона поиска, могут быть ложные срабатывания:

  • разделитель столбцов: ,по умолчанию
  • вся строка заключена в круглые скобки:()
  • некоторые значения заключены в двойные кавычки "
  • \ может быть добавлен как escape-символ

И текстовое представление некоторых столбцов может зависеть от локальных настроек - но эта двусмысленность присуща самому вопросу, а не моему решению.

Каждая соответствующая строка возвращается только один раз , даже если она совпадает несколько раз (в отличие от других ответов здесь).

Выполняется поиск по всей БД, кроме системных каталогов. Обычно на завершение уходит много времени . Возможно, вы захотите ограничиться определенными схемами / таблицами (или даже столбцами), как показано в других ответах. Или добавьте уведомления и индикатор прогресса, что также показано в другом ответе.

Тип regclassидентификатора объекта представлен в виде имени таблицы, дополненного схемой, где это необходимо, для устранения неоднозначности в соответствии с текущим search_path:

Что такое ctid?

Вы можете захотеть экранировать символы со специальным значением в шаблоне поиска. Видеть:

Эрвин Брандштеттер
источник
Это отличное решение еще лучше с lower () - 'SELECT $ 1, ctid FROM% st WHERE lower (t :: text) ~~ lower (% L)'
Георгий Бончев
5

И если кто-то думает, это может помочь. Вот функция @Daniel Vérité с другим параметром, принимающим имена столбцов, которые можно использовать в поиске. Таким образом сокращается время обработки. По крайней мере, в моем тесте он сильно уменьшился.

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;

Ниже приведен пример использования функции search_function, созданной выше.

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

Без сохранения новой процедуры вы можете использовать блок кода и выполнить его для получения таблицы вхождений. Вы можете фильтровать результаты по схеме, таблице или имени столбца.

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
источник
Где указать строку поиска? Или это просто выгрузка всей БД, таблица за таблицей?
jimtut
1
Я не создавал параметр для строки. Вы можете либо жестко запрограммировать его и запустить непосредственно как блок, либо создать из него хранимую процедуру. В любом случае ваша строка для поиска проходит здесь между двумя знаками процента: WHERE UPPER (', rec1. "Column_name",') LIKE UPPER ('' ',' %% ',' '')
profimedica
5

Есть способ добиться этого без создания функции или использования внешнего инструмента. Используя query_to_xml()функцию Postgres, которая может динамически запускать запрос внутри другого запроса, можно искать текст во многих таблицах. Это основано на моем ответе на получение количества строк для всех таблиц :

Для поиска строки fooво всех таблицах схемы можно использовать следующее:

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

Обратите внимание, что для использования xmltableтребуется Postgres 10 или новее. Для более старой версии Postgres это также можно сделать с помощью 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)

Общее табличное выражение ( WITH ...) используется только для удобства. Он просматривает все таблицы в publicсхеме. Для каждой таблицы через query_to_xml()функцию выполняется следующий запрос :

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

Предложение where используется, чтобы гарантировать, что дорогостоящая генерация XML-контента выполняется только для строк, содержащих строку поиска. Это может вернуть что-то вроде этого:

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

Преобразование всей строки в jsonbвыполнено, чтобы в результате можно было увидеть, какое значение принадлежит какому столбцу.

Вышеупомянутое может вернуть что-то вроде этого:

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

Онлайн-пример для Postgres 10+

Онлайн-пример для более старых версий Postgres

a_horse_with_no_name
источник
Я пытаюсь запустить код для более старых версий PostgreSQL и получаю следующую ошибку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
Мэтт
Значит, многие ваши версии Postgres настолько стары, что у id даже нет format()функции
a_horse_with_no_name
Думаю, Redshift основан на 8.3?
Мэтт
3

Вот функция @Daniel Vérité с функцией отчета о прогрессе. Он сообщает о прогрессе тремя способами:

  1. УВЕДОМЛЕНИЕ О ПОВЫШЕНИИ;
  2. уменьшив значение предоставленной последовательности {progress_seq} с {общее количество столбцов для поиска} до 0;
  3. записав прогресс вместе с найденными таблицами в текстовый файл, расположенный в 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

- Ниже функция перечислит все таблицы, которые содержат определенную строку в базе данных

 select TablesCount(‘StringToSearch’);

- Перебирает все таблицы в базе данных

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

- Возвращает количество таблиц, для которых выполнено условие. - Например, если предполагаемый текст существует в любом из полей таблицы, - тогда счетчик будет больше нуля. Мы можем найти уведомления - в разделе «Сообщения» программы просмотра результатов в базе данных 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;

--Получить поля каждой таблицы. Создает предложение where со всеми столбцами таблицы.

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
Ганеш
источник