Existe-t-il un moyen de SELECT
toutes les colonnes d'une table, à l'exception de celles spécifiques? Il serait très pratique de sélectionner toutes les colonnes non blob ou non géométriques d'une table.
Quelque chose comme:
SELECT * -the_geom FROM segments;
Une telle fonctionnalité n'existe ni dans Postgres ni dans le SQL Standard (AFAIK). Je pense que c'est une question assez intéressante donc j'ai googlé un peu et suis tombé sur un article intéressant sur postgresonline.com .
Ils montrent une approche qui sélectionne les colonnes directement dans le schéma:
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
Vous pouvez créer une fonction qui fait quelque chose comme ça. Ces sujets ont également été discutés sur les listes de diffusion, mais le consensus général était à peu près le même: interroger le schéma.
Je suis sûr qu'il existe d'autres solutions, mais je pense qu'elles impliqueront toutes une sorte de schéma magique.
BTW: soyez prudent avec SELECT * ...
car cela peut entraîner des pénalités de performance
La vraie réponse est que vous ne pouvez tout simplement pas en pratique. C'est une fonctionnalité demandée depuis des décennies et les développeurs refusent de l'implémenter.
La réponse populaire suggérant d'interroger les tables de schéma ne pourra pas s'exécuter efficacement car l'optimiseur Postgres considère les fonctions dynamiques comme une boîte noire (voir le cas de test ci-dessous). Cela signifie que les index ne seront pas utilisés et que les jointures ne se feront pas intelligemment. Vous seriez beaucoup mieux avec une sorte de système macro comme m4. Au moins, cela ne confondra pas l'optimiseur (mais il peut encore vous embrouiller.) Sans bifurquer le code et écrire la fonctionnalité vous-même ou en utilisant une interface de langage de programmation, vous êtes coincé.
J'ai écrit une simple preuve de concept ci-dessous montrant à quel point les performances seraient mauvaises avec une exécution dynamique très simple dans plpgsql. Notez également que ci-dessous, je dois contraindre une fonction renvoyant un enregistrement générique dans un type de ligne spécifique et énumérer les colonnes. Cette méthode ne fonctionnera donc pas pour "sélectionner tout mais" sauf si vous souhaitez refaire cette fonction pour toutes vos tables.
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)
Comme vous pouvez le voir, l'appel de fonction a analysé toute la table tandis que la requête directe utilisait l'index (95,46 ms contre 00,07 ms.) Ces types de fonctions chargeaient tout type de requête compliquée qui nécessitait l'utilisation d'index ou joindre des tables dans le bon ordre.
C'est en fait quelque peu possible avec PostgreSQL commençant par 9.4 où JSONB a été introduit. Je réfléchissais à une question similaire sur la façon d'afficher tous les attributs disponibles dans Google Map (via GeoJSON).
johto sur le canal irc a suggéré d'essayer de supprimer l'élément de JSONB.
Voici l'idée
select the_geom,
to_jsonb(foo) - 'the_geom'::text attributes
from (
select * from
segments
) foo
Bien que vous obteniez json au lieu de colonnes individuelles, c'était exactement ce que je voulais. Peut-être que json peut être développé en colonnes individuelles.
La seule façon de le faire (ne dites pas que vous devriez le faire) est d'utiliser des instructions sql dynamiques. Il est facile (comme l'a écrit DrColossos) d'interroger les vues système et de trouver la structure de la table et de créer des instructions appropriées.
PS: Pourquoi voudriez-vous sélectionner toutes/certaines colonnes sans connaître/écrire exactement la structure de votre table?
Dans n commentaire vous expliquez que votre motif est d'avoir la commodité de ne pas afficher le contenu des colonnes avec un contenu long, plutôt que de ne pas afficher la colonne elle-même:
… Parfois, je veux interroger une table avec une colonne géométrique, sans afficher la chaîne de géométrie très longue qui brouille la sortie. Je ne veux pas spécifier toutes les colonnes, car il pourrait y en avoir des dizaines.
C'est possible, à l'aide d'une fonction d'assistance qui remplace le contenu long par null
(n'importe quelle colonne text
dans mon exemple, mais vous modifieriez cela pour les types que vous souhaitez supprimer):
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 ici
Si votre objectif est de supprimer l'encombrement de l'écran pendant le débogage en n'affichant pas les colonnes avec de grandes valeurs de données, vous pouvez utiliser l'astuce suivante:
(installez le paquet contrib "hstore" si vous ne l'avez pas déjà: "CREATE EXTENSION hstore;
")
Pour un tableau "test" avec col1, col2, col3, vous pouvez définir la valeur de "col2" sur null avant d'afficher:
select (r).* from (select (test #= hstore('col2',null)) as r from test) s;
Ou, définissez deux colonnes sur null avant d'afficher:
select (r).* from (select (test #= hstore('col2',null) #= hstore('col1',null)) as r from test) s;
la mise en garde est que "test" doit être une table (un alias ou une sous-sélection ne fonctionnera pas) car le type d'enregistrement alimentant hstore doit être défini.
Il y a une solution de contournement que je viens de découvrir, mais elle nécessite d'envoyer des requêtes SQL à partir de R. Elle peut être utile aux utilisateurs de R.
Fondamentalement, le package dplyr
envoie des requêtes SQL (et spécifiquement PostgreSQL) et accepte l'argument -(column_name)
.
Ainsi, votre exemple pourrait être écrit comme suit:
select(segments, -(the_geom))
Dynamiquement comme indiqué ci-dessus est la seule réponse mais je ne le recommanderai pas. Que se passe-t-il si vous ajoutez plus de colonnes à long terme mais qu'elles ne sont pas nécessairement requises pour cette requête?
Vous commenceriez à tirer plus de colonnes que vous n'en avez besoin.
Que faire si la sélection fait partie d'un insert comme dans
Insérer dans le tableauA (col1, col2, col3 .. coln) Sélectionner tout sauf 2 colonnes DU tableauB
La correspondance des colonnes sera incorrecte et votre insertion échouera.
C'est possible, mais je recommande toujours d'écrire chaque colonne nécessaire pour chaque sélection écrite, même si presque chaque colonne est requise.
Du point de vue de l'application, il s'agit d'une solution paresseuse. Il est peu probable qu'une application sache automatiquement quoi faire avec la ou les nouvelles colonnes.
Les applications du navigateur de données peuvent interroger les métadonnées pour les données et exclure les colonnes des requêtes en cours d'exécution, ou sélectionner un sous-ensemble des données de la colonne. Les nouveaux BLOB peuvent être exclus lorsqu'ils sont ajoutés. Les données BLOB pour des lignes particulières peuvent être sélectionnées à la demande.
Dans toute variante SQL qui prend en charge les requêtes dynamiques, la requête peut être créée à l'aide d'une requête sur les métadonnées des tables. Pour votre intention, j'exclure les colonnes basées sur le type plutôt que sur le nom.
Vous ne voyez jamais *
Dans SQL-VIEWS ... vérifiez \d any_view
Dans votre psql
. Il existe un (introspectif) prétraitement pour la représentation interne.
Toutes les discussions ici montrent que la proposition de problème (implicite dans la question et les discussions) est un sucre de syntaxe pour les programmeurs, pas un vrai "problème d'optimisation SQL" ... Eh bien, je suppose que c'est pour 80% des programmeurs.
Peut donc être implémenté comme " pré-analyse avec introspection" ... Voir ce que PostgreSQL fait lorsque vous déclarez un SQL-VIEW avec SELECT *
: Le constructeur VIEW transforme *
Dans une liste de toutes les colonnes (par introspection et au moment où vous exécutez le code source CREATE VIEW).
Il s'agit d'une mise en œuvre viable. Supposons que la table t
avec les champs (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;
Idem pour instruction PREPARE .
... donc, c'est possible, et c'est ce dont 80% des programmeurs ont besoin, un sucre de syntaxe pour PREPARE et VIEWS!
NOTE: bien sûr la syntaxe viable n'est peut-être pas - column_name
, S'il y a un conflit dans PostgreSQL, nous pouvons donc suggérer EXCEPT column_name
,EXCEPT (column_name1, column_name2, ..., column_nameN)
ou autre.
C'est ma fonction pour sélectionner toutes les colonnes, attendez-en une. J'ai combiné des idées de postgresonline.com et postgresql tuturial et d'autres sources.
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)