SQL: SELECT Toutes les colonnes sauf certaines


108

Existe-t-il un moyen d’ SELECTafficher toutes les colonnes d’un tableau, à l’exception de celles spécifiques? C’est très pratique pour sélectionner toutes les colonnes non géométriques ou non géométriques d’un tableau.

Quelque chose comme:

SELECT * -the_geom FROM segments;
  • Une fois, j'ai entendu dire que cette fonctionnalité avait été délibérément exclue du standard SQL, car le fait d'ajouter des colonnes à la table modifierait les résultats de la requête. Est-ce vrai? L'argument est-il valide?
  • Existe-t-il une solution de contournement, en particulier dans PostgreSQL?

Quel est le cas d'utilisation pour lequel vous voulez connaître toutes les colonnes sauf certaines? Est-ce juste pour afficher à l'écran tout en faisant des requêtes manuelles? Cela fait-il partie d'un programme?
joanolo

2
Une table avec 6 colonnes significatives et courtes (a-la name, age, sid) qui convient bien dans la largeur de l' écran, alongwith une longue binaire geomcolonne. Je veux interroger tous les champs sauf le binaire géométrie, et écrire leurs noms un par un est fastidieux.
Adam Matan

Dans ce cas, cela pourrait être plus quelque chose à voir avec l'outil que vous utilisez avec la requête interactive qu'avec le SQL lui-même ...
joanolo

1
@joanolo Plain shell PostgreSQL.
Adam Matan

4
Cela semble tellement évident. Parfois, vous ne souhaitez pas imprimer une ou deux colonnes car elles ne sont pas intéressantes ou vous souhaitez simplement que la table de résultats s'adapte à l'écran (surtout si un client en ligne de commande est utilisé). Je m'attendrais à une syntaxe commeselect (!coluns2,!column5) from sometable;
gumkins

Réponses:


54

Une telle fonctionnalité n'existe ni dans Postgres ni dans SQL Standard (autant que je sache). Je pense que c'est une question assez intéressante, alors j'ai googlé un peu et je suis tombé sur un article intéressant sur postgresonline.com .

Ils montrent une approche qui sélectionne les colonnes directement à partir du 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é abordé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-queriying-foo magique.

BTW: Soyez prudent avec SELECT * ...car cela peut avoir des pénalités de performance


Comment créer une telle fonction? Je ne trouve aucun moyen de créer une fonction qui renvoie une requête inconnue. Je devrais toujours déclarer une table au préalable.
ePascoal

17

La vraie réponse est que vous ne pouvez pas pratiquement. Cette fonctionnalité est demandée depuis des décennies et les développeurs refusent de la mettre en œuvre.

La réponse courante 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 les jointures ne se feront pas de manière intelligente. Vous seriez beaucoup mieux avec une sorte de système macro tel que m4. Au moins, cela ne confondra pas l'optimiseur (mais cela peut encore vous dérouter). Sans forger le code et écrire la fonctionnalité vous-même ou en utilisant une interface de langage de programmation, vous êtes bloqué.

J'ai écrit ci-dessous une simple démonstration de faisabilité montrant la mauvaise performance d'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. Donc, cette méthode ne fonctionnera pas pour 'tout sélectionner sauf' sauf si vous voulez 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 balayé la totalité de la table alors que la requête directe utilisait l'index ( 95,46 ms par rapport à 00,07ms .) .


1
Perspective intéressante. Il s’agit là d’une fonctionnalité destinée aux utilisateurs humains et non au code (ou du moins je l’espère!), Ce qui me permet de comprendre le point de savoir si le client est responsable. On peut supposer que des éléments tels que l'affichage étendu (\ x on) sont implémentés uniquement dans le client et que l'omission de colonnes doit être implémentée à un endroit similaire.
Max Murphy

13

En fait, c'est un peu possible avec PostgreSQL à partir de la version 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 un élément de JSONB.

Voici l'idée

select the_geom,
  row_to_json(foo)::jsonb - '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 reconverti en colonnes individuelles.


Ouais, peut-être quelque chose d'ici, mais je n'ai pas encore réussi à faire fonctionner ceci- stackoverflow.com/questions/36174881/…
chrismarx

6

La seule façon de le faire (ne pas dire que vous devriez) est d'utiliser des instructions SQL dynamiques. Il est facile (comme l'a écrit DrColossos) d'interroger les vues système, de trouver la structure de la table et de construire les instructions appropriées.

PS: Pourquoi voudriez-vous sélectionner toutes / certaines colonnes sans connaître / écrire exactement la structure de votre table?


7
Concernant votre PS: Parfois, je souhaite interroger une table avec une colonne géométrique sans afficher la très longue chaîne de géométrie qui tronque la sortie. Je ne veux pas spécifier toutes les colonnes, car il pourrait y en avoir des dizaines.
Adam Matan

Donc, seul SQL dynamique peut vous éviter beaucoup de saisie :-).
Marian

Tout le monde suppose que celui qui fait la requête est celui qui a conçu la base de données. :-) Supposons que vous ayez besoin d'interroger une ancienne base de données contenant beaucoup de champs (plus de 30) pour générer un fichier Excel, mais qu'un ou deux champs contiennent des informations sensibles que vous ne souhaitez pas transmettre.
yucer

3

Dynamiquement, comme indiqué ci-dessus, c'est la seule réponse mais je ne le recommanderai pas. Que faire si vous ajoutez plus de colonnes sur le long terme mais qu'elles ne sont pas nécessairement requises pour cette requête?

Vous commenceriez à tirer plus de colonne que nécessaire.

Que faire si la sélection fait partie d'un insert comme dans

Insérer dans la tableA (col1, col2, col3 .. coln) Sélectionnez tout sauf 2 colonnes FROM tableB

La correspondance de colonne sera fausse et votre insertion échouera.

C'est possible, mais je recommande quand même d'écrire chaque colonne nécessaire pour chaque sélection écrite, même si presque chaque colonne est requise.


Cette approche est évidemment incorrecte par programmation, mais elle est inoffensive et utile en tant que requête de console pour SELECTs.
Adam Matan

3

Si votre objectif est de supprimer l'encombrement de l'écran lors du débogage en n'affichant pas les colonnes contenant des valeurs de données volumineuses, vous pouvez utiliser le truc suivant:

(installez le paquet contrib "hstore" si vous ne l'avez pas déjà: " CREATE EXTENSION hstore;")

Pour une table "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;

les mises en garde sont que "test" doit être une table (un alias ou un sous-choix ne fonctionnera pas) car le type d'enregistrement alimentant hstore doit être défini.


3

Il existe une solution de contournement que je viens de découvrir, mais elle nécessite l'envoi de requêtes SQL depuis R. Elle peut être utile aux utilisateurs de R.

En gros, le dplyrpaquet envoie des requêtes SQL (et plus particulièrement PostgreSQL) et accepte l' -(column_name)argument.

Donc, votre exemple pourrait être écrit comme suit:

select(segments, -(the_geom))

3

Dans un commentaire, vous expliquez que votre motif est d'avoir la commodité de ne pas afficher le contenu des colonnes à long contenu, plutôt que de ne pas afficher la colonne elle-même:

… Parfois, je souhaite interroger une table avec une colonne géométrique sans afficher la très longue chaîne de géométrie qui perturbe le résultat. Je ne veux pas spécifier toutes les colonnes, car il pourrait y en avoir des dizaines.

Cela est possible, à l'aide d'une fonction d'assistance qui remplace le contenu long par null(n'importe quelle textcolonne de mon exemple, mais que vous modifieriez 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 | nul

dbfiddle ici


2
  • 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 de 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 BLOBs peuvent être exclus une fois ajoutés. Les données BLOB pour des lignes particulières peuvent être sélectionnées à la demande.

  • Dans toute variante SQL prenant en charge les requêtes dynamiques, la requête peut être générée à l'aide d'une requête sur les métadonnées des tables. Pour votre intention, j'exclurais les colonnes basées sur le type plutôt que sur le nom.


2

Vous ne voyez jamais *dans SQL-VIEWS ... vérifiez \d any_viewà votre psql. Il existe un prétraitement (introspectif) 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 cela concerne 80% des programmeurs.

Donc, peut être implémenté comme " pré-analyse avec introspection" ... Voyez ce que fait PostgreSQL lorsque vous déclarez une vue SQL-VIEW avec SELECT *: le constructeur VIEW se transforme *en une liste de toutes les colonnes (par introspection et au moment où vous exécutez le CREATE VIEW code source).

Implémentation pour CREATE VIEW et PREPARE

C'est une implémentation viable. Supposons une table tavec des 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 l' instruction PREPARE .

... donc, c'est possible, et c'est ce dont ont besoin 80% des programmeurs, 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.


1

Ceci est ma fonction pour sélectionner toutes les colonnes en attendre une. J'ai combiné des idées de postgresonline.com et de 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.