Tüm tablolarda belirli bir değer nasıl aranır (PostgreSQL)?


112

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.


Bir araç mı yoksa bağlantılı soruda gösterilen prosedürlerin bir uygulamasını mı arıyorsunuz?
a_horse_with_no_name

Hayır, tüm alanlarda / tablolarda belirli bir değeri bulmanın en basit yolu.
Sandro Munda

Yani harici bir araç kullanmak istemiyor musunuz?
a_horse_with_no_name

1
En basit yol buysa => harici bir araç için tamam :-)
Sandro Munda

Yanıtlar:


132

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 --insertsiç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');

5
+1 ücretsiz ve basit. Ve eğer yapı istiyorsanız pg_dump bunu da yapabilir. Ayrıca, grep sizin işiniz değilse, atılan yapılarda ve / veya verilerde istediğiniz dosya içeriği arama aracını kullanın.
Kuberchaun

Metin verilerini (genellikle daha yeni postgres sürümlerinde kodlanır) grep etmek istiyorsanız, 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_dumpkomut için belirtmenin bir yolunu göremiyorum .)
phils

detaylı olarak açıklar mısınız ..? Tüm tablolarda 'ABC' dizesi nasıl aranır?
Bay Bhosale

1
IntelliJ kullanıyorsanız, db'nizi sağ tıklayıp "'pg_dump' ile döküm" veya "Verileri dosyaya / dosyalara dök" seçeneğini seçebilirsiniz
Laurens

3
Bu, onu diskinize dökemeyeceğiniz kadar büyük olan herhangi bir veritabanı için nasıl geçerli bir çözüm olabilir?
Govind Parmar

76

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 ctidsatı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:

  • Genel şema içindeki tüm tablolarda ara:
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)
  • Belirli bir tabloda ara:
 search_columns'dan * seçin ('foobar', '{w}');
 şemaname | tablename | sütun adı | Rowctid
------------ + ----------- + ------------ + ---------
 genel | w | vücut | (0,2)
(1 sıra)
  • Bir seçimden elde edilen tabloların bir alt kümesinde arama yapın:
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)
  • Karşılık gelen temel tablo ve ctid ile bir sonuç satırı alın:
public.w'den * seçin, burada ctid = '(0,2)';
 başlık | vücut | tsv         
------- + -------- + ---------------------
 toto | foobar | 'foobar': 2 'toto': 1

Varyantlar

  • 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)


HATA: "varsayılan" satırında veya yakınında sözdizimi hatası SATIR 3: haystack_tables adı [] varsayılan '{}' (PostgreSQL 8.2.17 kullanılıyor ve yükseltilemiyor)
Henno

@Henno: evet PG-9.1 gerektiriyor. Bunu açıklığa kavuşturmak için şimdi düzenlendi. Eski sürümlerle kullanmak için uyarlamanız gerekir.
Daniel Vérité

1
@Rajendra_Prasad: Normal ifade operatörünün büyük / küçük harf duyarlı olmayan bir varyantı var: ~*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.
Daniel Vérité

2
Bu, şema-tablo-sütunu başına yalnızca bir satır döndürür.
theGtknerd

1
Çok teşekkürler. Bu çözüm benim için mükemmel çalışıyor. Belirli bir url içeren 1000'den fazla tablodan oluşan bir listede bir tablo bulmam gerekiyordu. Günümü kurtardın !.
Sunil

7

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:

  • Verilen değeri eşitlemenin aksine, metin temsilinde verilen değeri içeren herhangi bir sütuna sahip herhangi bir satırı bulun .
  • Tablo adını ( 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?

textTemsil edilen satır için ayırıcılar ve dekoratörler arama modelinin bir parçası olabilirse, yanlış pozitifler olabilir:

  • sütun ayırıcı: ,varsayılan olarak
  • tüm satır parantez içine alınır:()
  • bazı değerler çift tırnak içine alınır "
  • \ kaçış karakteri olarak eklenebilir

Ve 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.

regclassNesne 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:


Bu harika bir çözüm daha iyi ile () alt - 'düşük (t :: metin) düşürmek ~~% st (% L) DAN $ 1, ctid SEÇ'
Georgi Bonçev

5

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')
);

5

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;

Arama dizesini nerede belirtirsiniz? Yoksa bu tüm DB'yi tablo tablo mı döküyor?
jimtut

1
Dize için bir parametre oluşturmadım. Ya sabit kodlayabilir ve bir blok olarak doğrudan çalıştırabilir veya ondan bir saklı yordam oluşturabilirsiniz. Her durumda,
aranacak dizeniz

5

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 :

fooBir ş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 xmltablePostgres 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 jsonbyapı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"}

Postgres 10+ için çevrimiçi örnek

Eski Postgres sürümleri için çevrimiçi örnek


Eski PostgreSQL sürümleri için kodu çalıştırmaya çalışıyorum ve şu hatayı alıyorumERROR: 42883: function format("unknown", information_schema.sql_identifier, information_schema.sql_identifier) does not exist
Matt

Muhtemelen onları format('%I.%I', table_schema::text, table_name::text)
atmanız

Tamam, şimdi yaptımERROR: 42883: function format("unknown", character varying, character varying) does not exist
Matt

O halde Postgres sürümünüzün çoğu o kadar eski ki, format()
id'nin

Redshift'in 8.3'e dayandığını düşünüyorum?
Matt

3

İşte @Daniel Vérité'nin ilerleme raporlama işlevine sahip işlevi. İlerlemeyi üç şekilde rapor eder:

  1. YÜKSELTME BİLDİRİMİ ile;
  2. sağlanan {progress_seq} dizisinin değerini {aranacak toplam sütun sayısından} 0'a düşürerek;
  3. bulunan tablolarla birlikte ilerlemeyi c: \ windows \ temp \ {progress_seq} .txt konumunda bulunan metin dosyasına yazarak.

_

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

- 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.