web-dev-qa-db-ja.com

SQL:SELECT一部の列を除くすべての列

特定の列を除いて、テーブルのすべての列をSELECTする方法はありますか? ITは、テーブルからすべての非ブロブ列または非ジオメトリック列を選択するのに非常に便利です。

何かのようなもの:

SELECT * -the_geom FROM segments;
  • この機能が意図的に SQL標準 から除外されたと聞いたことがあります。これは、テーブルへの列の追加を変更するとクエリの結果が変わるためです。これは本当ですか?引数は有効ですか?
  • 特にPostgreSQLに回避策はありますか?
119
Adam Matan

このような機能はPostgresにもSQL標準(AFAIK)にもありません。これは非常に興味深い質問だと思うので、少しググって、 postgresonline.com に関する興味深い記事を見つけました。

これらは、スキーマから列を直接選択するアプローチを示しています。

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

そのようなことをする関数を作成できます。このようなトピックはメーリングリストでも議論されましたが、全体的なコンセンサスはほぼ同じでした:スキーマをクエリします。

他の解決策もあると確信していますが、それらにはすべて、ある種の魔法のスキーマ-queriying-fooが含まれると思います。

ところで、これはパフォーマンスを低下させる可能性があるため、SELECT * ...には注意してください

59
DrColossos

本当の答えは、実際にはできないということです。これは何十年もの間要求されている機能であり、開発者はそれを実装することを拒否します。

Postgresオプティマイザは動的関数をブラックボックスと見なすため、スキーマテーブルのクエリを実行することを提案する一般的な回答は効率的に実行できません(以下のテストケースを参照)。つまり、インデックスは使用されず、結合はインテリジェントに行われません。あなたはm4のようなある種のマクロシステムがあればはるかに良いでしょう。少なくとも、オプティマイザを混乱させることはありません(ただし、混乱を招く可能性があります)。コードをフォークして機能を自分で記述したり、プログラミング言語のインターフェイスを使用したりせずに、行き詰まっています。

以下の簡単な概念実証を書いて、plpgsqlでの非常に単純な動的実行でパフォーマンスがどのように低下​​するかを示しました。また、以下では、汎用レコードを返す関数を特定の行タイプに強制し、列を列挙する必要があることにも注意してください。したがって、この方法は、すべてのテーブルに対してこの関数を再作成したくない限り、「すべて選択」では機能しません。

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)

直接クエリがインデックスを使用している間に関数呼び出しがテーブル全体をスキャンしたことがわかるように(95.46 ms vs. 00.07ms。)または、正しい順序でテーブルを結合します。

18
user17130

実際、JSONBが導入された9.4以降のPostgreSQLではある程度可能です。私は(GeoJSONを介して)Googleマップですべての利用可能な属性を表示する方法について同様の質問について考えていました。

jSONBから要素を削除することを提案するircチャネルのjohto。

ここにアイデアがあります

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

個々の列の代わりにjsonを取得している間、それはまさに私が欲しかったものでした。おそらく、jsonを展開して個々の列に戻すことができます。

14
mlt

これを行うことができる(そうすべきではない)唯一の方法は、動的SQLステートメントを使用することです。 (DrColossosが書いたように)システムビューをクエリしてテーブルの構造を見つけ、適切なステートメントを作成するのは簡単です。

PS:テーブル構造を正確に知らないか、または記述せずに、すべてまたは一部の列を選択するのはなぜですか?

6
Marian

コメント で、あなたの動機は、長いコンテンツの列のcontentsを表示しないという便利さを持つことではなく、列自体を表示しないより:

…出力が文字化けする非常に長いジオメトリ文字列を表示せずに、ジオメトリック列のあるテーブルをクエリしたい場合があります。数十個あるかもしれないので、すべての列を指定したくありません。

これは、長いコンテンツをnull(この例ではtext列ですが、抑制したい型に合わせて変更する)に置き換えるヘルパー関数を使用して可能です。

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 |バー| baz 
-:| -:| :---------------------------- 
 1 | 2 |何とか何とか何とか何とか何とか何とか何とか
 3 | 4 |何とか何とか
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 |バー| baz 
-:| -:| :--- 
 1 | 2 | null
 3 | 4 | null

dbfiddle ここ

大きなデータ値を含む列を表示しないことにより、デバッグ中に画面が煩雑にならないようにすることが目的の場合は、次の方法を使用できます。

(「hstore」contribパッケージをまだインストールしていない場合はインストールしてください: "CREATE EXTENSION hstore; ")

Col1、col2、col3を含むテーブル「test」の場合、表示する前に「col2」の値をnullに設定できます。

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

または、表示する前に2つの列をnullに設定します。

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

注意点は、hstoreにフィードするレコードタイプを定義する必要があるため、「test」はテーブルでなければならないことです(エイリアスまたはサブセレクトは機能しません)。

3
Sean

私が発見した回避策はありますが、R内からSQLクエリを送信する必要があります。これは、Rユーザーに役立つ場合があります。

基本的にdplyrパッケージはSQL(特にPostgreSQL)クエリを送信し、-(column_name)引数を受け入れます。

だからあなたの例は次のように書くことができます:

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

上記のように動的に行うことが唯一の答えですが、お勧めしません。長期的に列を追加しても、そのクエリに必ずしも必要ではない場合はどうなりますか?

必要以上の列をプルし始めます。

選択が次のように挿入の一部である場合

TableAに挿入(col1、col2、col3 .. coln)tableBから2列を除くすべてを選択します

列の一致が間違っているため、挿入は失敗します。

それは可能ですが、ほぼすべての列が必要な場合でも、書き込まれるすべての選択に対して必要なすべての列を書き込むことをお勧めします。

3
  • アプリケーションの観点からは、これは遅延ソリューションです。アプリケーションが新しい列をどうするかを自動的に知ることはほとんどありません。

    データブラウザーアプリケーションは、データのメタデータをクエリして、実行中のクエリから列を除外したり、列のデータのサブセットを選択したりできます。新しいBLOBは追加時に除外できます。特定の行のBLOBデータは、必要に応じて選択できます。

  • 動的クエリをサポートするSQLバリアントでは、テーブルのメタデータに対するクエリを使用してクエリを作成できます。あなたの意図のために、名前ではなくタイプに基づいて列を除外します。

3
BillThor

SQL-VIEWSに_*_が表示されない... psqlで_\d any_view_を確認してください。内部表現には(内省的) 前処理 があります。


ここでのすべてのディスカッションは、theissueプロポーザル(質問とディスカッションに含まれる)が構文糖であることを示していますプログラマーにとっては、実際の「SQL最適化の問題」ではありません...まあ、私の推測では、それはプログラマーの80%のためです。

そのため、「事前解析イントロスペクション」として実装できます... _SELECT *_を使用してSQL-VIEWを宣言した場合のPostgreSQLの動作を確認してください:VIEWコンストラクター変換_*_をすべての列のリストに挿入します(イントロスペクションにより、現時点ではCREATE VIEWソースコードを実行します)。

CREATE VIEWおよびPREPAREの実装

それは実行可能な実装です。フィールド__(id serial, name text, the_geom geom)_を含むテーブルtがあるとします。

_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;
_

PREPAREステートメント についても同様です。

...それが可能であり、それがプログラマーの80%が必要としているものです。PREPAREとVIEWSの構文砂糖です!


注:もちろん、可能な構文は、おそらく_- column_name_ではありません。PostgreSQLに競合がある場合、_EXCEPT column_name_を提案できます。
EXCEPT (column_name1, column_name2, ..., column_nameN)またはその他。

2
Peter Krauss

これは、すべての列を選択するための関数です。 postgresonline.compostgresql tuturial のアイデアと他のソースのアイデアを組み合わせました。

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