it-swarm-eu.dev

SQL: SELECT Alle Spalten außer einigen

Gibt es eine Möglichkeit, SELECT alle Spalten in einer Tabelle außer bestimmten zu _? IT wäre sehr praktisch, um alle nicht blobigen oder nicht geometrischen Spalten aus einer Tabelle auszuwählen.

Etwas wie:

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

Eine solche Funktion gibt es weder in Postgres noch im SQL Standard (AFAIK). Ich denke, das ist eine ziemlich interessante Frage, also habe ich ein bisschen gegoogelt und bin auf einen interessanten Artikel auf postgresonline.com gestoßen.

Sie zeigen einen Ansatz, bei dem die Spalten direkt aus dem Schema ausgewählt werden:

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-Abfrage-Foo beinhalten werden.

Übrigens: Seien Sie vorsichtig mit SELECT * ... da dies zu Leistungseinbußen führen kann

59
DrColossos

Die wirkliche Antwort ist, dass man einfach praktisch nicht kann. Dies ist seit Jahrzehnten eine gefragte Funktion, und die Entwickler weigern sich, sie zu implementieren.

Die beliebte Antwort, die vorschlägt, die Schematabellen abzufragen, kann nicht effizient ausgeführt werden, da der Postgres-Optimierer dynamische Funktionen als Black Box betrachtet (siehe Testfall unten). Das bedeutet, dass Indizes nicht verwendet werden und Verknüpfungen nicht intelligent durchgeführt werden. Mit einem Makrosystem wie m4 wären Sie viel besser dran. Zumindest wird es den Optimierer nicht verwirren (aber es kann Sie dennoch verwirren). Ohne den Code zu forken und die Funktion 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 auflisten muss. Diese Methode funktioniert also nicht für "Alle auswählen außer", 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 gescannt, während die direkte Abfrage den Index verwendet hat (95,46 ms gegenüber 00,07 ms.). Diese Art von Funktionen würde jede Art von komplizierter Abfrage tanken, die zur Verwendung von Indizes erforderlich ist oder verbinden Sie Tabellen in der richtigen Reihenfolge.

18
user17130

Mit PostgreSQL ab 9.4, wo JSONB eingeführt wurde, ist dies tatsächlich einigermaßen möglich. Ich habe über eine ähnliche Frage nachgedacht, wie alle verfügbaren Attribute in Google Map (über GeoJSON) angezeigt werden können.

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

Hier ist die Idee

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

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

14
mlt

Sie können dies nur mithilfe dynamischer SQL-Anweisungen tun (sagen Sie nicht, dass Sie dies tun sollten). Es ist einfach (wie DrColossos schrieb), die Systemansichten abzufragen, die Struktur der Tabelle zu finden und die richtigen Anweisungen zu erstellen.

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

6
Marian

In ein Kommentar erklären Sie, dass Ihr Motiv darin besteht, den Inhalt von Spalten mit langem Inhalt nicht anzuzeigen als 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 beeinträchtigt. Ich möchte nicht alle Spalten angeben, da es möglicherweise einige Dutzend gibt.

Dies ist mit Hilfe einer Hilfsfunktion möglich, die den langen Inhalt durch null ersetzt (eine beliebige text -Spalte 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 
 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

Wenn Sie beim Debuggen Unordnung vom Bildschirm entfernen möchten, indem Sie keine Spalten mit großen Datenwerten anzeigen, können Sie den folgenden Trick verwenden:

(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 Folgendes angezeigt wird:

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

Oder setzen Sie zwei Spalten auf null, bevor Sie Folgendes 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, der in hstore eingespeist wird, definiert werden muss.

3
Sean

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

Grundsätzlich sendet das Paket dplyr SQL-Abfragen (und insbesondere PostgreSQL-Abfragen) und akzeptiert das Argument -(column_name).

Ihr Beispiel könnte also wie folgt geschrieben werden:

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

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 jedoch nicht unbedingt für diese Abfrage erforderlich sind?

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

Was ist, wenn die Auswahl Teil einer Einfügung wie in ist?

In Tabelle A einfügen (Spalte1, Spalte2, Spalte3 .. Spalte) Wählen Sie alles außer 2 Spalten aus TabelleB 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 ausgewählte Auswahl zu schreiben, auch wenn fast jede Spalte erforderlich ist.

3
  • 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 können die Metadaten für die Daten abfragen und die Spalten von den ausgeführten Abfragen ausschließen oder eine Teilmenge der Daten der Spalte auswählen. 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 für die Metadaten der Tabellen erstellt werden. Für Ihre Absicht würde ich Spalten ausschließen, die eher auf dem Typ als auf dem Namen basieren.

3
BillThor

Sie sehen * In SQL-VIEWS nie ... überprüfen Sie \d any_view In Ihrem psql. Es gibt ein (introspektives) Vorverarbeitung für die interne Darstellung.


Alle Diskussionen hier zeigen, dass der Issue-Vorschlag (implizit in der Frage und den Diskussionen enthalten) ein Syntaxzucker für Programmierer ist, kein echtes "SQL-Optimierungsproblem" ... Nun, ich denke, es ist für 80% der Programmierer.

Kann also als " Pre-Parsing mit Introspektion" implementiert werden ... Sehen Sie, was PostgreSQL tut, wenn Sie eine SQL-VIEW mit SELECT * Deklarieren: Der VIEW-Konstruktor transformiert * In eine Liste aller Spalten (durch Selbstbeobachtung und in dem Moment, in dem Sie den Quellcode CREATE VIEW ausführen).

Implementierung für CREATE VIEW und PREPARE

Es ist eine praktikable Implementierung. Angenommen, Tabelle t mit 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 PREPARE-Anweisung .

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


HINWEIS: Natürlich ist die praktikable Syntax möglicherweise nicht - column_name, Wenn es in PostgreSQL einen Konflikt gibt, können wir EXCEPT column_name Vorschlagen.
EXCEPT (column_name1, column_name2, ..., column_nameN) oder andere.

2
Peter Krauss

Dies ist meine Funktion, um alle Spalten auszuwählen, die eine erwarten. Ich habe Ideen aus 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)
1