Чи можна шукати в кожному стовпчику кожної таблиці певне значення в PostgreSQL?
Подібне запитання є тут і для Oracle.
Чи можна шукати в кожному стовпчику кожної таблиці певне значення в PostgreSQL?
Подібне запитання є тут і для Oracle.
Відповіді:
Як щодо скидання вмісту бази даних, а потім використання 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');
ALTER DATABASE your_db_name SET bytea_output = 'escape';
у базі даних (або їх копії), перш ніж скидати їх. (Я не бачу способу вказати це лише для pg_dump
команди.)
Ось функція 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 ряди)
виберіть * з 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)
~*
адекватніший ніж нижчий (). Але все одно t.*
це не є частиною наведеної відповіді. Пошук стовпця за стовпцем не є таким самим, як пошук рядка як значення через роздільники стовпців.
пошук у кожному стовпчику кожної таблиці певного значення
Це не визначає, як саме відповідати.
Також не визначено, що саме потрібно повернути.
Припустимо:
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
?
Можливо, ви захочете уникнути символів із спеціальним значенням у шаблоні пошуку. Побачити:
І якщо хтось думає, це може допомогти. Ось функція @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')
);
Не зберігаючи нову процедуру, ви можете використовувати блок коду та виконати для отримання таблиці випадків. Ви можете фільтрувати результати за назвою схеми, таблиці чи стовпця.
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;
Існує спосіб досягти цього без створення функції або використання зовнішнього інструменту. Використовуючи 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"}
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()
функції
Ось функція @Daniel Vérité з функціоналом звітування про прогрес. Він повідомляє про прогрес трьома способами:
_
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;
- Нижче буде перераховано всі таблиці, які містять певну рядок у базі даних
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