PostgreSQLで0列の扱い

Posted on:

きっかけ

現在のバージョンのPostgreSQLではSELECT ;というSQL文を実行してもエラーになりません。 試してみると(1 row)と返ってきたので、あれ?なんで?となりました。

列数が0という指摘を受けて、一応0列があることを理解していたつもりだったのですけど、 その仕様を勘違いしていたことに気づきました。

テーブルを省略したSELECTの扱い

全部のSQL実装では無いですが、SQLのSELECTはFROM句がなくても動作する実装が多いです。 psqlで実行すると以下のようになります。

SELECT '1';
 ?column? 
----------
 1
(1 row)

のようにすれば文字列1が返ってきます。列名は無いので?column?で表されていますが、1行1列のテーブルと同じ扱いになります。

SELECT ;はこの流れで行数が1で、列数が0のテーブルということになります。

psqlでは実行すると以下のように行が(改行も含めて)表示されないまま 1 row と表示されるのでちょっと変な感じがしたのですが、これはpsql側でどう表示するかの問題であって、1行0列のテーブルと同じ扱いになっています。

SELECT ;
--
(1 row)

なにも指定していないので、0行0列になるかと勘違いしてましたが、1行0列の方が正しいとわかります。

PostgreSQLは0列のテーブルが作れる

前述の石井さんから指摘にあるように最近のPostgreSQLでは0列のテーブルが作成できるようになっています。

CREATE TABLE empty ();

元からSQLでは行をINSERTしなければ0行のテーブルになるので、0列のテーブルを作っただけだと0列0行のテーブルになります。

SELECT * FROM empty;
--
(0 rows)

psqlの表示では0列の場合(1 row)と(0 rows)の表示でしか区別出来ないですが、0行と1行でちゃんと違いがあって整合が取れている動作になっています。

0列のテーブル操作

前は0列のSQLが許可されないところが多かったのですが、現在進行形で0列を許可するように修正されているので、前はエラーになったものが通るようになっていたり、これから通るようになったりする可能性があります。

INSERT

0列のテーブルにINSERTしようとすると素直にできませんでした。

INSERT INTO empty () VALUES ();
ERROR:  42601: syntax error at or near ")"

SELECT

列数が0のテーブルだけでなく、列数が1つ以上のテーブルであってもSELECT FROM oneで列数が0で返すことができます。 これにより列数が0で、複数行のテーブルを表現できます。

\d one
                 Table "public.one"
 Column |  Type   | Collation | Nullable | Default 
--------+---------+-----------+----------+---------
 i      | integer |           |          | 
SELECT FROM one;
--
(2 rows)

INSERT INTO SELECT

ということはINSERT INTO table SELECTならINSERTが可能になります。

INSERT INTO empty SELECT FROM one;
INSERT 0 2

INSERT出来てます。

SELECT * FROM empty;
--
(2 rows)

行数が0のときとは明確に区別されます。

INSERT INTO empty SELECT FROM one WHERE false;
INSERT 0 0

行数0でもSELECT i FROMにしてしまうとちゃんとエラーになります。

INSERT INTO empty SELECT i FROM one WHERE false;
ERROR:  42601: INSERT has more expressions than target columns
LINE 1: INSERT INTO empty SELECT i FROM one WHERE false;
                                 ^
LOCATION:  transformInsertRow, analyze.c:936