SQL: SELECT Alle Spalten außer einigen


108

Gibt es einen Weg zu SELECTallen Spalten in einer Tabelle, außer zu bestimmten? Es wäre sehr praktisch, alle nicht-blob- oder nicht-geometrischen Spalten aus einer Tabelle auszuwählen.

So etwas wie:

SELECT * -the_geom FROM segments;
  • Ich habe einmal gehört, dass diese Funktionalität absichtlich vom SQL-Standard ausgeschlossen wurde, da das Hinzufügen von Spalten zur Tabelle die Abfrageergebnisse verändert. Ist das wahr? Ist das Argument gültig?
  • Gibt es eine Problemumgehung, insbesondere in PostgreSQL?

Für welchen Anwendungsfall möchten Sie alle Spalten außer einigen kennen? Soll es nur auf dem Bildschirm angezeigt werden, während einige manuelle Abfragen ausgeführt werden? Ist es Teil eines Programms?
Joanolo

2
Eine Tabelle mit 6 bedeutungsvoll, kurze Säulen (A-la name, age, sid), die sich gut in die Bildschirmbreite, alongwith eine lange binäre passt geomSpalte. Ich möchte alle Felder mit Ausnahme der Geometrie-Binärdatei abfragen, und es ist mühsam, ihre Namen nacheinander zu schreiben.
Adam Matan

In diesem Fall hat dies möglicherweise eher mit dem Tool zu tun, das Sie für die interaktive Abfrage verwenden, als mit SQL selbst ...
joanolo

1
@joanolo Einfache PostgreSQL-Shell.
Adam Matan

3
Das sieht so offensichtlich aus. Manchmal möchten Sie eine oder zwei Spalten nicht drucken, weil sie nicht interessant sind, oder Sie möchten nur, dass die Ergebnistabelle in den Bildschirm passt (insbesondere, wenn ein Befehlszeilenclient verwendet wird). Ich würde eine Syntax wieselect (!coluns2,!column5) from sometable;
gumkins

Antworten:


54

Eine solche Funktion gibt es weder in Postgres noch im SQL-Standard (AFAIK). Ich halte das für eine sehr interessante Frage, deshalb habe ich ein wenig gegoogelt und bin auf einen interessanten Artikel auf postgresonline.com gestoßen .

Sie zeigen einen Ansatz, der die Spalten direkt aus dem Schema auswählt:

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

Sie könnten eine Funktion erstellen, die so etwas tut. Solche Themen wurden auch auf den Mailinglisten diskutiert, aber der allgemeine Konsens war ziemlich gleich: Fragen Sie das Schema ab.

Ich bin mir sicher, dass es andere Lösungen gibt, aber ich denke, dass sie alle eine Art magisches Schema-Abfragen-Foo beinhalten werden.

Übrigens: Seien Sie vorsichtig, SELECT * ...da dies Leistungseinbußen nach sich ziehen kann


Wie erstelle ich eine solche Funktion? Ich kann keine Möglichkeit finden, eine Funktion zu erstellen, die eine unbekannte Abfrage zurückgibt. Ich müsste vorher immer eine Tabelle deklarieren.
ePascoal

17

Die wirkliche Antwort ist, dass man das praktisch einfach nicht kann. Dies ist seit Jahrzehnten ein begehrtes Feature und wird von den Entwicklern nicht mehr implementiert.

Die verbreitete Antwort, die vorschlägt, die Schematabellen abzufragen, kann nicht effizient ausgeführt werden, da der Postgres-Optimierer dynamische Funktionen als Black Box ansieht (siehe Testfall unten). Dies bedeutet, dass Indizes nicht verwendet und Verknüpfungen nicht intelligent ausgeführt werden. Mit einem Makrosystem wie m4 wäre es viel besser für Sie. Zumindest wird es den Optimierer nicht verwirren (aber es kann Sie trotzdem verwirren.) Ohne den Code zu forken und das Feature selbst zu schreiben oder eine Programmiersprachenschnittstelle zu verwenden, stecken Sie fest.

Ich habe unten einen einfachen Proof of Concept geschrieben, der zeigt, wie schlecht die Leistung bei einer sehr einfachen dynamischen Ausführung in plpgsql wäre. Beachten Sie auch, dass ich unten eine Funktion erzwingen muss, die einen generischen Datensatz in einen bestimmten Zeilentyp zurückgibt, und die Spalten aufzählen muss. Diese Methode funktioniert also nicht für "Alles auswählen, aber", es sei denn, Sie möchten diese Funktion für alle Ihre Tabellen neu erstellen.

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)

Wie Sie sehen können, hat der Funktionsaufruf die gesamte Tabelle durchsucht, während die direkte Abfrage den Index verwendete ( 95,46 ms gegenüber 00,07 ms ). Diese Art von Funktionen würden jede Art von komplizierter Abfrage speichern , die zur Verwendung von Indizes oder zum Verknüpfen von Tabellen in der richtigen Reihenfolge erforderlich ist .


1
Interessante Perspektive. Dies ist definitiv eher eine Funktion für menschliche Benutzer als für Code (das sollte ich hoffen!), Damit ich verstehen kann, wie man den Client zur Verantwortung zieht. Vermutlich werden Dinge wie die erweiterte Anzeige (\ x on) nur im Client implementiert, und weggelassene Spalten sollten an einer ähnlichen Stelle implementiert werden.
Max Murphy

13

Es ist tatsächlich mit PostgreSQL ab 9.4 möglich, wo JSONB eingeführt wurde. Ich habe über ähnliche Fragen nachgedacht, wie alle verfügbaren Attribute in Google Map (über GeoJSON) angezeigt werden sollen.

johto on irc channel schlug vor, ein Element aus JSONB zu löschen.

Hier ist die Idee

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

Während Sie json anstelle von einzelnen Spalten erhalten, war es genau das, was ich wollte. Vielleicht kann json wieder in einzelne Spalten erweitert werden.


Ja, vielleicht etwas von hier, aber ich habe es noch nicht zum Laufen gebracht
stackoverflow.com/questions/36174881/…

6

Die einzige Möglichkeit, dies zu tun, ist die Verwendung dynamischer SQL-Anweisungen. Es ist einfach (wie DrColossos geschrieben hat), die Systemansichten abzufragen, die Struktur der Tabelle zu ermitteln und die richtigen Anweisungen zu erstellen.

PS: Warum sollten Sie alle / einige Spalten auswählen, ohne Ihre Tabellenstruktur genau zu kennen / zu schreiben?


7
Zu Ihrer PS: Manchmal möchte ich eine Tabelle mit einer geometrischen Spalte abfragen, ohne die sehr lange Geometriezeichenfolge anzuzeigen, die die Ausgabe verstümmelt. Ich möchte nicht alle Spalten angeben, da möglicherweise einige Dutzend vorhanden sind.
Adam Matan

Nur dynamisches SQL erspart Ihnen also viel Tipparbeit :-).
Marian

Jeder geht davon aus, dass derjenige, der die Abfrage vornimmt, derjenige ist, der die Datenbank entworfen hat. :-) Angenommen, Sie müssen eine alte Datenbank mit vielen Feldern (mehr als 30) abfragen, um ein Excel zu generieren, aber es gibt ein oder zwei Felder mit vertraulichen Informationen, die Sie nicht liefern möchten.
Yucer

3

Dynamisch wie oben angegeben ist die einzige Antwort, aber ich werde es nicht empfehlen. Was ist, wenn Sie auf lange Sicht weitere Spalten hinzufügen, diese aber für diese Abfrage nicht unbedingt erforderlich sind?

Sie würden anfangen, mehr Spalte zu ziehen, als Sie benötigen.

Was ist, wenn der Select Teil eines Inserts wie in ist?

Einfügen in TabelleA (Spalte1, Spalte2, Spalte3 .. Spalte) Wählen Sie mit Ausnahme von 2 Spalten AUS TabelleB alles aus

Die Spaltenübereinstimmung ist falsch und Ihre Einfügung schlägt fehl.

Es ist möglich, aber ich empfehle trotzdem, jede benötigte Spalte für jede geschriebene Auswahl zu schreiben, auch wenn fast jede Spalte erforderlich ist.


Dieser Ansatz ist offensichtlich programmgesteuert falsch, aber harmlos und nützlich als Konsolenabfrage für SELECTs.
Adam Matan

3

Wenn Ihr Ziel darin besteht, während des Debuggens Unordnung vom Bildschirm zu entfernen, indem keine Spalten mit großen Datenwerten angezeigt werden, können Sie den folgenden Trick anwenden:

(Installieren Sie das Contrib-Paket "hstore", falls Sie es noch nicht haben: " CREATE EXTENSION hstore;")

Für eine Tabelle "test" mit col1, col2, col3 können Sie den Wert von "col2" auf null setzen, bevor Sie Folgendes anzeigen:

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

Oder setzen Sie zwei Spalten auf null, bevor Sie anzeigen:

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

Die Einschränkung ist, dass "test" eine Tabelle sein muss (ein Alias ​​oder eine Unterauswahl funktioniert nicht), da der Datensatztyp definiert werden muss, der in hstore eingespeist wird.


3

Es gibt eine Problemumgehung, die ich gerade entdeckt habe, die jedoch das Senden von SQL-Abfragen aus R erfordert. Sie kann für R-Benutzer hilfreich sein.

Grundsätzlich dplyrsendet das Paket SQL- (und insbesondere PostgreSQL-) Anfragen und akzeptiert das -(column_name)Argument.

So könnte Ihr Beispiel wie folgt geschrieben werden:

select(segments, -(the_geom))

3

In einem Kommentar erklären Sie, dass Ihr Motiv darin besteht, den Inhalt von Spalten mit langem Inhalt nicht anzuzeigen, anstatt die Spalte selbst nicht anzuzeigen:

… Manchmal möchte ich eine Tabelle mit einer geometrischen Spalte abfragen, ohne die sehr lange Geometriezeichenfolge anzuzeigen, die die Ausgabe verstümmelt. Ich möchte nicht alle Spalten angeben, da möglicherweise einige Dutzend vorhanden sind.

Dies ist mit Hilfe einer Hilfsfunktion möglich, die den langen Inhalt durch ersetzt null(jede textSpalte in meinem Beispiel, aber Sie würden dies für die Typen ändern, die Sie unterdrücken möchten):

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 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 hier


2
  • Aus Sicht der Anwendung ist dies eine träge Lösung. Es ist unwahrscheinlich, dass eine Anwendung automatisch weiß, was mit den neuen Spalten zu tun ist.

    Datenbrowseranwendungen fragen möglicherweise die Metadaten nach den Daten ab und schließen die Spalten von den ausgeführten Abfragen aus oder wählen eine Teilmenge der Spaltendaten aus. Neue BLOBs können beim Hinzufügen ausgeschlossen werden. BLOB-Daten für bestimmte Zeilen können bei Bedarf ausgewählt werden.

  • In jeder SQL-Variante, die dynamische Abfragen unterstützt, kann die Abfrage mithilfe einer Abfrage der Tabellen-Metadaten erstellt werden. Für Ihre Absicht würde ich Spalten basierend auf dem Typ und nicht auf dem Namen ausschließen.


2

Sie sehen nie *in SQL-VIEWS ... überprüfen Sie \d any_viewan Ihrem psql. Es gibt eine (introspektive) Vorverarbeitung für die interne Repräsentation.


Alle hier zur Diskussion zeigt , dass die Frage Vorschlag (implizit in der Frage und Diskussionen) eine Syntax Zucker für Programmierer ist, nicht ein echtes „SQL - Optimierung Problem“ ... Nun, meine Vermutung, es für 80% des Programmierers ist.

Kann also als " Pre-Parsing mit Introspection" implementiert werden ... Sehen Sie, was PostgreSQL macht, wenn Sie eine SQL-VIEW deklarieren mit SELECT *: Der VIEW-Konstruktor wandelt sich *in eine Liste aller Spalten um (nach Introspection und zum Zeitpunkt der Ausführung der CREATE VIEW-Quellcode).

Implementierung für CREATE VIEW und PREPARE

Es ist eine praktikable Implementierung. Angenommen, eine Tabelle tmit Feldern (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;

Gleiches gilt für die PREPARE-Anweisung .

... also, das ist möglich und das ist es, was 80% der Programmierer brauchen, ein Syntaxzucker für PREPARE und VIEWS!


HINWEIS: natürlich die tragfähige Syntax vielleicht nicht der Fall ist - column_name, wenn es einige Konflikte in PostgreSQL sind, so können wir vorschlagen EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)oder andere.


1

Dies ist meine Funktion, um alle Spalten auszuwählen, die eine erwarten. Ich habe Ideen von postgresonline.com und postgresql tuturial und aus anderen Quellen kombiniert .

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.