it-swarm-eu.dev

SQL: VÝBĚR Všechny sloupce kromě některých

Existuje způsob SELECT všech sloupců v tabulce, s výjimkou konkrétních sloupců? IT by bylo velmi výhodné pro výběr všech ne-blob nebo ne-geometrických sloupců z tabulky.

Něco jako:

SELECT * -the_geom FROM segments;
  • Jednou jsem slyšel, že tato funkce byla záměrně vyloučena z standard SQL , protože změna přidání sloupců do tabulky změní výsledky dotazu. Je to pravda? Je argument platný?
  • Existuje řešení, zejména v PostgreSQL?
119
Adam Matan

Taková funkce neexistuje ani v Postgresu, ani v SQL Standardu (AFAIK). Myslím, že je to docela zajímavá otázka, takže jsem se trochu pohnul a narazil na zajímavý článek o postgresonline.com .

Zobrazují přístup, který vybírá sloupce přímo ze schématu:

SELECT 'SELECT ' || array_to_string(ARRAY(SELECT 'o' || '.' || c.column_name
        FROM information_schema.columns As c
            WHERE table_name = 'officepark' 
            AND  c.column_name NOT IN('officeparkid', 'contractor')
    ), ',') || ' FROM officepark As o' As sqlstmt

Mohli byste vytvořit funkci, která něco takového dělá. Taková témata byla také diskutována v seznamech adresátů, ale celkový konsenzus byl do značné míry stejný: zeptejte se schématu.

Jsem si jistý, že existují i ​​jiná řešení, ale myslím, že všechny budou zahrnovat nějaký druh magického schématu-queriying-foo.

BTW: Buďte opatrní s SELECT * ... protože to může mít sankce za výkon

59
DrColossos

Skutečnou odpovědí je, že prostě nemůžete prakticky. Toto je požadovaná vlastnost po celá desetiletí a vývojáři ji odmítají implementovat.

Populární odpověď navrhující dotazování tabulek schémat nebude schopna efektivního běhu, protože Optimalizátor Postgres považuje dynamické funkce za černé pole (viz níže uvedený testovací případ). To znamená, že indexy nebudou použity a připojení nebude provedeno inteligentně. S nějakým druhem makro systému, jako je m4, byste byli mnohem lepší. Aspoň to nezmění optimalizátor (ale může vás stále zaměnit.) Bez rozvětvení kódu a napsání funkce sami nebo pomocí rozhraní programovacího jazyka jste uvízl.

Níže jsem napsal jednoduchý důkaz konceptu ukazující, jak špatný výkon by byl s velmi jednoduchým dynamickým provedením v plpgsql. Všimněte si také, že níže musím donutit funkci, která vrací obecný záznam do konkrétního typu řádku a vyjmenovává sloupce. Tato metoda tedy nebude fungovat pro 'select all but', pokud nechcete předělat tuto funkci pro všechny tabulky.

test=# create table atest (i int primary key);
CREATE TABLE
test=# insert into atest select generate_series(1,100000);
INSERT 0 100000

test=# create function get_table_column(name text) returns setof record as
$$
    declare r record;
    begin
    for r in execute 'select  * from ' || $1 loop
    return next r;
    end loop;
    return; 
    end; 
$$ language plpgsql; 

test=# explain analyze select i from atest where i=999999;
                                                      QUERY PLAN                                    
----------------------------------------------------------------------------------------------------
-------------------
 Index Only Scan using atest_pkey on atest  (cost=0.29..8.31 rows=1 width=4) (actual time=0.024..0.0
24 rows=0 loops=1)
   Index Cond: (i = 999999)
   Heap Fetches: 0
 Planning time: 0.130 ms
 Execution time: 0.067 ms
(5 rows)

test=# explain analyze
    select * from get_table_column('atest') as arowtype(i int) where i = 999999;
                                                        QUERY PLAN                                  
----------------------------------------------------------------------------------------------------
-----------------------
 Function Scan on get_table_column arowtype  (cost=0.25..12.75 rows=5 width=4) (actual time=92.636..
92.636 rows=0 loops=1)
   Filter: (i = 999999)
   Rows Removed by Filter: 100000
 Planning time: 0.080 ms
 Execution time: 95.460 ms
(5 rows)

Jak vidíte, volání funkce prohledává celou tabulku, zatímco přímý dotaz používá index (95,46 ms vs. 00,07ms.). nebo připojit tabulky ve správném pořadí.

18
user17130

Ve skutečnosti je to poněkud možné u PostgreSQL začínající na 9,4, kde byl představen JSONB. Přemýšlel jsem o podobné otázce, jak zobrazit všechny dostupné atributy v Google Mapě (přes GeoJSON).

johto na irc kanálu navrhl zkusit odstranit prvek z JSONB.

Tady je nápad

select the_geom,
  to_jsonb(foo) - 'the_geom'::text attributes
from (
  select * from
  segments
) foo

Zatímco místo jednotlivých sloupců dostanete json, bylo to přesně to, co jsem chtěl. Možná json lze rozšířit zpět do jednotlivých sloupců.

14
mlt

Jediným způsobem, jak to můžete udělat (neříkej, že byste měli), je použití dynamických příkazů SQL. Je snadné (jako napsal DrColossos) dotazovat se na pohledy systému a najít strukturu tabulky a sestavovat správná prohlášení.

PS: Proč byste chtěli vybrat všechny/některé sloupce, aniž byste věděli/psali přesně strukturu vaší tabulky?

6
Marian

V komentář vysvětlujete, že vaším motivem je mít výhodu, že nezobrazíte obsah sloupců s dlouhým obsahem, spíše než nezobrazení samotného sloupce:

… Někdy chci dotazovat tabulku s geometrickým sloupcem, aniž bych zobrazoval velmi dlouhý řetězec geometrie, který vyřizuje výstup. Nechci specifikovat všechny sloupce, protože tam může být několik desítek.

To je možné pomocí pomocné funkce, která nahradí dlouhý obsah sloupcem null (jakýkoli sloupec text v mém příkladu, ale ty by jste to upravili pro typy, které chcete potlačit):

create table my_table(foo integer, bar integer, baz text);
insert into my_table(foo,bar,baz) values (1,2,'blah blah blah blah blah blah'),(3,4,'blah blah');
select * from my_table;
 foo | bar | baz 
 -: | -: | : ---------------------------- 
 1 | 2 | bla bla bla bla bla bla. 
 3 | 4 | bla bla 
create function f(ttype anyelement) returns setof anyelement as
$$
declare
  toid oid;
  tname text;
  nname text;
  cols text;
begin
  --
  select pg_type.oid, pg_namespace.nspname, pg_type.typname
  into toid, nname, tname
  from pg_type join pg_namespace on pg_namespace.oid=pg_type.typnamespace
  where pg_type.oid=pg_typeof(ttype);
  --
  select string_agg((case when data_type<>'text' 
                          then column_name 
                          else 'null::'||data_type||' "'||column_name||'"' end)
                   ,', ' order by ordinal_position)
  into cols
  from information_schema.columns 
  where table_schema=nname and table_name=tname;
  --
  return query execute 'select '||cols||' from '||nname||'.'||tname;
  --
end
$$ language plpgsql;
select * from f(null::my_table);
 foo | bar | baz 
 -: | -: | : --- 
 1 | 2 |  null  
 3 | 4 |  null 

dbfiddle zde

Pokud je vaším cílem odstranit nepořádek z obrazovky během ladění tím, že se nezobrazí sloupce s velkými datovými hodnotami, můžete použít následující trik:

(Nainstalujte balíček příspěvku „hstore“, pokud jej ještě nemáte: "CREATE EXTENSION hstore; ")

Pro tabulku „test“ s col1, col2, col3 můžete nastavit hodnotu „col2“ na null před zobrazením:

select (r).* from (select (test #= hstore('col2',null)) as r from test) s;

Nebo před zobrazením nastavte dva sloupce na null:

select (r).* from (select (test #= hstore('col2',null) #= hstore('col1',null)) as r from test) s;

upozornění je, že „test“ musí být tabulka (alias nebo podvýběr nebude fungovat), protože musí být definován typ záznamu, který se má přenést do hstore.

3
Sean

Existuje řešení, které jsem právě objevil, ale vyžaduje zasílání dotazů SQL zv rámci R. Může to být užitečné uživatelům R.

Balíček dplyr v podstatě odesílá dotazy SQL (a konkrétně PostgreSQL) a přijímá argument -(column_name).

Váš příklad tak může být napsán následovně:

select(segments, -(the_geom))
3
Dario Lacan

Dynamicky, jak je uvedeno výše, je jediná odpověď, ale já ji nedoporučuji. Co když z dlouhodobého hlediska přidáte další sloupce, ale nejsou pro tento dotaz nutně vyžadovány?

Začali byste tahat více sloupců, než potřebujete.

Co když je výběr součástí vložky jako v

Vložit do tabulky A (col1, col2, col3 .. coln) Vyberte vše kromě 2 sloupců z tabulky B

Shoda sloupců bude chybná a vaše vložení selže.

Je to možné, ale přesto doporučuji napsat každý potřebný sloupec pro každý napsaný výběr, i když je vyžadován téměř každý sloupec.

3
  • Z aplikačního hlediska je to líné řešení. Je nepravděpodobné, že aplikace automaticky bude vědět, co dělat s novými sloupci.

    Aplikace prohlížeče dat mohou dotazovat metadata pro data a vyloučit sloupce ze spuštěných dotazů, nebo vybrat podmnožinu dat sloupce. Nové BLOB lze po přidání vyloučit. Data BLOB pro jednotlivé řádky lze vybrat na vyžádání.

  • V jakékoli variantě SQL, která podporuje dynamické dotazy, může být dotaz vytvořen pomocí dotazu na metadata tabulky. Pro váš úmysl bych vyloučil sloupce spíše na základě typu než názvu.

3
BillThor

V SQL-VIEWS nikdy nevidíte * ... zkontrolujte \d any_view Na svém psql. Pro interní reprezentaci existuje (introspektivní) předzpracování .


Veškerá diskuse zde ukazuje, že návrh vydání (implicitně v otázce a diskusích) je cukr syntaxe pro programátory, nejedná se o skutečný „problém s optimalizací SQL“ ... Myslím, že je to pro 80% programátorů.

Lze tedy implementovat jako „ předběžnou analýzu s introspekcí“ ... Podívejte se, co PostgreSQL dělá, když deklarujete SQL-VIEW pomocí SELECT *: Konstruktor VIEW transformuje * do seznamu všech sloupců (introspekcí a v okamžiku spuštění zdrojového kódu CREATE VIEW).

Implementace pro CREATE VIEW a PREPARE

Je to realizovatelná implementace. Předpokládejme tabulku t s poli (id serial, name text, the_geom geom).

CREATE VIEW t_full AS SELECT * FROM t;
-- is transformed into SELECT id,name,the_geom FROM t;

CREATE VIEW t_exp_geom AS SELECT * -the_geom FROM t;
-- or other syntax as EXCEPT the_geom
-- Will be transformed into SELECT id,name FROM t;

Totéž platí pro příkaz PREPARE .

... takže je to možné a to je to, co 80% programátorů potřebuje, syntaxový cukr pro PREPARE a VIEWS!


POZNÁMKA: samozřejmě životaschopná syntaxe možná není - column_name, Pokud v PostgreSQL dochází ke konfliktu, takže můžeme navrhnout EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN) nebo jiné.

2
Peter Krauss

To je moje funkce pro výběr všech sloupců, které očekáváte. Kombinoval jsem nápady z postgresonline.com a postgresql tuturial az dalších zdrojů.

CREATE TABLE phonebook(phone VARCHAR(32), firstname VARCHAR(32),
lastname VARCHAR(32), address VARCHAR(64));
INSERT INTO phonebook(phone, firstname, lastname, address) 
VALUES ('+1 123 456 7890', 'John', 'Doe', 'North America'), 
('+1 321 456 7890', 'Matti', 'Meikeläinen', 'Finland'), 
('+1 999 456 7890', 'Maija', 'Meikeläinen', 'Finland'), 
('+9 123 456 7890', 'John', 'Doe', 'Canada'), 
('+1 123 456 7890', 'John', 'Doe', 'Sweden'), 
('+1 123 456 7890', 'John', 'Doe2', 'North America');

drop function all_except_one(text,text);
CREATE OR REPLACE FUNCTION all_except_one(to_remove TEXT, table_name1 TEXT) 
RETURNS void AS $$

 DECLARE 
 rec_row RECORD;
 curs1 refcursor ;

 BEGIN
  --print column names:
  raise notice '%', ('|'|| ARRAY_TO_STRING(ARRAY(SELECT 
  COLUMN_NAME::CHAR(20) FROM INFORMATION_SCHEMA.COLUMNS WHERE
  TABLE_NAME=table_name1 AND COLUMN_NAME NOT IN (to_remove) ), 
  '|') ||'|') ; 

  OPEN curs1 FOR
  EXECUTE 'select table_1  from (SELECT ' || ARRAY_TO_STRING(ARRAY(
  SELECT COLUMN_NAME::VARCHAR(50) FROM INFORMATION_SCHEMA.COLUMNS 
  WHERE TABLE_NAME=table_name1 AND COLUMN_NAME NOT IN (to_remove)    
  ), ', ') || ' FROM ' || table_name1 || ' limit 30)   table_1 ';

  LOOP
  -- fetch row into the rec_row
  FETCH curs1 INTO rec_row;

  -- exit when no more row to fetch
  EXIT WHEN NOT FOUND;

  -- build and print the row output

  raise notice '%',(select'| '|| regexp_replace( array_to_string(
  array_agg(a::char(20)),'|'),'["\(.*\)]+',   '','g') ||'|'  from 
  unnest(string_to_array(replace(replace(replace(trim(rec_row::text,
  '()'),'"',''), ', ','|'),')',' '),',')) as a);

  END LOOP;

  -- Close the cursor

  CLOSE curs1;

  END; $$ LANGUAGE plpgsql;

select  all_except_one('phone','phonebook');

--output:
--NOTICE:  |firstname           |lastname            |address             |
--NOTICE:  | John               |Doe                 |North America       |
--NOTICE:  | Matti              |Meikeläinen         |Finland             |
--NOTICE:  | Maija              |Meikeläinen         |Finland             |
--NOTICE:  | John               |Doe                 |Canada              |
--NOTICE:  | John               |Doe                 |Sweden              |
--NOTICE:  | John               |Doe2                |North America       |
-- all_except_one 
-- ----------------
-- (1 row)
1
Veli-Matti Sorvala