Chapter 2. 問い合わせ

Table of Contents
2.1. テーブル式
2.2. 選択リスト
2.3. 問い合わせの結合
2.4. 行の並び換え
2.5. LIMIT と OFFSET

問い合わせ とは抽出の過程のこともしくは、データベースからデータを抽出する コマンドのことです。 SQLでは、SELECT コマンドを、問い合わせを特定するためにつかいます。 SELECT コマンドの一般的な構文は次の通りです。

SELECT select_list FROM table_expression [sort_specification]
次の節では、選択リスト、テーブル式およびソートの仕様ついて詳細に説明します。 最も単純な問い合わせの形式は、次の通りです。
SELECT * FROM table1;
table1というテーブルがあるとして、このコマンドはtable1からす べての行およびすべての列を取り出します。 (抽出する手段はクライアントアプリケーションに依存します。 例えば、psqlプログラムがアスキー文字による図(テキスト)で画面上に テーブルを表示したり、クライアントライブラリが個々の行と列を取り出す する機能を提供します。) 選択リストの指定で *は、テーブルが持っているすべての列を 意味します。 選択リストは、列の一部分を選択することや列を抽 出する前に列に対して計算を施すこともできます。 Section 2.2参照。 例えば、table1にa、bおよびc(他のものもある場合がありますが)という名前の列がある場合、下 記のような問い合わせができます。
SELECT a, b + c FROM table1;
(ここで、bとcが数値データ型であるとみなします)

FROM table1は特に単純なテーブル式です。 一般に基本テーブルや結合そして副問い合わせなどで、テーブル式は複 雑になることがあります。 しかし、テーブル式を省略し、SELECTコマンドを電卓として使用すること もできます。

SELECT 3 * 4;
選択リストの式が異なる結果を返す場合、これはさらに有用です。 例えば、関数を次のように呼び出すことができます。
SELECT random();

2.1. テーブル式

テーブル式 でテーブルを指定します。 テーブル式には、FROM句があって、オプションとして、WHERE 句、GROUP BY句およびHAVING句を つけることができます。 単純なテーブル式は、単にディスク上のいわゆる基本テーブルと呼ばれる テーブルを参照するだけです。しかし、様々な方法で基本テーブル を修正したり、組合せたりするためにより複雑な式を使用することができます。

テーブル式のオプションで指定するWHERE句、GROUP BY句およびHAVING句は、 FROM句で引き出されたテーブル上で、次々に変換されて実行されます。 これら全ての変換によって生成される派生テーブルは、列評価式の選択リストで 指定されたように出力行を算出するための入力行を提供します。

2.1.1. FROM句

FROM句は、で与えられ、カンマで分けられたテーブル参照リストで与えられる1つ以上の テーブルから1つのテーブルを構築します。

FROM table_reference [, table_reference [, ...]]
テーブル参照(table_reference)はテーブル名であったり、副問合せまたはテーブル結合、さらには それらが複雑に組合わさって作られるテーブルです。 FROM句に1つ以上のテーブル参照がある場合、 それらは、あとで WHERE句、GROUP BY句およびHAVING句によって 変換され得る派生テーブルを形成す るためのクロス結合(下記を参照)で、最終的には全ての テーブル式の結果となります。

テーブル参照があるテーブルをテーブルの継承階層のスーパーテーブルと名前をつけると、 テーブル名の前にONLYキーワードが無い場合は、テーブル参照はそのテーブルだけでなくそ のサブテーブルに継承されたすべての列を生成します。 しかしながら、参照は、名前付きのテーブルに現れた列のみを生成し、サブテーブルの中に追加された列は無視されます。

2.1.1.1. 結合テーブル

結合テーブルは、2つの実テーブルか派生テーブルから、 結合の規則に従って派生されます。 INNER 結合、OUTER 結合、および、CROSS 結合がサ ポートされています。

結合のタイプ

クロス結合(CROSS JOIN)
T1 CROSS JOIN T2

どのT1T2の 行の組合せについても、T1のす べての列に続き、T2のすべての列 を含む行が派生テーブルに含まれます。 2つのテーブルがN行とM行で構成されている場合、結合されたテーブルは N×M行となります。 クロス結合はINNER JOIN ON TRUEと同じです。

Tip: FROM T1 CROSS JOIN T2FROM T1, T2 と等価です。

修飾付き結合(Qualified JOINs)
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expression
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

INNEROUTER は、省略可能です。 INNER がデフォルトとなります。 LEFTRIGHT, FULL をOUTER JOINに指定できます。

結合条件 は、ON句かUSING句で指定するか、または 暗黙的にNATURAL記述で指定されます。 結合条件は、下で詳しく説明するように、2つの元となるテーブルのどの行が "一致するか"を決めます。

ON句は最も一般的な結合条件であり、WHERE句で使われるのと同じブール値 評価式となります。 ON で表現された部分が真となる場合、T1とT2の行が対象となります。

USINGは略記法です。それは、結合テーブルが共通で持つカンマで区切られた列名 のリストから、各々の列の組合せをイコールとした結合条件を生成します。 更に、JOIN USINGの出力は、入力列でイコールとなった列の組合せにたいして、 ひとつの列があり、 各テーブルの他のすべての列がそのあと続きます。 ONを使った場合は、結果において a,b,cは2つの列になりますが、USINGを使うと それぞれ1つの列になるという例外はありますが、 USING (a, b, c)ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c)と 等価です。

最後にNATURALはUSINGの略記形式です。 2つの入力テーブルに必ずある列名の持ったをUSING句リストを形成します。 USINGのように、これらの列は出力テーブルに一度だけ現われます。

JOINには次のものがあります

内部結合(INNER JOIN)

T1の各行R1に対して、T2においてR1との結合条件を満たしている行が、 結合されたテーブルに含まれます。

左外部結合(LEFT OUTER JOIN)

まず、内部結合が行われます。 その後、T2の任意の行で結合条件を満たさないT1の各行につい ては、T2の列はNULL値として、結合されたテーブルを作ります。 したがって、連結されたテーブルは、無条件に少なくとも1行は T1の行があります。

右外部結合(RIGHT OUTER JOIN)

まず、内部結合が行われます。 その後、T1の任意の行で結合条件を満たさないT2の各行につい ては、T1の列はNULL値として、結合されたテーブルを作ります。 これは左結合の反対です。結果のテーブルは、T2の行が無条件に入ります。

完全外部結合(FULL OUTER JOIN)

まず、内部結合が行われます。 その後、T1の各行で結合条件を満たさないT2の任意の行については、 T2の列はNULL値として結合します。 さらに、T2の各行で結合条件を満たさないT1の任意の行については、 T1の列はNULL値として結合します。

すべての結合は、互いに結び付けたり、あるいは、入れ子にしたりすることが できます。T1T2の どちらか、あるいは両方が、結合テーブルになることがあります。 小括弧は結合の順序を制御するためにJOIN句をくくることに使用うことが できます。小括弧がない場合、JOIN句は左から右に入れ子にします。

2.1.1.2. 副問い合わせ(Subqueries)

派生テーブルを特殊化する副問い合わせは、必ず小括弧でくくられなけらばならず、 さらに、名前にはAS句を 使わなければなりません。 (Section 2.1.1.3参照)

FROM (SELECT * FROM table1) AS alias_name

この例は、FROM table1 AS alias_nameと同じです。 さらに、興味深いケースとして、副問い合わせがグループ化や集約を呼んでいる場合、 単純結合にまとめることはできないことです。

2.1.1.3. テーブルと列の別名

テーブルや複雑なテーブル参照は、その先の処理のため、 派生テーブルを参照するために一時的な名前を与えることができます。 これをテーブルの別名(table alias)と呼びます。

FROM table_reference AS alias
ここで、aliasはどんな通常識別子でもかまいません。 別名は問い合わせでテーブル参照をするときの新しい名前になります。 その場合は、オリジナルの名前でテーブルを参照することはできなくなります。 そこで、
SELECT * FROM my_table AS m WHERE my_table.a > 5;
は、正しいSQLの構文ではありません。 つまり、(これはSQL標準に対するPostgres拡張ですが) FROM句に暗黙のテーブル参照が追加されます。 そこで、次のような問い合わせを書いたものとして処理されます。
SELECT * FROM my_table AS m, my_table AS my_table WHERE my_table.a > 5;
テーブルの別名は主に表記を簡単にするためにあります。 しかし次のように、一つのテーブルが自分自身と結合する場合は、必須となります。
SELECT * FROM my_table AS a CROSS JOIN my_table AS b ...
さらに、テーブル参照がサブクエリの場合に別名が必要になります。

小括弧は、あいまいさを無くすために使われます。 次のSQL文は、前の例題と異なり、結合の結果をb として別名を与えられます。

SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...

FROM table_reference alias
この形式は前に書いたものと等価です。ASキーワードは省略可能です。

FROM table_reference [AS] alias ( column1 [, column2 [, ...]] )
この形式では、前に説明したようなテーブルの改名に加えてさらに、 テーブルの列は、囲んでいる問い合わせの中で一時的な名前として与えられます。 もし、実際のテーブルが持つ列よりも少ない数の列の別名が与えれられる場合、 残りのコラムは改名されません。 この構文は、自己結合あるいは副問い合わせで特に役立ちます。

別名が、JOIN句の結果として適用される場合、これらの形式のいずれかを使うと、 別名はJOINの内のオリジナル名を隠します。 例えば、

SELECT a.* FROM my_table AS a JOIN your_table AS b ON ...
は正しいSQL文です。しかし
SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c
は正しくありません。テールブルの別名 A は、外側の別名Cでは参照することができません。

2.1.1.4. 例

FROM T1 INNER JOIN T2 USING (C)
FROM T1 LEFT OUTER JOIN T2 USING (C)
FROM (T1 RIGHT OUTER JOIN T2 ON (T1C1=T2C1)) AS DT1
FROM (T1 FULL OUTER JOIN T2 USING (C)) AS DT1 (DT1C1, DT1C2)

FROM T1 NATURAL INNER JOIN T2
FROM T1 NATURAL LEFT OUTER JOIN T2
FROM T1 NATURAL RIGHT OUTER JOIN T2
FROM T1 NATURAL FULL OUTER JOIN T2

FROM (SELECT * FROM T1) DT1 CROSS JOIN T2, T3
FROM (SELECT * FROM T1) DT1, T2, T3
上記は結合テーブルと複雑な派生テーブルの例です。 AS句がどのように改名、あるいは、派生テーブルに名前を付けるか、 さらに、省略可能なカンマで区切られた列名をどのように改名するかに注目してください。 最後の2つのFROM句はT1、T2およびT3から同じ派生テーブルを作ります。 副問い合わせにDT1と名前付けする際のASキーワードは省略されています。 さらに、キーワードOUTERおよびINNERも省略することができます。

2.1.2. WHERE句

WHERE search_condition
です。 search_condition は、 Section 1.3で定義した、 boolean型を返すどのような評価式も指定できます。

FROM句の処理が終わった後に、派生テーブルの各行は検索条件と照合されます。 条件の結果が真の場合、その行は出力されます。 そうでない、すなわち結果が偽またはNULLの場合は、その行は捨てられます。 一般的に検索条件は、FORM句で生成されたテーブルのどれかの列をすくなくともいくつか指定します。 これは、必須ではありませんが、そうしないとFORM句はまったく意味がなくなります。

Note: 結合(JOIN)構文が実装される以前では、WHERE句の中に 内部結合の結合条件を挿入する必要がありました。 例えば、次のテーブル式は等価です。

FROM a, b WHERE a.id = b.id AND b.val > 5
FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5
そして、さらに、
FROM a NATURAL JOIN b WHERE b.val > 5
どれを使うかは、主にスタイルの問題です。 FROM句のJOIN構文は、おそらく、他の製品への移植性がありません。 外部結合については、FROM句でなければならなく、選択の余地はありません。 外部結合のON句やUSING句は、WHERE条件とは等しくありません。 なぜなら、最終結果から行を除去すると同様に、マッチしない入力行に対する 行の追加も行うからです。

FROM FDT WHERE
    C1 > 5

FROM FDT WHERE
    C1 IN (1, 2, 3)
FROM FDT WHERE
    C1 IN (SELECT C1 FROM T2)
FROM FDT WHERE
    C1 IN (SELECT C3 FROM T2 WHERE C2 = FDT.C1 + 10)

FROM FDT WHERE
    C1 BETWEEN (SELECT C3 FROM T2 WHERE C2 = FDT.C1 + 10) AND 100

FROM FDT WHERE
    EXISTS (SELECT C1 FROM T2 WHERE C2 > FDT.C1)

上記の例題で、FDTはFROM句の中から派生されたテーブルです。 WHARE句の検索条件を満たさなかった行は、FDTから削除されます。 評価式としてのスカラ副問い合わせの使い方に注目してください。 他の問い合わせのように、副問い合わせは、複雑なテーブル式を使うことができます。 副問い合わせの中でどのようにFDTは参照されるかに注意してください。 C1をFDT.C1のように修飾することは、 C1が副問い合わせの入力テーブルから派生した列名でもあるときにだけ必要です。 列名の修飾は、必須の場合では無くても、明確にするために役立ちます。 これは、外側の問い合わせの列名に名前をつける範囲を、どのようにして 内側の問い合わせまで拡張するかを示します。

2.1.3. GROUP BY と HAVING 句

WHEREフィルターを通した後、派生された入力テーブルは、GROUP BY 句でグループ化され、 HAVING 句を使うことにより不要なグループは取り除かれます。

SELECT select_list FROM ... [WHERE ...] GROUP BY grouping_column_reference [, grouping_column_reference]...

GROUP BY 句は、指定された全列で同じ値となる行を、互いにグループ化 するために使われます。 (ORDER BY句 とは違い)指定された列の順番は関係ありません。 この目的は、共通する値を持つ行を、代表となる1つのグループ行へまとめ ることにより、各グループの行を減らすことにあります。 これは出力の冗長度を排除し、さらにまた、これらのグループに適用される 集約が得られます。

一旦テーブルがグループ化されると、グループの中のどの行を使えば 良いのかが曖昧となるので、集約式を除いてグループ化に 使われない列は参照することができません。 グループ毎に既知の一定の値を持つので、選択リストの列評価式で グループ化された列は参照することができます。 グループ化で使われない列の集約関数は、テーブル全体ではなく 一つのグループでの値を計算します。 例えば、 sum(sales) は、 製品コードごとにグループ化された各製品の売り上げ合計であり、全ての 製品の売り上げ合計ではありません。 グループ化されていない列を計算した集約はグループを代表しますが、 グループ化されていない列の個別の値はそうではありません。

例:

SELECT pid, p.name, (sum(s.units) * p.price) AS sales
  FROM products p LEFT JOIN sales s USING ( pid )
  GROUP BY pid, p.name, p.price;
この例では、pid列とp.name列、p.price列は必ず GROUP BY句で指定しなければなりません。なぜなら、これらは、問い合わ せリストの中で使われているためです。 s.units列はGROUP BYで指定する必要はありません。これは、 製品毎の売上げ計算の集約関数(sum())の中だけで 使われるためです。 各製品については、製品のすべての販売に関しての合計行が返されます。

厳密なSQLでは、GROUP BYは、ソーステーブルの列によってグループ 化できますが、Postgresはこれを、問い合わせ選択リストの列による グループ化も許すように拡張しています。 単純な列名の代りに、評価式でグループ化することもできます。

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_expression
GROUP BY句を使ってグループ化されたテーブルで 決まったグループのみ必要な場合、 グループ化されたテーブルから不要なグループを除くのに、 WHERE句のようにHAVING句を使うことができます。 PostgresはHAVING句をGROUP BY句無しに使うことができますが、その場合、 もう一つのWHERE句のように振る舞います。このようにHAVINGを使うポイントは明確 ではありませんが、目安としては、HAVING条件が集約関数の結果を参照する ということです。集約を含んでいない制約は、WHERE句の中でより効果的に 表記されます。

例:

SELECT pid    AS "Products",
       p.name AS "Over 5000",
       (sum(s.units) * (p.price - p.cost)) AS "Past Month Profit"
  FROM products p LEFT JOIN sales s USING ( pid )
  WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks'
  GROUP BY pid, p.name, p.price, p.cost
    HAVING sum(p.price * s.units) > 5000;
前の例は、WHERE 句はグループ化する前の列の行を選択し、その後、 HAVING句が、総売上高の合計が5000より大きいグループを抽出します。