SQL: SELECCIONAR Todas las columnas excepto algunas

108

¿Hay alguna forma de SELECTtodas las columnas de una tabla, excepto las específicas? Sería muy conveniente seleccionar todas las columnas no geométricas o no geométricas de una tabla.

Algo como:

SELECT * -the_geom FROM segments;
  • Una vez escuché que esta funcionalidad se excluyó deliberadamente del estándar SQL porque cambiar agregar columnas a la tabla alterará los resultados de la consulta. ¿Es esto cierto? ¿Es válido el argumento?
  • ¿Hay alguna solución, especialmente en PostgreSQL?
Adam Matan
fuente
¿Cuál es el caso de uso para el que desea conocer todas las columnas excepto algunas? ¿Es solo para mostrar en pantalla mientras realizo algunas consultas manuales? ¿Es parte de un programa?
joanolo
2
Una mesa con 6, columnas cortas significativas (a-la name, age, sid) que encaja muy bien en la anchura de la pantalla, alongwith un binario largo geomde la columna. Quiero consultar todos los campos excepto el binario de geometría, y escribir sus nombres uno por uno es tedioso.
Adam Matan
En ese caso, esto podría tener más que ver con la herramienta que está utilizando con la consulta interactiva que con el propio SQL ...
joanolo
1
@joanolo Plain PostgreSQL shell.
Adam Matan
44
Esto se ve tan obvio. A veces no desea imprimir una o dos columnas porque no son interesantes o simplemente desea que la tabla de resultados se ajuste a la pantalla (especialmente si se utiliza un cliente de línea de comandos). Esperaría una sintaxis comoselect (!coluns2,!column5) from sometable;
gumkins

Respuestas:

54

Tal característica no existe en Postgres ni en el Estándar SQL (AFAIK). Creo que esta es una pregunta bastante interesante, así que busqué en Google un poco y encontré un artículo interesante en postgresonline.com .

Muestran un enfoque que selecciona las columnas directamente del 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

Podría crear una función que haga algo así. Dichos temas también se discutieron en las listas de correo, pero el consenso general fue más o menos el mismo: consultar el esquema.

Estoy seguro de que hay otras soluciones, pero creo que todas involucrarán algún tipo de esquema mágico-queriying-foo.

Por cierto: tenga cuidado SELECT * ...ya que esto puede tener sanciones de rendimiento

DrColossos
fuente
¿Cómo crear tal función? No puedo encontrar ninguna manera de crear una función que devuelva una consulta desconocida. Siempre tendría que declarar una tabla de antemano.
ePascoal
17

La verdadera respuesta es que simplemente no puedes prácticamente. Esta ha sido una característica solicitada durante décadas y los desarrolladores se niegan a implementarla.

La respuesta popular que sugiere consultar las tablas de esquema no podrá ejecutarse de manera eficiente porque el optimizador de Postgres considera las funciones dinámicas como un cuadro negro (consulte el caso de prueba a continuación). Eso significa que los índices no se utilizarán y las uniones no se realizarán de manera inteligente. Sería mucho mejor con algún tipo de sistema macro como m4. Al menos no confundirá al optimizador (pero aún así puede confundirlo a usted). Sin bifurcar el código y escribir la función usted mismo o usar una interfaz de lenguaje de programación, está atascado.

Escribí una prueba de concepto simple a continuación que muestra cuán malo sería el rendimiento con una ejecución dinámica muy simple en plpgsql. Observe también que a continuación tengo que forzar una función que devuelve un registro genérico a un tipo de fila específico y enumerar las columnas. Por lo tanto, este método no funcionará para 'seleccionar todo pero' a menos que desee rehacer esta función para todas sus tablas.

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 puede ver, la llamada a la función escaneó toda la tabla mientras la consulta directa usaba el índice ( 95.46 ms vs. 00.07ms .) Este tipo de funciones acumularían cualquier tipo de consulta complicada que necesitara usar índices o unir tablas en el orden correcto .

usuario17130
fuente
1
Perspectiva interesante Esta es definitivamente una característica para usuarios humanos en lugar de código (¡o eso espero!) Para que pueda ver el punto de responsabilizar al cliente. Presumiblemente, cosas como la pantalla extendida (\ x on) se implementan exclusivamente en el cliente y la omisión de columnas debe implementarse en un lugar similar.
Max Murphy
13

En realidad, es algo posible con PostgreSQL comenzando con 9.4 donde se introdujo JSONB. Estaba reflexionando sobre una pregunta similar sobre cómo mostrar todos los atributos disponibles en Google Map (a través de GeoJSON).

johto en el canal irc sugirió intentar eliminar el elemento de JSONB.

Aqui esta la idea

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

Si bien obtienes json en lugar de columnas individuales, era exactamente lo que quería. Quizás json pueda expandirse nuevamente en columnas individuales.

mlt
fuente
Sí, tal vez algo de aquí, pero no he conseguido que esto funcione todavía- stackoverflow.com/questions/36174881/...
chrismarx
6

La única forma en que puede (no diga que debe hacerlo) es mediante el uso de sentencias sql dinámicas. Es fácil (como escribió DrColossos) consultar las vistas del sistema y encontrar la estructura de la tabla y construir declaraciones adecuadas.

PD: ¿Por qué querrías seleccionar todas / algunas columnas sin saber / escribir exactamente la estructura de tu tabla?

Mariana
fuente
77
Con respecto a su PS: a veces quiero consultar una tabla con columna geométrica, sin mostrar la cadena de geometría muy larga que confunde la salida. No quiero especificar todas las columnas, porque puede haber algunas docenas.
Adam Matan
Entonces, solo sql dinámico puede salvarte de escribir mucho :-).
Marian
Todos asumen que quien hace la consulta es quien diseñó la base de datos. :-) Suponga que necesita consultar una base de datos antigua con muchos campos (más de 30) para generar un Excel, pero hay uno o dos campos que tienen información confidencial que no desea entregar.
Yucer
3

Dinámicamente, como se indicó anteriormente, es la única respuesta, pero no la recomendaré. ¿Qué sucede si agrega más columnas a largo plazo pero no son necesariamente necesarias para esa consulta?

Comenzaría a tirar de más columna de la que necesita.

¿Qué pasa si la selección es parte de una inserción como en

Insertar en la tabla A (col1, col2, col3 .. coln) Seleccione todo menos 2 columnas DE la tablaB

La coincidencia de columna será incorrecta y su inserción fallará.

Es posible, pero aún así recomiendo escribir todas las columnas necesarias para cada selección escrita, incluso si se requiere casi cada columna.

Nicolas de Fontenay
fuente
Este enfoque es obviamente incorrecto desde el punto de vista programático, pero es inofensivo y útil como una consulta de consola para SELECTs.
Adam Matan
3

Si su objetivo es eliminar el desorden de la pantalla durante la depuración al no mostrar columnas con valores de datos grandes, puede usar el siguiente truco:

(instale el paquete de contribución "hstore" si aún no lo tiene: " CREATE EXTENSION hstore;")

Para una tabla "prueba" con col1, col2, col3, puede establecer el valor de "col2" en nulo antes de mostrar:

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

O establezca dos columnas en nulo antes de mostrar:

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

la advertencia es que "prueba" debe ser una tabla (un alias o subselección no funcionará) ya que el tipo de registro que se introduce en hstore debe estar definido.

Sean
fuente
3

Hay una solución que acabo de descubrir, pero requiere enviar consultas SQL desde R. Puede ser útil para los usuarios de R.

Básicamente, el dplyrpaquete envía consultas SQL (y específicamente PostgreSQL) y acepta el -(column_name)argumento.

Entonces su ejemplo podría escribirse de la siguiente manera:

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

En un comentario , explica que su motivo es tener la conveniencia de no mostrar el contenido de columnas con contenido largo, en lugar de no mostrar la columna en sí:

... A veces quiero consultar una tabla con una columna geométrica, sin mostrar la cadena de geometría muy larga que confunde la salida. No quiero especificar todas las columnas, porque puede haber algunas docenas.

Esto es posible, con la ayuda de una función auxiliar que reemplaza el contenido largo con null(cualquier textcolumna en mi ejemplo, pero lo modificaría para los tipos que desea 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 | 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 | nulo 
  3 | 4 | nulo

dbfiddle aquí

Jack Douglas
fuente
2
  • Desde la perspectiva de la aplicación, esta es una solución perezosa. Es poco probable que una aplicación sepa automáticamente qué hacer con las nuevas columnas.

    Las aplicaciones del navegador de datos pueden consultar los metadatos para los datos y excluir las columnas de las consultas que se ejecutan, o seleccionar un subconjunto de los datos de la columna. Los nuevos BLOB se pueden excluir cuando se agregan. Los datos BLOB para filas particulares se pueden seleccionar a pedido.

  • En cualquier variante SQL que admita consultas dinámicas, la consulta se puede generar utilizando una consulta en los metadatos de las tablas. Para su intención, excluiría columnas basadas en el tipo en lugar del nombre.

BillThor
fuente
2

Nunca se ve *en SQL-VIEWS ... verifique \d any_viewen su psql. Hay un preprocesamiento (introspectivo) para la representación interna.


Toda la discusión aquí muestra que la propuesta de problema (implícita en la pregunta y las discusiones) es un azúcar de sintaxis para los programadores, no un verdadero "problema de optimización de SQL" ... Bueno, supongo que es para el 80% de los programadores.

Por lo tanto, se puede implementar como " análisis previo con introspección" ... Vea lo que hace PostgreSQL cuando declara un SQL-VIEW con SELECT *: el constructor VIEW se transforma *en una lista de todas las columnas (por introspección y en el momento en que ejecuta el CREAR VER código fuente).

Implementación para CREATE VIEW y PREPARE

Es una implementación viable. Supongamos una tabla tcon 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;

Lo mismo para la declaración PREPARE .

... entonces, eso es posible, y eso es lo que necesita el 80% de los programadores, ¡un azúcar de sintaxis para PREPARE y VIEWS!


NOTA: por supuesto, la sintaxis viable tal vez no es - column_name, si hay algún conflicto en PostgreSQL, por lo que pueden sugerir EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)o de otro tipo.

Peter Krauss
fuente
1

Esta es mi función para seleccionar todas las columnas y esperar una. Combiné ideas de postgresonline.com y postgresql tuturial y de otras fuentes.

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
fuente