PostgreSQL: PostgreSQLの演算子と式
最終更新:2026-08-26
1. 学習内容
- 算術演算子
+ - * / %とそのオーバーフローおよび精度の動作 - 比較演算子と論理演算子
- 文字列連結
||とパターンマッチング演算子 - 型キャスト
::とCAST() - 行コンストラクタ
ROW()と範囲演算子@> <@ &< &> - - サブクエリ式
IN / EXISTS / ANY / ALL
2. ストーリー
CharlieはSaaSプラットフォームの財務アナリストで、以下の業務を担当しています:
- 各注文の税込金額の計算(
total * 1.08) - 注文が指定された日付範囲内にあるかの確認
- 文字列形式の日付をDATE型に変換して比較
- 価格範囲の重なりの確認
これらのタスクにはPostgreSQLのさまざまな演算子と式を自在に使いこなす必要があります。
3. 概念: 算術演算子
(1) 基本算術演算子
| 演算子 | 意味 | 例 | 結果 |
|---|---|---|---|
+ |
加算 | 100 + 8 |
108 |
- |
減算 | 500 - 50 |
450 |
* |
乗算 | 29.99 * 3 |
89.97 |
/ |
整数除算(切り捨て) | 7 / 2 |
3 |
/ |
数値除算(小数あり) | 7.0 / 2 |
3.5 |
% |
剰余 | 10 % 3 |
1 |
^ |
べき乗 | 2 ^ 10 |
1024 |
| ` | /` | 平方根 | ` |
| ` | /` | 立方根 | |
! |
階乗 | 5 ! |
120 |
@ |
絶対値 | @ -15 |
15 |
▶ サンプル: 税込金額の計算
SELECT
order_id,
total_amount,
total_amount * 0.08 AS tax,
total_amount * 1.08 AS total_with_tax
FROM orders
WHERE order_status = 'completed';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ サンプル: 整数除算と数値除算
SELECT
7 / 2 AS int_div,
7.0 / 2 AS numeric_div,
7 / 2.0 AS numeric_div2,
CAST(7 AS NUMERIC) / 2 AS cast_div;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) 演算子の優先順位
| 優先順位 | 演算子 | 説明 |
|---|---|---|
| 1(最高) | . |
メンバーアクセス |
| 2 | :: |
型キャスト |
| 3 | [ |
配列添字 |
| 4 | -(単項)、@、! |
単項演算 |
| 5 | ^ |
べき乗 |
| 6 | *、/、% |
乗算 / 除算 / 剰余 |
| 7 | +、- |
加算 / 減算 |
| 8 | ` | |
| 9 | 比較演算子 | = <> < > <= >= |
| 10 | IS、IN、BETWEEN |
述語 |
| 11 | NOT |
論理否定 |
| 12 | AND |
論理積 |
| 13(最低) | OR |
論理和 |
▶ サンプル: 優先順位と括弧
SELECT
2 + 3 * 4 AS no_parens,
(2 + 3) * 4 AS with_parens;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) オーバーフローと精度
PostgreSQLのINTEGERの範囲は約-21億から21億です。これを超えるとエラーが発生します。金額計算にはNUMERIC(precision, scale)またはDECIMALを使用してください。
| 型 | 範囲 | 精度 | 最適な用途 |
|---|---|---|---|
SMALLINT |
-32768 〜 32767 | 整数 | 小範囲のカウント |
INTEGER |
±21億 | 整数 | 一般的な整数 |
BIGINT |
±920京 | 整数 | 大範囲のID |
NUMERIC(p,s) |
無制限 | 任意精度 | 金額計算 |
▶ サンプル: 金額計算の安全な精度
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
4. 概念: 比較演算子
(1) 基本比較
| 演算子 | 意味 | 1 <> 2 |
NULL = NULL |
|---|---|---|---|
= |
等しい | FALSE | NULL |
<> / != |
等しくない | TRUE | NULL |
< |
より小さい | TRUE | NULL |
> |
より大きい | FALSE | NULL |
<= |
以下 | TRUE | NULL |
>= |
以上 | FALSE | NULL |
NULLとの比較は常にNULLを返し、TRUEでもFALSEでもありません。
▶ サンプル: 比較操作
SELECT product_name, unit_price
FROM products
WHERE unit_price >= 100
AND unit_price < 500
AND category = 'Electronics';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) BETWEENと複合比較
| 式 | 同等の式 |
|---|---|
x BETWEEN a AND b |
x >= a AND x <= b |
x NOT BETWEEN a AND b |
x < a OR x > b |
▶ サンプル: BETWEEN範囲比較
SELECT order_id, total_amount, order_date
FROM orders
WHERE total_amount BETWEEN 100 AND 1000
AND order_date BETWEEN '2025-01-01' AND '2025-12-31';
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) IS DISTINCT FROM
IS DISTINCT FROMはNULLを比較可能な値として扱い、標準比較のNULLの落とし穴を回避します。
| 式 | NULL = NULL |
NULL IS DISTINCT FROM NULL |
|---|---|---|
| 結果 | NULL | FALSE |
1 IS DISTINCT FROM NULL |
— | TRUE |
▶ サンプル: IS DISTINCT FROM
SELECT order_id, old_status, new_status
FROM order_status_log
WHERE old_status IS DISTINCT FROM new_status;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
5. 概念: 論理演算子
(1) 3値論理の真理値表
PostgreSQLは3値論理(TRUE / FALSE / NULL)を使用します。NULLが論理演算に参加すると、結果は直感に反することがあります。
| a | b | a AND b | a OR b |
|---|---|---|---|
| T | T | T | T |
| T | F | F | T |
| T | N | N | T |
| F | T | F | T |
| F | F | F | F |
| F | N | F | N |
| N | T | N | T |
| N | F | F | N |
| N | N | N | N |
▶ サンプル: 論理演算におけるNULL
SELECT
TRUE AND NULL AS and_result,
TRUE OR NULL AS or_result,
FALSE AND NULL AS and_false,
FALSE OR NULL AS or_null,
NOT NULL AS not_result;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ サンプル: WHERE句でのNULLの動作
-- これはdiscount_rateがNULLの行を除外する
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0;
-- NULL行は含まれない。NULL > 0はNULL、NULL = 0はNULLのため
-- 修正: NULLを明示的に処理
SELECT product_name, discount_rate
FROM products
WHERE discount_rate > 0 OR discount_rate = 0 OR discount_rate IS NULL;
Output:
count
-------
5
(1 row)
6. 概念: 文字列演算子
(1) 文字列連結 ||
||はPostgreSQLの文字列連結演算子で、SQL Serverの+とは異なります。
| 演算子 | 意味 | 例 |
|---|---|---|
| ` | ` |
▶ サンプル: 文字列連結
SELECT
customer_id,
first_name || ' ' || last_name AS full_name,
'Order #' || order_id || ': $' || total_amount::TEXT AS order_summary
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id;
Output:
result
----------
42.50
(1 row)
(2) NULLとの文字列連結
任意の文字列とNULLを連結すると結果はNULLになります。COALESCEを使用して処理します。
| 式 | 結果 |
|---|---|
| `'A' | |
| `'A' |
▶ サンプル: 安全な文字列連結
SELECT
product_name || COALESCE(' - ' || description, '') AS product_info
FROM products;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
7. 概念: 型キャスト
(1) :: と CAST()
| 形式 | 例 | 標準SQL | 説明 |
|---|---|---|---|
::type |
'2025-01-01'::DATE |
いいえ(PG固有) | 簡潔、PGで推奨 |
CAST(expr AS type) |
CAST('2025-01-01' AS DATE) |
はい | 移植可能、冗長 |
▶ サンプル: 文字列から日付へ
SELECT
order_id,
order_date::DATE AS date_only,
'2025-06-15'::DATE AS literal_date,
CAST('2025-06-15 14:30:00' AS TIMESTAMP) AS full_timestamp
FROM orders
WHERE order_date::DATE >= '2025-01-01'::DATE;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ サンプル: 数値型変換
SELECT
total_amount::NUMERIC(12,2) AS rounded,
total_amount::INTEGER AS truncated,
total_amount::TEXT AS as_text
FROM orders;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) よくあるキャストのケース
| 変換元 → 変換先 | 用途 |
|---|---|
| TEXT → DATE | '2025-01-01'::DATE |
| TEXT → TIMESTAMP | CAST(ts AS TIMESTAMP) |
| NUMERIC → TEXT | 123.45::TEXT |
| INTEGER → NUMERIC | id::NUMERIC |
| TEXT → INTEGER | '42'::INTEGER |
▶ サンプル: Charlieの日付範囲確認
SELECT order_id, total_amount, order_date
FROM orders
WHERE order_date >= '2025-01-01'::DATE
AND order_date < '2025-07-01'::DATE
AND total_amount::NUMERIC(12,2) > 500;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
8. 概念: 行コンストラクタとサブクエリ式
(1) ROWコンストラクタ
ROW(val1, val2, ...)は匿名の行値を構築し、行レベルの比較に有用です。
▶ サンプル: 行コンストラクタ比較
SELECT order_id, order_status, total_amount
FROM orders
WHERE ROW(order_status, total_amount) = ROW('completed', 500);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) 行比較とIN
行コンストラクタはINと組み合わせて複数カラムのマッチングに使用できます。
▶ サンプル: 複数カラムのIN一致
SELECT order_id, customer_id, order_status
FROM orders
WHERE (order_status, customer_id) IN (
('completed', 1001),
('completed', 1002),
('pending', 1003)
);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(3) サブクエリ式の比較
| 式 | 意味 | 戻り値の型 |
|---|---|---|
col IN (subquery) |
サブクエリ結果のいずれかの値と等しい | BOOLEAN |
col = ANY(subquery) |
INと同じ | BOOLEAN |
col > ALL(subquery) |
サブクエリのすべての値より大きい | BOOLEAN |
EXISTS (subquery) |
サブクエリが行を返したか | BOOLEAN |
▶ サンプル: ALL比較サブクエリ
SELECT product_name, unit_price, category
FROM products
WHERE unit_price > ALL (
SELECT AVG(unit_price) FROM products GROUP BY category
);
Output:
result
----------
42.50
(1 row)
9. 概念: 範囲演算子
(1) 範囲型
PostgreSQLにはint4range、int8range、numrange、daterange、tstzrangeなどの組み込み範囲型があります。
▶ サンプル: daterangeの作成
SELECT daterange('2025-01-01'::DATE, '2025-06-30'::DATE) AS h1_range;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
(2) 範囲演算子リファレンス
| 演算子 | 意味 | 例 |
|---|---|---|
@> |
要素を含む | '[1,5]'::int4range @> 3 → TRUE |
<@ |
含まれる | 3 <@ '[1,5]'::int4range → TRUE |
&& |
重なり | '[1,5]'::int4range && '[3,8]'::int4range → TRUE |
&< |
右に拡張しない | '[1,3]'::int4range &< '[2,5]'::int4range → TRUE |
&> |
左に拡張しない | '[3,5]'::int4range &> '[1,3]'::int4range → TRUE |
| `- | -` | 隣接 |
+ |
和集合 | '[1,3]'::int4range + '[3,6]'::int4range → [1,6) |
* |
積集合 | '[1,5]'::int4range * '[3,8]'::int4range → [3,5) |
- |
差集合 | '[1,5]'::int4range - '[3,8]'::int4range → [1,3) |
▶ サンプル: 日付範囲の重なりを確認
SELECT o1.order_id, o2.order_id
FROM orders o1, orders o2
WHERE o1.customer_id = o2.customer_id
AND o1.order_id < o2.order_id
AND daterange(o1.order_date, o1.delivery_date) &&
daterange(o2.order_date, o2.delivery_date);
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
▶ サンプル: 価格範囲の包含確認
SELECT product_name, unit_price
FROM products
WHERE numrange(100, 500) @> unit_price::NUMERIC;
Output:
id | name | value
----+----------+-------
1 | example | 42
(1 row)
10. フローチャート: 型キャストの判断
flowchart TD
A["型キャストが必要?"] --> B{"対象の型は?"}
B -->|"日付/時刻"| C["::DATE / ::TIMESTAMP"]
B -->|"数値"| D["::NUMERIC / ::INTEGER"]
B -->|"文字列"| E["::TEXT"]
B -->|"移植性が必要"| F["CAST(expr AS type)"]
C --> G{"元の形式は?"}
G -->|"標準形式"| H["直接 :: キャスト"]
G -->|"カスタム形式"| I["TO_DATE() / TO_TIMESTAMP()"]
D --> J["精度の損失に注意"]
E --> K["NULL連結に注意"]
H --> L["完了"]
I --> L
J --> L
K --> L
F --> L
style A fill:#e1f5fe
style L fill:#c8e6c9
11. 総合サンプル
Charlieは財務分析に必要なすべての計算を完了しました。
-- ステップ1: 精度制御付き税額計算
SELECT
order_id,
total_amount::NUMERIC(12,2) AS amount,
(total_amount::NUMERIC(12,2) * 0.08)::NUMERIC(12,2) AS tax,
(total_amount::NUMERIC(12,2) * 1.08)::NUMERIC(12,2) AS total_with_tax
FROM orders
WHERE order_status = 'completed'
AND order_date >= '2025-01-01'::DATE;
-- ステップ2: daterangeによる日付範囲確認
SELECT order_id, customer_id, order_date, total_amount
FROM orders
WHERE daterange('2025-01-01'::DATE, '2025-07-01'::DATE) @> order_date::DATE
AND order_status <> 'cancelled';
-- ステップ3: 文字列から日付への変換とフォーマット
SELECT
order_id,
'Order #' || order_id || ' - ' ||
COALESCE(customer_name, 'Unknown') ||
' ($' || total_amount::TEXT || ')' AS order_label
FROM orders o
LEFT JOIN customers c ON c.customer_id = o.customer_id
WHERE o.order_date::DATE >= '2025-01-01'::DATE
ORDER BY o.total_amount DESC;
-- ステップ4: 行コンストラクタと範囲の重なり
SELECT
p1.product_name AS product_a,
p2.product_name AS product_b,
numrange(p1.unit_price, p1.unit_price * 1.2) AS price_range_a,
numrange(p2.unit_price, p2.unit_price * 1.2) AS price_range_b
FROM products p1
JOIN products p2 ON p1.product_id < p2.product_id
WHERE p1.category = p2.category
AND numrange(p1.unit_price, p1.unit_price * 1.2) &&
numrange(p2.unit_price, p2.unit_price * 1.2)
AND p1.is_active = true AND p2.is_active = true;
❓ よくある質問
::とCAST()のどちらが良いで��か?::はより簡潔でPostgreSQL固有の構文です。CAST()はSQL標準形式です。純粋なPostgreSQLプロジェクトでは::を推奨します。クロスデータベース互換性が必要な場合はCAST()を使用してください。TRUE AND NULL = NULL(TRUEではない)、FALSE OR NULL = NULL(FALSEではない)、NOT NULL = NULLです。WHERE句ではNULLはTRUEでないものとして扱われるため、該当行が除外されます。COALESCE(col, '')でNULLを置き換えてください。WHERE (status, amount) = ROW('completed', 500)は2つのカラムを別々に比較するよりクリーンで、複数カラムのINにも対応します。@> <@ &&などの演算子はPostgreSQLの組み込みRange型(int4range、numrange、daterangeなど)に作用します。通常のカラムは比較前にRange値に変換する必要があります。^は乗算/除算より優先順位が高いですか?^は* / %より強く結合します。2^3^2 = 2^(3^2) = 512(右結合)であり、(2^3)^2 = 64ではありません。IS DISTINCT FROMと<>の違いは何ですか?<>ではNULL <> NULLはNULL(TRUEでもFALSEでもない)を返します。IS DISTINCT FROMはNULLを比較可能な値として扱います。NULL IS DISTINCT FROM NULLはFALSEを返し、1 IS DISTINCT FROM NULLはTRUEを返します。📖 まとめ
- 算術演算子: 整数除算の切り捨てと精度に注意。金額には
NUMERIC(p,s)を使用 - 比較演算子はNULLとの比較でNULLを返す。安全な比較には
IS DISTINCT FROMを使用 - 3値論理では
NULL AND TRUE = NULL、NULL OR FALSE = NULL - 文字列連結
||はNULLを返す。COALESCEで処理 ::はPG固有のキャスト構文。CAST()はSQL標準- 行コンストラクタ
ROW()は複数カラム比較と複数カラムINをサポート - 範囲演算子
@> <@ &&は効率的なRangeの包含と重なり確認を提供
📝 練習問題
-
⭐
order_itemsの各行についてunit_price * quantityを計算し、結果をNUMERIC(10,2)にキャストして、行金額の降順で並べるクエリを記述してください。 -
⭐⭐
daterangeと@>演算子を使用して、order_dateが2025年第1四半期(1月1日〜3月31日)に含まれる注文を検索し、||を使用して注文サマリー文字列(形式:"Order #id - customer_name - $amount")を作成し、NULLの顧客名を処理するクエリを記述してください。 -
⭐⭐⭐ 同じカテゴリ内で価格範囲が重なる商品のペアを検索し(
numrange(unit_price, unit_price * 1.2)で範囲を構築し、&&で重なりを確認)、各行コンストラクタ比較ROW(a.price, a.category) = ROW(b.price, b.category)が等しいかを各ペアについて表示するクエリを記述してください。