SQL: SELECT Todas as colunas, exceto algumas

108

Existe um caminho para SELECTtodas as colunas em uma tabela, exceto as específicas? Seria muito conveniente selecionar todas as colunas não-blob ou não-geométricas de uma tabela.

Algo como:

SELECT * -the_geom FROM segments;
  • Uma vez ouvi dizer que essa funcionalidade foi deliberadamente excluída do padrão SQL porque a alteração da adição de colunas na tabela alterará os resultados da consulta. Isso é verdade? O argumento é válido?
  • Existe uma solução alternativa, especialmente no PostgreSQL?
Adam Matan
fonte
Qual é o caso de uso para o qual você deseja conhecer todas as colunas, exceto algumas? É apenas para mostrar na tela enquanto faz algumas consultas manuais? Faz parte de um programa?
Joanolo
2
Uma tabela com 6, colunas curtas significativos (a-la name, age, sid) que se encaixa bem na largura da tela, alongwith um binário longo geomda coluna. Eu quero consultar todos os campos, exceto o binário da geometria, e escrever seus nomes um por um é entediante.
Adam Matan
Nesse caso, isso pode ser mais algo a ver com a ferramenta que você está usando com a consulta interativa do que com o próprio SQL ...
joanolo
1
@joanolo Shell simples do PostgreSQL.
Adam Matan
3
Isso parece tão óbvio. Às vezes, você não deseja imprimir uma ou duas colunas porque elas não são interessantes ou apenas deseja que a tabela de resultados caiba na tela (especialmente se um cliente de linha de comando for usado). Eu esperaria uma sintaxe comoselect (!coluns2,!column5) from sometable;
gumkins

Respostas:

54

Esse recurso não existe no Postgres nem no SQL Standard (AFAIK). Eu acho que essa é uma pergunta bastante interessante, então pesquisei um pouco no Google e me deparei com um artigo interessante no postgresonline.com .

Eles mostram uma abordagem que seleciona as colunas diretamente do esquema:

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

Você pode criar uma função que faça algo assim. Esses tópicos também foram discutidos nas listas de discussão, mas o consenso geral era praticamente o mesmo: consultar o esquema.

Tenho certeza de que existem outras soluções, mas acho que todas elas envolverão algum tipo de esquema mágico-consulta-foo.

BTW: tenha cuidado, SELECT * ...pois isso pode ter penalidades de desempenho

DrColossos
fonte
Como criar essa função? Não consigo encontrar nenhuma maneira de criar uma função que retorne uma consulta desconhecida. Sempre precisaria declarar uma tabela com antecedência.
ePascoal
17

A resposta real é que você simplesmente não pode praticamente. Esse é um recurso solicitado há décadas e os desenvolvedores se recusam a implementá-lo.

A resposta popular que sugere consultar as tabelas de esquema não poderá ser executada com eficiência, porque o otimizador do Postgres considera as funções dinâmicas uma caixa preta (veja o caso de teste abaixo). Isso significa que os índices não serão usados ​​e as junções não serão feitas de maneira inteligente. Você seria muito melhor com algum tipo de sistema macro como o m4. Pelo menos, não confundirá o otimizador (mas ainda poderá confundi-lo.) Sem bifurcar o código e escrever o recurso você mesmo ou usar uma interface de linguagem de programação, você está preso.

Eu escrevi uma prova simples de conceito abaixo, mostrando o quão ruim seria o desempenho com uma execução dinâmica muito simples no plpgsql. Observe também que abaixo eu tenho que coagir uma função retornando um registro genérico em um tipo de linha específico e enumerar as colunas. Portanto, esse método não funcionará para 'selecionar tudo menos', a menos que você queira refazer esta função para todas as suas tabelas.

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)

Como você pode ver, a chamada de função varreu a tabela inteira enquanto a consulta direta usava o índice ( 95,46 ms vs. 00,07 ms .) Esses tipos de funções armazenariam qualquer tipo de consulta complicada que precisasse usar índices ou ingressar em tabelas na ordem correta .

user17130
fonte
1
Perspectiva interessante. Definitivamente, esse é um recurso para usuários humanos, e não para o código (ou pelo menos espero)! Presumivelmente, coisas como exibição estendida (\ x on) são implementadas apenas no cliente e as colunas omitidas devem ser implementadas em um local semelhante.
Max Murphy
13

Na verdade, é algo possível com o PostgreSQL, começando com a 9.4, onde o JSONB foi introduzido. Eu estava pensando em uma pergunta semelhante sobre como mostrar todos os atributos disponíveis no Google Map (via GeoJSON).

johto no canal irc sugeriu tentar excluir o elemento do JSONB.

Aqui está a ideia

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

Enquanto você obtém json em vez de colunas individuais, era exatamente o que eu queria. Talvez o json possa ser expandido novamente em colunas individuais.

mlt
fonte
Sim, talvez algo a partir daqui, mas eu não ter chegado a este trabalho ainda- stackoverflow.com/questions/36174881/...
chrismarx
6

A única maneira de você (não dizer que deveria) fazer isso é usando instruções sql dinâmicas. É fácil (como escreveu o DrColossos) consultar as visualizações do sistema e encontrar a estrutura da tabela e criar instruções apropriadas.

PS: Por que você deseja selecionar todas / algumas colunas sem saber / escrever exatamente a estrutura da sua tabela?

Marian
fonte
7
Em relação ao seu PS: Às vezes, quero consultar uma tabela com coluna geométrica, sem exibir a string de geometria muito longa que ilumina a saída. Não quero especificar todas as colunas, porque pode haver algumas dezenas.
Adam Matan 29/03
Portanto, apenas o sql dinâmico pode salvá-lo de muitas digitações :-).
Marian
Todo mundo assume que quem faz a consulta é quem criou o banco de dados. :-) Suponha que você precise consultar um banco de dados antigo com muitos campos (mais de 30) para gerar um excel, mas há um ou dois campos que possuem informações confidenciais que você não deseja fornecer.
yucer 28/02
3

Dinamicamente, como indicado acima, é a única resposta, mas não a recomendo. E se você adicionar mais colunas a longo prazo, mas elas não forem necessariamente necessárias para essa consulta?

Você começaria a puxar mais coluna do que precisa.

E se o select fizer parte de uma inserção como em

Inserir na tabelaA (col1, col2, col3 .. coln) Selecionar tudo, exceto 2 colunas DA tabelaB

A correspondência da coluna estará incorreta e sua inserção falhará.

É possível, mas ainda recomendo escrever todas as colunas necessárias para cada seleção gravada, mesmo que quase todas as colunas sejam necessárias.

Nicolas de Fontenay
fonte
Essa abordagem está obviamente programaticamente errada, mas é inofensiva e útil como uma consulta de console para SELECTs.
Adam Matan 29/03
3

Se seu objetivo é remover a confusão da tela durante a depuração, não exibindo colunas com grandes valores de dados, você pode usar o seguinte truque:

(instale o pacote contrib "hstore" se você ainda não o possui: " CREATE EXTENSION hstore;")

Para uma tabela "test" com col1, col2, col3, você pode definir o valor de "col2" como nulo antes de exibir:

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

Ou defina duas colunas como nulas antes de exibir:

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

a ressalva é que "teste" deve ser uma tabela (um alias ou subseleção não funcionará), pois o tipo de registro que alimenta o hstore deve ser definido.

Sean
fonte
3

Há uma solução alternativa que acabei de descobrir, mas requer o envio de consultas SQL de dentro de R. Pode ser útil para usuários de R.

Basicamente, o dplyrpacote envia consultas SQL (e especificamente PostgreSQL) e aceita o -(column_name)argumento.

Portanto, seu exemplo pode ser escrito da seguinte maneira:

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

Em um comentário, você explica que seu motivo é ter a conveniência de não exibir o conteúdo de colunas com conteúdo longo, em vez de não exibir a própria coluna:

... Às vezes, quero consultar uma tabela com coluna geométrica, sem exibir a string de geometria muito longa que ilumina a saída. Não quero especificar todas as colunas, porque pode haver algumas dezenas.

Isso é possível, com a ajuda de uma função auxiliar que substitui o conteúdo longo por null(qualquer textcoluna no meu exemplo, mas você modificaria isso para os tipos que deseja suprimir):

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 blá blá blá blá blá blá
  3 4 blá blá                    
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 nulo 
  3 | 4 nulo

dbfiddle aqui

Jack Douglas
fonte
2
  • Do ponto de vista da aplicação, esta é uma solução lenta. É improvável que um aplicativo saiba automaticamente o que fazer com as novas colunas.

    Os aplicativos de navegador de dados podem consultar os metadados para os dados e excluir as colunas das consultas que estão sendo executadas ou selecionar um subconjunto dos dados da coluna. Novos BLOBs podem ser excluídos quando adicionados. Dados BLOB para linhas específicas podem ser selecionados sob demanda.

  • Em qualquer variante SQL que suporte consultas dinâmicas, a consulta pode ser criada usando uma consulta nos metadados da tabela. Para sua intenção, excluiria colunas com base no tipo e não no nome.

BillThor
fonte
2

Você nunca vê *no SQL-VIEWS ... verifique \d any_viewno seu psql. Há um pré - processamento (introspectivo) para representação interna.


Toda discussão aqui mostra que a proposta do problema (implícita na pergunta e nas discussões) é um açúcar de sintaxe para os programadores, não um "problema de otimização de SQL" real ... Bem, meu palpite, é para 80% dos programadores.

Portanto, pode ser implementado como " pré-análise com introspecção" ... Veja o que o PostgreSQL faz quando declara um SQL-VIEW com SELECT *: o construtor VIEW se transforma *em uma lista de todas as colunas (por introspecção e no momento em que você executa o CRIAR código-fonte VIEW).

Implementação para CREATE VIEW e PREPARE

É uma implementação viável. Suponha tabela tcom campos (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;

O mesmo para a declaração PREPARE .

... então, isso é possível, e é disso que 80% dos programadores precisam, um açúcar de sintaxe para PREPARE e VIEWS!


NOTA: é claro que a sintaxe viável , talvez, não é - column_name, se houver algum conflito no PostgreSQL, para que possamos sugerir EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)ou outro.

Peter Krauss
fonte
1

Esta é a minha função para selecionar todas as colunas esperadas uma. Combinei idéias de postgresonline.com e postgresql tuturial e de outras fontes.

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)
Veli-Matti Sorvala
fonte