PostgreSQL'de her tablonun her sütununda belirli bir değer aramak mümkün müdür ?
Benzer bir soru vardır burada Oracle için.
PostgreSQL'de her tablonun her sütununda belirli bir değer aramak mümkün müdür ?
Benzer bir soru vardır burada Oracle için.
Yanıtlar:
Veritabanının içeriğini döküp sonra kullanmaya ne dersiniz 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');
Aynı yardımcı program, pg_dump, çıktıda sütun adlarını içerebilir. Sadece değiştirmek --inserts
için --column-inserts
. Bu şekilde, belirli sütun adlarını da arayabilirsiniz. Ancak sütun adlarını arıyor olsaydım, muhtemelen veriler yerine şemayı dökerdim.
$ 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';
dökmeden önce veritabanına (veya bir kopyasına) ihtiyacınız olabilir . (Bunu sadece bir pg_dump
komut için belirtmenin bir yolunu göremiyorum .)
Herhangi bir sütunun belirli bir değer içerdiği kayıtları bulan bir pl / pgsql işlevi . Metin biçiminde aranacak değeri bağımsız değişken olarak, aranacak bir tablo adları dizisi (varsayılan olarak tüm tablolar için) ve bir dizi şema adı (varsayılan olarak tüm şema adları) alır.
Şema, tablo adı, sütun adı ve sözde sütun içeren bir tablo yapısı döndürür (tablodaki ctid
satırın kalıcı olmayan fiziksel konumu, bkz. Sistem Sütunları )
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;
Aynı prensibe dayanan, ancak bazı hız ve raporlama iyileştirmeleri ekleyerek github'daki sürüme de bakın .
Bir test veritabanında kullanım örnekleri:
search_columns'dan * seçin ('foobar'); şemaname | tablename | sütun adı | Rowctid ------------ + ----------- + ------------ + --------- genel | s3 | usename | (0,11) genel | s2 | relname | (7,29) genel | w | vücut | (0,2) (3 sıra)
search_columns'dan * seçin ('foobar', '{w}'); şemaname | tablename | sütun adı | Rowctid ------------ + ----------- + ------------ + --------- genel | w | vücut | (0,2) (1 sıra)
search_columns'dan * seçin ('foobar', array (table_name :: name from information_schema.tables seçin, burada table_name 's%' gibi), array ['public']); şemaname | tablename | sütun adı | Rowctid ------------ + ----------- + ------------ + --------- genel | s2 | relname | (7,29) genel | s3 | usename | (0,11) (2 sıra)
public.w'den * seçin, burada ctid = '(0,2)'; başlık | vücut | tsv ------- + -------- + --------------------- toto | foobar | 'foobar': 2 'toto': 1
Grep gibi katı eşitlik yerine normal bir ifadeye karşı test etmek için sorgunun bu kısmı:
SELECT ctid FROM %I.%I WHERE cast(%I as text)=%L
şu şekilde değiştirilebilir:
SELECT ctid FROM %I.%I WHERE cast(%I as text) ~ %L
Büyük / küçük harfe duyarlı olmayan karşılaştırmalar için şunları yazabilirsiniz:
SELECT ctid FROM %I.%I WHERE lower(cast(%I as text)) = lower(%L)
~*
daha düşük () 'den daha yeterli. Ama yine de t.*
yukarıdaki cevabın parçası değil. Sütun bazında arama yapmak, sütun ayırıcılar nedeniyle satırı bir değer olarak aramakla aynı şey değildir.
her tablonun her sütununda belirli bir değer aramak için
Bu tam olarak nasıl eşleşeceğini tanımlamaz.
Tam olarak neyin döndürüleceğini de tanımlamaz.
Varsayalım:
regclass
) ve demet kimliğini ( ctid
) döndürün , çünkü bu en basitidir.İşte çok basit, hızlı ve biraz kirli bir yol:
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;
Aramak:
SELECT * FROM search_whole_db('mypattern');
Arama modelini eklemeden sağlayın %
.
Neden biraz kirli?
text
Temsil edilen satır için ayırıcılar ve dekoratörler arama modelinin bir parçası olabilirse, yanlış pozitifler olabilir:
,
varsayılan olarak()
"
\
kaçış karakteri olarak eklenebilirVe bazı sütunların metin temsili yerel ayarlara bağlı olabilir - ancak bu belirsizlik çözümümün değil sorunun doğasında var.
Her uygun satır, birden çok kez eşleşse bile (buradaki diğer yanıtların aksine) yalnızca bir kez döndürülür .
Bu, sistem katalogları dışında tüm DB'yi arar. Tamamlanması genellikle uzun zaman alır . Diğer yanıtlarda gösterildiği gibi belirli şemalar / tablolarla (hatta sütunlarla) sınırlandırmak isteyebilirsiniz. Veya başka bir yanıtta gösterilen bildirimler ve bir ilerleme göstergesi ekleyin.
regclass
Nesne tanımlayıcısı tipi tablo adını, şema nitelikli gerekli akım göre belirgin hale getirmeye olarak temsil edilir search_path
:
Nedir ctid
?
Arama düzeninde özel anlamı olan karakterlerden kaçınmak isteyebilirsiniz. Görmek:
Ve eğer birisi yardımcı olabileceğini düşünürse. Burada @Daniel Vérité'nin işlevi, aramada kullanılabilecek sütunların adlarını kabul eden başka bir param ile. Bu şekilde işlem süresini kısaltır. En azından benim testimde çok azaldı.
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şağıda, yukarıda oluşturulan arama_ işlevinin kullanımına bir örnek verilmiştir.
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')
);
Yeni bir prosedürü depolamadan, bir kod bloğu kullanabilir ve bir oluşum tablosu elde etmek için yürütebilirsiniz. Sonuçları şema, tablo veya sütun adına göre filtreleyebilirsiniz.
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;
Bir işlev oluşturmadan veya harici bir araç kullanmadan bunu başarmanın bir yolu var. query_to_xml()
Bir sorguyu dinamik olarak başka bir sorgu içinde çalıştırabilen Postgres işlevini kullanarak , bir metni birçok tabloda aramak mümkündür. Bu, tüm tablolar için satır sayısını alma cevabıma dayanmaktadır :
foo
Bir şemadaki tüm tablolarda dizeyi aramak için aşağıdakiler kullanılabilir:
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
Kullanımının xmltable
Postgres 10 veya daha yenisini gerektirdiğini unutmayın . Eski Postgres sürümü için bu, xpath () kullanılarak da yapılabilir.
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)
Ortak tablo ifadesi ( WITH ...
) yalnızca kolaylık sağlamak için kullanılır. public
Şemadaki tüm tablolarda döngü oluşturur . Her tablo için aşağıdaki sorgu query_to_xml()
işlev aracılığıyla çalıştırılır :
select to_jsonb(t)
from some_table t
where t::text like '%foo%';
Where cümlesi, pahalı XML içeriğinin yalnızca arama dizesini içeren satırlar için yapıldığından emin olmak için kullanılır. Bu şuna benzer bir sonuç verebilir:
<table xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<row>
<table_row>{"id": 42, "some_column": "foobar"}</table_row>
</row>
</table>
Tüm satırın dönüştürülmesi jsonb
yapılır, böylece sonuçta hangi değerin hangi sütuna ait olduğu görülebilir.
Yukarıdakiler şöyle bir sonuç verebilir:
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()
İşte @Daniel Vérité'nin ilerleme raporlama işlevine sahip işlevi. İlerlemeyi üç şekilde rapor eder:
_
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;
- Aşağıdaki fonksiyon, veritabanında belirli bir dizge içeren tüm tabloları listeleyecektir
select TablesCount(‘StringToSearch’);
- Veritabanındaki tüm tabloları yineler
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
- Koşulun karşılandığı tabloların sayısını döndürür. - Örneğin, istenen metin tablonun herhangi bir alanında mevcutsa, sayı 0'dan büyük olacaktır. Bildirimleri, postgres veritabanındaki sonuç görüntüleyicinin Mesajlar bölümünde bulabiliriz.
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;
- Her tablonun alanlarını alın. Bir tablonun tüm sütunlarıyla where yan tümcesini oluşturur.
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