Jak sprawdzić, czy tabela istnieje w danym schemacie

149

Bazy danych Postgres 8.4 i nowsze zawierają typowe tabele w publicschemacie i specyficzne dla firmy tabele w companyschemacie.
companynazwy schematów zawsze zaczynają się 'company'i kończą numerem firmy.
Mogą więc istnieć schematy takie jak:

public
company1
company2
company3
...
companynn

Aplikacja zawsze współpracuje z jedną firmą.
Jest search_pathto odpowiednio określone w parametrach połączenia odbc lub npgsql, na przykład:

search_path='company3,public'

Jak sprawdzić, czy dana tabela istnieje w określonym companynschemacie?

na przykład:

select isSpecific('company3','tablenotincompany3schema')

powinien wrócić false, i

select isSpecific('company3','tableincompany3schema')

powinien wrócić true.

W każdym razie funkcja powinna sprawdzać tylko companynprzekazany schemat, a nie inne schematy.

Jeśli dana tabela istnieje w obu publici przekazanym schemacie, funkcja powinna zwrócić true.
Powinien działać na Postgres 8.4 lub nowszym.

Andrus
źródło

Odpowiedzi:

284

To zależy od tego, co dokładnie chcesz przetestować .

Schemat informacyjny?

Aby dowiedzieć się, „czy tabela istnieje” ( bez względu na to, kto o to pyta ), wysłanie zapytania do schematu informacyjnego ( information_schema.tables) jest niepoprawne , ściśle mówiąc, ponieważ ( zgodnie z dokumentacją ):

Wyświetlane są tylko te tabele i widoki, do których bieżący użytkownik ma dostęp (będąc właścicielem lub posiadającym pewne uprawnienia).

Zapytanie dostarczone przez @kong może zwrócić FALSE, ale tabela może nadal istnieć. Odpowiada na pytanie:

Jak sprawdzić, czy tabela (lub widok) istnieje, a aktualny użytkownik ma do niej dostęp?

SELECT EXISTS (
   SELECT FROM information_schema.tables 
   WHERE  table_schema = 'schema_name'
   AND    table_name   = 'table_name'
   );

Schemat informacji jest przydatny głównie w celu zachowania przenośności między głównymi wersjami i różnymi systemami RDBMS. Ale implementacja jest powolna, ponieważ Postgres musi używać wyrafinowanych widoków, aby zachować zgodność ze standardem ( information_schema.tablesjest to raczej prosty przykład). A niektóre informacje (takie jak OID) giną podczas tłumaczenia z katalogów systemowych - co w rzeczywistości zawierają wszystkie informacje.

Katalogi systemowe

Twoje pytanie brzmiało:

Jak sprawdzić, czy istnieje stół?

SELECT EXISTS (
   SELECT FROM pg_catalog.pg_class c
   JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
   WHERE  n.nspname = 'schema_name'
   AND    c.relname = 'table_name'
   AND    c.relkind = 'r'    -- only tables
   );

Korzystaj z katalogów systemowych pg_classi pg_namespacebezpośrednio, co jest również znacznie szybsze. Jednak zgodnie z dokumentacją dotyczącąpg_class :

Katalog pg_classkataloguje tabele i większość innych elementów, które mają kolumny lub są w inny sposób podobne do tabeli. Obejmuje to indeksy (ale zobacz także pg_index), sekwencje , widoki , widoki zmaterializowane , typy złożone i tabele TOAST ;

W przypadku tego konkretnego pytania możesz również użyć widoku systemowegopg_tables . Nieco prostsze i bardziej przenośne w głównych wersjach Postgres (co nie stanowi problemu w przypadku tego podstawowego zapytania):

SELECT EXISTS (
   SELECT FROM pg_tables
   WHERE  schemaname = 'schema_name'
   AND    tablename  = 'table_name'
   );

Identyfikatory muszą być unikalne wśród wszystkich wymienionych powyżej obiektów. Jeśli chcesz zapytać:

Jak sprawdzić, czy jest przyjmowana nazwa tabeli lub podobnego obiektu w danym schemacie?

SELECT EXISTS (
   SELECT FROM pg_catalog.pg_class c
   JOIN   pg_catalog.pg_namespace n ON n.oid = c.relnamespace
   WHERE  n.nspname = 'schema_name'
   AND    c.relname = 'table_name'
   );

Alternatywa: rzut do regclass

SELECT 'schema_name.table_name'::regclass

Powoduje to wyjątek, jeśli (opcjonalnie kwalifikowana według schematu) tabela (lub inny obiekt zajmujący tę nazwę) nie istnieje.

Jeśli nazwa tabeli nie zostanie zakwalifikowana przez schemat, rzutowanie na wartość regclassdomyślną search_pathi zwróci OID dla pierwszej znalezionej tabeli - lub wyjątek, jeśli tabela nie znajduje się w żadnym z wymienionych schematów. Zwróć uwagę, że schematy systemowe pg_catalogi pg_temp(schemat tymczasowych obiektów bieżącej sesji) są automatycznie częścią search_path.

Możesz tego użyć i złapać możliwy wyjątek w funkcji. Przykład:

Zapytanie takie jak powyżej pozwala uniknąć możliwych wyjątków i dlatego jest nieco szybsze.

to_regclass(rel_name) w Postgres 9.4+

Teraz dużo prostsze:

SELECT to_regclass('schema_name.table_name');

To samo co obsada, ale powraca ...

... null zamiast zgłaszać błąd, jeśli nazwa nie zostanie znaleziona

Erwin Brandstetter
źródło
4
z muszli:[[ `psql dbname -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'ejabberd' AND table_name = 'users');"` = 't' ]]
brauliobo
1
Czy jest jakiś powód, dla którego nie używasz pg_tables ?
m0meni
1
pg_tablesto właściwie dobry pomysł na „Jak sprawdzić, czy tabela istnieje?” (Sprawdzanie tabel tylko ., A nie do innych celów, jak wyjaśniono powyżej Ponadto, pg_tablesjest widokiem z udziałem kilku tabel ( pg_class, pg_namespace, pg_tablespace), który jest nieco droższy Najważniejszym powodem. Jestem przyzwyczajony do zapytania pg_classbezpośrednio i nie pomyśl, pg_tablespisząc tę ​​odpowiedź. Dodałem ją powyżej teraz, dzięki.
Erwin Brandstetter
1
@ sage88: Racja, usunąłem mój niepoprawny komentarz. Możesz użyć, pg_my_temp_schema()aby uzyskać identyfikator OID rzeczywistego schematu tymczasowego, jeśli istnieje. (Ale widoki w teście information_schemanie obejmują OIDów. Można SELECT nspname FROM pg_namespace WHERE OID = pg_my_temp_schema()) Twój test ma kilka słabych punktów. Prawidłowe badanie byłoby table_schema LIKE 'pg\_temp\_%'lub surowszych: table_schema ~ '^pg_temp_\d+$'.
Erwin Brandstetter
1
@PeterKrauss Otrzymasz ten błąd, jeśli spróbujesz użyć funkcji to_regclass na wersji postgres starszej niż 9.4. Musi mieć 9.4+
spetz83
44

Być może użyj schematu information_schema :

SELECT EXISTS(
    SELECT * 
    FROM information_schema.tables 
    WHERE 
      table_schema = 'company3' AND 
      table_name = 'tableincompany3schema'
);
kong
źródło
0

Dla PostgreSQL 9.3 lub starszego ... Lub kto lubi wszystko znormalizowane do tekstu

Trzy smaki mojej starej biblioteki SwissKnife: relname_exists(anyThing), relname_normalized(anyThing)i relnamechecked_to_array(anyThing). Wszystkie sprawdzenia z tabeli pg_catalog.pg_class i zwracają standardowe uniwersalne typy danych ( boolean , text lub text []).

/**
 * From my old SwissKnife Lib to your SwissKnife. License CC0.
 * Check and normalize to array the free-parameter relation-name.
 * Options: (name); (name,schema), ("schema.name"). Ignores schema2 in ("schema.name",schema2).
 */
CREATE FUNCTION relname_to_array(text,text default NULL) RETURNS text[] AS $f$
     SELECT array[n.nspname::text, c.relname::text]
     FROM   pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace,
            regexp_split_to_array($1,'\.') t(x) -- not work with quoted names
     WHERE  CASE
              WHEN COALESCE(x[2],'')>'' THEN n.nspname = x[1]      AND c.relname = x[2]
              WHEN $2 IS NULL THEN           n.nspname = 'public'  AND c.relname = $1
              ELSE                           n.nspname = $2        AND c.relname = $1
            END
$f$ language SQL IMMUTABLE;

CREATE FUNCTION relname_exists(text,text default NULL) RETURNS boolean AS $wrap$
  SELECT EXISTS (SELECT relname_to_array($1,$2))
$wrap$ language SQL IMMUTABLE;

CREATE FUNCTION relname_normalized(text,text default NULL,boolean DEFAULT true) RETURNS text AS $wrap$
  SELECT COALESCE(array_to_string(relname_to_array($1,$2), '.'), CASE WHEN $3 THEN '' ELSE NULL END)
$wrap$ language SQL IMMUTABLE;
Peter Krauss
źródło