Як шукати певне значення у всіх таблицях (PostgreSQL)?


111

Чи можна шукати в кожному стовпчику кожної таблиці певне значення в PostgreSQL?

Подібне запитання є тут і для Oracle.


Ви шукаєте інструмент або для здійснення процедур, показаних у пов'язаному питанні?
a_horse_with_no_name

Ні, просто найпростіший спосіб знайти певне значення у всіх полях / таблицях.
Сандро Мунда

Отже, ви не хочете використовувати зовнішній інструмент?
a_horse_with_no_name

1
Якщо це найпростіший спосіб => нормально для зовнішнього інструменту :-)
Сандро Мунда

Відповіді:


131

Як щодо скидання вмісту бази даних, а потім використання 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

Якщо ви хочете скопіювати текстові дані (які, як правило, кодуються в останніх версіях постгресів), вам, можливо, знадобиться ALTER DATABASE your_db_name SET bytea_output = 'escape';у базі даних (або їх копії), перш ніж скидати їх. (Я не бачу способу вказати це лише для pg_dumpкоманди.)
phils

ви можете детально пояснити ..? Як шукати рядок "ABC" у всіх таблицях?
Містер Бхосале

1
Якщо ви користуєтеся IntelliJ, ви можете просто клацнути правою кнопкою миші ваш db та вибрати "Дамп із" pg_dump "" або "Dump data to file (s)"
Laurens

3
Як це правильне рішення для будь-якої достатньо великої бази даних, що ви не можете скинути її на свій диск?
Говінд Пармар

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_column ('foobar');
 схема | назва таблиці | назва стовпця | rowctid
------------ + ----------- + ------------ + ---------
 громадські | s3 | usename | (0,11)
 громадські | s2 | перейменування | (7,29)
 громадські | w | корпус | (0,2)
(3 ряди)
  • Пошук у конкретній таблиці:
 виберіть * з пошуку_колонок ('foobar', '{w}');
 схема | назва таблиці | назва стовпця | rowctid
------------ + ----------- + ------------ + ---------
 громадські | w | корпус | (0,2)
(1 ряд)
  • Пошук у підмножині таблиць, отриманих з вибору:
виберіть * з search_column ('foobar', масив (виберіть table_name :: ім'я з information_schema.tables, де ім'я table_необхідно як 's%'), array ['public']);
 схема | назва таблиці | назва стовпця | rowctid
------------ + ----------- + ------------ + ---------
 громадські | s2 | перейменування | (7,29)
 громадські | s3 | usename | (0,11)
(2 ряди)
  • Отримайте рядок результатів із відповідною базовою таблицею та ctid:
виберіть * з public.w, де ctid = '(0,2)';
 назва | корпус | цв         
------- + -------- + ---------------------
 тото | 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)


ПОМИЛКА: помилка синтаксису на або біля "за замовчуванням" LINE 3: ім'я haystack_tables [] за замовчуванням '{}' (Використання PostgreSQL 8.2.17 та не вдається оновити)
Henno

@Henno: так, це вимагає PG-9.1. Відредаговано зараз, щоб зробити це явним. Щоб використовувати його з більш старими версіями, вам доведеться адаптувати його.
Даніель Верете

1
@Rajendra_Prasad: оператор регулярного виразу має нечутливий до регістру варіант: ~*адекватніший ніж нижчий (). Але все одно 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поданні можуть бути частиною шаблону пошуку, можуть бути помилкові позитиви:

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

І текстове подання деяких стовпців може залежати від локальних налаштувань - але ця неоднозначність притаманна питанню, а не моєму вирішенню.

Кожен кваліфікований рядок повертається лише один раз , навіть коли він збігається кілька разів (на відміну від інших відповідей тут).

Тут виконується пошук всієї БД, крім системних каталогів. Закінчення закінчується зазвичай багато часу . Ви можете обмежитися певними схемами / таблицями (або навіть стовпцями), як показано в інших відповідях. Або додайте повідомлення та показник прогресу, також продемонстровані в іншій відповіді.

regclassТип ідентифікатора об'єкта представляється в вигляді імені таблиці, схеми кваліфікованих , де це необхідно , щоб усунути неоднозначність в відповідно до струмом search_path:

Що таке ctid?

Можливо, ви захочете уникнути символів із спеціальним значенням у шаблоні пошуку. Побачити:


Це чудове рішення ще краще з нижчим () - "ВИБІР $ 1, ctid ВІД% ст., Де нижчий (t :: текст) ~~ нижчий (% 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;

Де ви вказуєте рядок пошуку? Або це просто скидання всієї БД, таблиця за таблицею?
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%';

Стаття де використовується, щоб переконатися, що дороге створення вмісту 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


Я намагаюся запустити код для старих версій 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)
a_horse_with_no_name

Гаразд, зробив це, тепер у менеERROR: 42883: function format("unknown", character varying, character varying) does not exist
Метт,

Тоді багато вашої версії Postgres настільки стара, що ідентифікатор навіть не має format()функції
a_horse_with_no_name

Я думаю, що Redshift базується на 8,3?
Метт

3

Ось функція @Daniel Vérité з функціоналом звітування про прогрес. Він повідомляє про прогрес трьома способами:

  1. за повідомленням RAISE NOTICE;
  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

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

- Отримайте поля кожної таблиці. Побудовує пункт де з усіма стовпцями таблиці.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.