SQL: SELEZIONA Tutte le colonne tranne alcune


108

C'è un modo per SELECTtutte le colonne in una tabella, tranne quelle specifiche? Sarebbe molto conveniente per selezionare tutte le colonne non BLOB o non geometriche da una tabella.

Qualcosa di simile a:

SELECT * -the_geom FROM segments;
  • Una volta ho sentito che questa funzionalità è stata deliberatamente esclusa dallo standard SQL perché la modifica dell'aggiunta di colonne alla tabella altererà i risultati della query. È vero? L'argomento è valido?
  • C'è una soluzione alternativa, soprattutto in PostgreSQL?

Qual è il caso d'uso per il quale vuoi conoscere tutte le colonne tranne alcune? È solo per mostrare sullo schermo mentre si fanno alcune query manuali? Fa parte di un programma?
joanolo,

2
Una tabella con 6 significative, colonne brevi (a-la name, age, sid) che si adatta bene alla larghezza dello schermo, alongwith un lungo binario geomcolonna. Voglio interrogare tutti i campi tranne il binario della geometria e scrivere i loro nomi uno per uno è noioso.
Adam Matan,

In tal caso, questo potrebbe essere più qualcosa a che fare con lo strumento che stai utilizzando con la query interattiva che con lo stesso SQL ...
joanolo,

1
@joanolo Semplice shell PostgreSQL.
Adam Matan,

3
Sembra così ovvio. A volte non si desidera stampare una o due colonne perché non sono interessanti o si desidera solo che la tabella dei risultati si adatti allo schermo (soprattutto se si utilizza un client da riga di comando). Mi aspetterei una sintassi simileselect (!coluns2,!column5) from sometable;
gumkins,

Risposte:


54

Tale caratteristica non esiste né in Postgres né nello standard SQL (AFAIK). Penso che questa sia una domanda abbastanza interessante, quindi ho cercato su Google un po 'e ho trovato un articolo interessante su postgresonline.com .

Mostrano un approccio che seleziona le colonne direttamente dallo schema:

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

Potresti creare una funzione che fa qualcosa del genere. Tali argomenti sono stati discussi anche nelle mailing list, ma il consenso generale era praticamente lo stesso: interrogare lo schema.

Sono sicuro che ci sono altre soluzioni, ma penso che coinvolgeranno tutti una specie di schema magico che richiede query.

A proposito: stai attento SELECT * ...perché questo può avere delle penalità di prestazione


Come creare tale funzione? Non riesco a trovare un modo per creare una funzione che restituisca una query sconosciuta, dovrei sempre dichiarare una tabella in anticipo.
ePascoal,

17

La vera risposta è che non puoi praticamente praticamente. Questa è una funzionalità richiesta da decenni e gli sviluppatori si rifiutano di implementarla.

La popolare risposta che suggerisce di interrogare le tabelle dello schema non sarà in grado di funzionare in modo efficiente perché l'ottimizzatore Postgres considera le funzioni dinamiche una scatola nera (vedere il caso di prova di seguito). Ciò significa che gli indici non verranno utilizzati e i join non verranno eseguiti in modo intelligente. Sarebbe molto meglio con una sorta di sistema macro come m4. Almeno non confonderà l'ottimizzatore (ma potrebbe comunque confonderti.) Senza biforcarti il ​​codice e scrivere tu stesso la funzione o usando un'interfaccia del linguaggio di programmazione sei bloccato.

Di seguito ho scritto una semplice dimostrazione del concetto che mostra quanto sarebbero andate male le prestazioni con un'esecuzione dinamica molto semplice in plpgsql. Si noti inoltre che di seguito devo forzare una funzione che restituisce un record generico in un tipo di riga specifico ed enumera le colonne. Quindi questo metodo non funzionerà per 'seleziona tutto ma' a meno che tu non voglia rifare questa funzione per tutte le tue tabelle.

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)

Come puoi vedere, la chiamata di funzione ha scansionato l'intera tabella mentre la query diretta utilizzava l'indice ( 95,46 ms vs. 00,07 ms .) Questo tipo di funzioni riempirebbe qualsiasi tipo di query complicata necessaria per utilizzare gli indici o unire le tabelle nell'ordine giusto .


1
Prospettiva interessante. Questa è sicuramente una funzionalità per gli utenti umani piuttosto che per il codice (o almeno così dovrei sperare!), Quindi posso vedere il punto su come rendere responsabile il cliente. Presumibilmente cose come la visualizzazione estesa (\ x on) sono implementate esclusivamente nel client e l'omissione delle colonne dovrebbe essere implementata in un posto simile.
Max Murphy,

13

In realtà è in qualche modo possibile con PostgreSQL a partire dalla 9.4 in cui è stato introdotto JSONB. Stavo riflettendo su una domanda simile su come mostrare tutti gli attributi disponibili in Google Map (tramite GeoJSON).

johto sul canale irc ha suggerito di provare a eliminare l'elemento da JSONB.

Ecco l'idea

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

Mentre ottieni json invece di singole colonne, era esattamente quello che volevo. Forse JSON può essere espanso nuovamente in singole colonne.


Sì, forse qualcosa da qui, ma non ho ottenuto questo al lavoro ancora- stackoverflow.com/questions/36174881/...
chrismarx

6

L'unico modo in cui puoi (non dire che dovresti farlo) è usando istruzioni sql dinamiche. È facile (come ha scritto DrColossos) interrogare le viste di sistema, trovare la struttura della tabella e creare le istruzioni appropriate.

PS: Perché dovresti selezionare tutte / alcune colonne senza conoscere / scrivere esattamente la struttura della tabella?


7
Per quanto riguarda il tuo PS: a volte voglio interrogare una tabella con colonna geometrica, senza visualizzare la stringa geometrica molto lunga che altera l'output. Non voglio specificare tutte le colonne, perché potrebbero essercene alcune dozzine.
Adam Matan,

Quindi solo sql dinamico può salvarti da molte battiture :-).
Marian

Tutti presumono che colui che crea la query sia colui che ha progettato il database. :-) Supponi di dover interrogare un vecchio database con molti campi (più di 30) per generare un Excel, ma ci sono uno o due campi che hanno informazioni sensibili che non vuoi fornire.
yucer

3

Dinamicamente, come detto sopra, è l'unica risposta, ma non lo consiglio. Cosa succede se si aggiungono più colonne a lungo termine ma non sono necessariamente necessarie per quella query?

Inizieresti a tirare più colonne del necessario.

Che cosa succede se la selezione fa parte di un inserto come in

Inserisci nella tabella A (col1, col2, col3 .. coln) Seleziona tutto tranne 2 colonne DA tableB

La corrispondenza della colonna sarà errata e l'inserimento non riuscirà.

È possibile ma consiglio comunque di scrivere ogni colonna necessaria per ogni selezione scritta anche se è richiesta quasi ogni colonna.


Questo approccio è ovviamente a livello di programmazione errato, ma è innocuo e utile come query della console per SELECTs.
Adam Matan,

3

Se il tuo obiettivo è rimuovere il disordine dallo schermo durante il debug non visualizzando colonne con valori di dati di grandi dimensioni, puoi utilizzare il seguente trucco:

(installa il pacchetto contrib "hstore" se non lo hai già: " CREATE EXTENSION hstore;")

Per una tabella "test" con col1, col2, col3, è possibile impostare il valore di "col2" su null prima di visualizzare:

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

In alternativa, impostare due colonne su null prima di visualizzare:

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

l'avvertenza è che "test" deve essere una tabella (un alias o una sottoselezione non funzioneranno) poiché il tipo di record che inserisce hstore deve essere definito.


3

C'è una soluzione alternativa che ho appena scoperto, ma richiede l'invio di query SQL all'interno di R. Potrebbe essere utile per gli utenti R.

Fondamentalmente il dplyrpacchetto invia query SQL (e in particolare PostgreSQL) e accetta l' -(column_name)argomento.

Quindi il tuo esempio potrebbe essere scritto come segue:

select(segments, -(the_geom))

3

In un commento spieghi che il tuo motivo è avere la comodità di non visualizzare il contenuto delle colonne con un contenuto lungo, piuttosto che non visualizzare la colonna stessa:

... A volte voglio interrogare una tabella con colonna geometrica, senza visualizzare la stringa geometrica molto lunga che altera l'output. Non voglio specificare tutte le colonne, perché potrebbero essercene alcune dozzine.

Questo è possibile, con l'aiuto di una funzione di supporto che sostituisce il contenuto lungo con null(qualsiasi textcolonna nel mio esempio, ma lo modificheresti per i tipi che vuoi sopprimere):

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;
pippo | bar | baz                          
-: | -: | : ----------------------------
  1 | 2 | blah blah blah blah blah blah
  3 | 4 | blah blah                    
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);
pippo | bar | baz
-: | -: | : ---
  1 | 2 | null 
  3 | 4 | nullo

dbfiddle qui


2
  • Dal punto di vista dell'applicazione, questa è una soluzione pigra. È improbabile che un'applicazione sappia automaticamente cosa fare con le nuove colonne.

    Le applicazioni del browser di dati possono richiedere i metadati per i dati ed escludere le colonne dalle query in esecuzione o selezionare un sottoinsieme dei dati della colonna. Nuovi BLOB possono essere esclusi quando aggiunti. I dati BLOB per righe particolari possono essere selezionati su richiesta.

  • In qualsiasi variante SQL che supporti query dinamiche, la query può essere creata utilizzando una query sui metadati delle tabelle. Per la tua intenzione, escluderei le colonne in base al tipo piuttosto che al nome.


2

Non vedi mai *in SQL-VIEWS ... controlla \d any_viewda te psql. Esiste una preelaborazione (introspettiva) per la rappresentazione interna.


Tutte le discussioni qui mostrano che la proposta di problema (implicita nella domanda e nelle discussioni) è uno zucchero sintattico per i programmatori, non un vero "problema di ottimizzazione SQL" ... Beh, suppongo, lo sia per l'80% dei programmatori.

Quindi può essere implementato come " pre-analisi con introspezione" ... Guarda cosa fa PostgreSQL quando dichiari un SQL-VIEW con SELECT *: il costruttore VIEW si trasforma *in un elenco di tutte le colonne (per introspezione e nel momento in cui esegui il CREATE VIEW codice sorgente).

Implementazione per CREATE VIEW e PREPARE

È un'implementazione praticabile. Supponiamo una tabella tcon campi (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;

Lo stesso vale per l' istruzione PREPARE .

... quindi, è possibile, ed è ciò di cui l'80% dei programmatori ha bisogno, uno zucchero sintattico per PREPARE e VISUALIZZA!


NOTA: ovviamente la sintassi praticabile non lo è - column_name, se ci sono dei conflitti in PostgreSQL, quindi possiamo suggerire EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)o altro.


1

Questa è la mia funzione per selezionare tutte le colonne se ne aspettano una. Ho combinato idee da postgresonline.com e tutorial postgresql e da altre fonti.

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.