psql

psql: はじめてのガイド — PostgreSQLコマンドラインクライアントでローカル開発をマスターする

本記事は、Apple M4 Max搭載のMacBook Proで、PostgreSQL公式の対話型ターミナルpsqlを使いこなすためのステップバイステップガイドです。この記事を最後まで進めると、PostgreSQLのインストール確認、psqlの基本メタコマンドの習得、データベースとテーブルの作成、psqlの生産性向上設定、ローカル開発ワークフローの構築が完了します。


1. 前提条件

ハードウェアとOS

本ガイドでは、Apple Silicon(特にM4 Maxチップ)上のmacOSを使用していることを前提としています。PostgreSQLはHomebrew経由でネイティブARM64ビルドを提供しており、Rosetta 2変換なしで動作します。

Homebrew

Homebrewがインストールされている必要があります。確認:

brew --version

期待される出力:

Homebrew 4.x.x

PostgreSQL

PostgreSQLがすでにインストールされている場合、インストールを確認します:

psql --version

期待される出力:

psql (PostgreSQL) 17.x

サーバーが起動しているか確認します:

brew services list | grep postgresql

期待される出力:

postgresql@17 started shogo ~/Library/LaunchAgents/homebrew.mxcl.postgresql@17.plist

起動していない場合:

brew services start postgresql@17

アーキテクチャの理解

PostgreSQLはクライアント・サーバーモデルを使用しています:

  • サーバーpostgres):データの管理、クエリの処理、接続の処理を行うデータベースエンジン。Macのバックグラウンドサービスとして動作します。
  • クライアントpsql):サーバーに接続し、SQLコマンドを送信し、結果を表示する対話型ターミナル。

psqlを実行すると、ローカル接続の場合はUnixドメインソケット(/tmp/.s.PGSQL.5432)、リモート接続の場合はTCP/IPでPostgreSQLサーバーに接続します。


2. 最初の接続

デフォルトデータベースへの接続

HomebrewでPostgreSQLをインストールすると、macOSのユーザー名と同じ名前のデフォルトデータベースとスーパーユーザーロールが作成されます。以下で接続します:

psql

これは以下と同等です:

psql -h localhost -p 5432 -U $(whoami) -d $(whoami)

期待される出力:

psql (17.x)
Type "help" for help.

shogo=#

=# プロンプトはスーパーユーザーとして接続していることを示します。=> プロンプトは一般ユーザーを示します。

psqlとは?

psqlはPostgreSQLの公式対話型ターミナルです。以下の機能を提供します:

  • SQL実行:接続先データベースに対して任意のSQLステートメントを実行。
  • メタコマンド:バックスラッシュコマンド(例:\dt\l)でSQLを書かずにデータベースオブジェクトを調査。
  • タブ補完:テーブル名、カラム名、SQLキーワード、メタコマンドを自動補完。
  • 履歴:コマンド履歴が~/.psql_historyにセッション間で保持。
  • スクリプティング:SQLファイルの実行とフォーマット済み出力の自動化。

接続パラメータ

psqlは複数の方法で接続先を指定できます:

フラグ環境変数説明デフォルト
-hPGHOSTサーバーのホスト名ローカルソケット
-pPGPORTポート番号5432
-UPGUSERユーザー名OSユーザー名
-dPGDATABASEデータベース名ユーザー名
-WPGPASSWORDパスワード入力を強制(パスワードなし)

接続URIも使用できます:

psql "postgresql://user:password@localhost:5432/dbname"

3. 基本メタコマンド

メタコマンドはバックスラッシュ(\)で始まり、サーバーに送信されずにpsql自体で処理されます。

ナビゲーションと情報

\l              -- すべてのデータベースを一覧表示
\c dbname       -- 別のデータベースに接続
\conninfo       -- 現在の接続情報を表示

\conninfo を実行:

You are connected to database "shogo" as user "shogo" via socket in "/tmp" at port "5432".

データベースオブジェクトの調査

\dt             -- 現在のスキーマのテーブル一覧
\dt+            -- テーブル一覧(サイズと説明付き)
\d tablename    -- テーブルの詳細(カラム、型、インデックス)
\d+ tablename   -- ストレージと統計情報付き詳細
\di             -- インデックス一覧
\dv             -- ビュー一覧
\df             -- 関数一覧
\ds             -- シーケンス一覧
\dn             -- スキーマ一覧
\du             -- ロール/ユーザー一覧

クエリ出力の制御

\x              -- 拡張表示の切り替え(縦方向出力、幅広テーブルに最適)
\x auto         -- 幅に応じて通常表示と拡張表示を自動切り替え
\timing         -- クエリ実行時間の表示切り替え
\pset format    -- 出力形式の設定(aligned, unaligned, csv, html, latex)
\pset border 2  -- 罫線スタイルの設定(0, 1, 2)

ヘルプと履歴

\?              -- すべてのメタコマンド一覧
\h              -- SQLコマンド一覧
\h CREATE TABLE -- 特定のSQLコマンドのヘルプ
\s              -- コマンド履歴の表示
\g              -- 最後のクエリを再実行
\e              -- 最後のクエリを$EDITORで開く

入出力

\i filename.sql      -- ファイルからSQLを実行
\o output.txt        -- クエリ出力をファイルに送信
\o                   -- ファイルへの出力を停止
\copy                -- クライアント側COPY(CSV のインポート/エクスポート)

4. 開発用データベースの作成

ロールとデータベースの作成

ローカル開発では、デフォルトのスーパーユーザーを使用するのではなく、専用のロールとデータベースを作成します:

CREATE ROLE devuser WITH LOGIN PASSWORD 'devpass';
CREATE DATABASE myapp OWNER devuser;

確認:

\l myapp

期待される出力:

                              List of databases
 Name  | Owner   | Encoding | Locale Provider |  Collate   |   Ctype    | ...
-------+---------+----------+-----------------+------------+------------+----
 myapp | devuser | UTF8     | libc            | en_US.UTF-8| en_US.UTF-8| ...
(1 row)

新しいロールとして接続

psql -U devuser -d myapp

またはpsql内から:

\c myapp devuser

スキーマの作成

スキーマはデータベース内の論理的な名前空間を提供します:

CREATE SCHEMA app;
SET search_path TO app, public;

5. テーブルの作成とデータの操作

テーブルの作成

CREATE TABLE app.users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE app.orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL REFERENCES app.users(id) ON DELETE CASCADE,
    product VARCHAR(255) NOT NULL,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    price NUMERIC(10, 2) NOT NULL,
    status VARCHAR(20) DEFAULT 'pending',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_orders_user_id ON app.orders(user_id);
CREATE INDEX idx_orders_status ON app.orders(status);

テーブルの確認

\d app.users

期待される出力:

                                       Table "app.users"
   Column   |           Type           | Collation | Nullable |              Default
------------+--------------------------+-----------+----------+-----------------------------------
 id         | integer                  |           | not null | nextval('app.users_id_seq'::regclass)
 email      | character varying(255)   |           | not null |
 name       | character varying(100)   |           | not null |
 created_at | timestamp with time zone |           |          | now()
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)
    "users_email_key" UNIQUE CONSTRAINT, btree (email)
Referenced by:
    TABLE "app.orders" CONSTRAINT "orders_user_id_fkey" FOREIGN KEY (user_id) REFERENCES app.users(id) ON DELETE CASCADE

データの挿入

INSERT INTO app.users (email, name) VALUES
    ('alice@example.com', 'Alice Johnson'),
    ('bob@example.com', 'Bob Smith'),
    ('charlie@example.com', 'Charlie Brown');

INSERT INTO app.orders (user_id, product, quantity, price, status) VALUES
    (1, 'Widget', 5, 19.99, 'completed'),
    (1, 'Gadget', 2, 49.99, 'pending'),
    (2, 'Widget', 10, 19.99, 'completed'),
    (3, 'Doohickey', 1, 99.99, 'shipped');

データのクエリ

SELECT u.name, o.product, o.quantity, o.price, o.status
FROM app.users u
JOIN app.orders o ON u.id = o.user_id
ORDER BY u.name, o.created_at;

期待される出力:

     name      |  product  | quantity | price  |  status
---------------+-----------+----------+--------+-----------
 Alice Johnson | Widget    |        5 |  19.99 | completed
 Alice Johnson | Gadget    |        2 |  49.99 | pending
 Bob Smith     | Widget    |       10 |  19.99 | completed
 Charlie Brown | Doohickey |        1 |  99.99 | shipped
(4 rows)

6. 生産性向上機能

拡張表示の切り替え

カラム数が多いテーブルには拡張表示を使用します:

\x auto
SELECT * FROM app.users WHERE id = 1;

期待される出力:

-[ RECORD 1 ]-------------------------
id         | 1
email      | alice@example.com
name       | Alice Johnson
created_at | 2026-04-13 10:30:00+09

タイミング

パフォーマンスをプロファイルするためにクエリタイミングを有効化:

\timing
SELECT count(*) FROM app.orders;

期待される出力:

 count
-------
     4
(1 row)

Time: 0.892 ms

エディタでの編集

最後のクエリをデフォルトエディタで開きます:

\e

$EDITOR(デフォルトはvi)で最後のクエリが開きます。保存して閉じると実行されます。

SQLファイルの実行

psql -U devuser -d myapp -f schema.sql

またはpsql内から:

\i schema.sql

CSVのエクスポートとインポート

クエリ結果をCSVにエクスポート:

\copy (SELECT * FROM app.users) TO 'users.csv' WITH CSV HEADER

CSVデータのインポート:

\copy app.users (email, name) FROM 'new_users.csv' WITH CSV HEADER

7. .psqlrcの設定

~/.psqlrc ファイルを作成して、毎回のセッションでpsqlの動作をカスタマイズします:

cat > ~/.psqlrc << 'EOF'
-- 起動時のクワイエットモード
\set QUIET 1

-- クエリタイミングを表示
\timing

-- 拡張表示の自動切り替え
\x auto

-- NULLの表示を改善
\pset null '(null)'

-- 詳細なエラーレポート
\set VERBOSITY verbose

-- 履歴を長く保持
\set HISTSIZE 5000

-- 重複コマンドを記録しない
\set HISTCONTROL ignoredups

-- カスタムプロンプト: database[user]=#
\set PROMPT1 '%[%033[1;32m%]%/%[%033[0m%][%n]%R%# '
\set PROMPT2 '%R%# '

-- SQLキーワードを大文字で補完
\set COMP_KEYWORD_CASE upper

-- クワイエットモードをオフ
\set QUIET 0

\echo 'psql config loaded.'
EOF

psqlを再起動して変更を確認:

psql -U devuser -d myapp

期待されるプロンプト:

psql config loaded.
myapp[devuser]=>

8. ローカル開発で役立つSQLパターン

アクティブな接続の確認

SELECT pid, usename, datname, state, query_start, query
FROM pg_stat_activity
WHERE datname = 'myapp';

テーブルサイズの確認

SELECT
    schemaname || '.' || relname AS table,
    pg_size_pretty(pg_total_relation_size(relid)) AS total_size,
    pg_size_pretty(pg_relation_size(relid)) AS data_size,
    pg_size_pretty(pg_total_relation_size(relid) - pg_relation_size(relid)) AS index_size
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;

クエリパフォーマンスの分析

EXPLAIN ANALYZE
SELECT u.name, count(o.id) AS order_count
FROM app.users u
LEFT JOIN app.orders o ON u.id = o.user_id
GROUP BY u.name
ORDER BY order_count DESC;

シーケンスのリセット

ALTER SEQUENCE app.users_id_seq RESTART WITH 1;

全テーブルの削除と再作成(開発環境リセット)

DROP SCHEMA app CASCADE;
CREATE SCHEMA app;
SET search_path TO app, public;
-- マイグレーションファイルを再実行
\i schema.sql

トランザクションのワークフロー

BEGIN;
INSERT INTO app.users (email, name) VALUES ('dave@example.com', 'Dave Wilson');
-- 結果を確認
SELECT * FROM app.users WHERE email = 'dave@example.com';
-- 問題なければコミット
COMMIT;
-- 取り消す場合
-- ROLLBACK;

9. アプリケーションコードからの接続

接続文字列の形式

アプリケーションは接続文字列を使用してPostgreSQLに接続します:

postgresql://devuser:devpass@localhost:5432/myapp

Python (psycopg2)

pip3 install psycopg2-binary
import psycopg2

conn = psycopg2.connect(
    host="localhost",
    port=5432,
    user="devuser",
    password="devpass",
    dbname="myapp"
)

cur = conn.cursor()
cur.execute("SELECT name, email FROM app.users ORDER BY name")
for row in cur.fetchall():
    print(f"{row[0]} <{row[1]}>")

cur.close()
conn.close()

Node.js (pg)

npm install pg
const { Client } = require('pg');

const client = new Client({
  connectionString: 'postgresql://devuser:devpass@localhost:5432/myapp'
});

async function main() {
  await client.connect();
  const res = await client.query('SELECT name, email FROM app.users ORDER BY name');
  res.rows.forEach(row => console.log(`${row.name} <${row.email}>`));
  await client.end();
}
main();

10. トラブルシューティング

"connection refused"

PostgreSQLサーバーが起動していません:

brew services start postgresql@17

"role does not exist"

指定したロールが存在しません。利用可能なロールを確認:

psql -c "\du"

"database does not exist"

利用可能なデータベースを一覧表示:

psql -c "\l"

"permission denied for schema"

ロールに権限を付与:

GRANT ALL ON SCHEMA app TO devuser;
GRANT ALL ON ALL TABLES IN SCHEMA app TO devuser;
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT ALL ON TABLES TO devuser;

サーバーログの確認

PostgreSQLのログは以下に格納されています:

tail -f /opt/homebrew/var/log/postgresql@17.log

11. クリーンアップ

開発用データベースとロールの削除

-- まずスーパーユーザーとして接続
DROP DATABASE myapp;
DROP ROLE devuser;

.psqlrcの削除(任意)

rm ~/.psqlrc

PostgreSQLの停止

brew services stop postgresql@17

PostgreSQLのアンインストール(任意)

brew uninstall postgresql@17
rm -rf /opt/homebrew/var/postgresql@17

主要概念のまとめ

概念説明
psqlSQL実行とデータベースオブジェクト調査のためのPostgreSQL公式対話型ターミナル
メタコマンドpsql自体が処理するバックスラッシュコマンド(\dt\l\d
ロールログインしてオブジェクトを所有できるデータベースID(「ユーザー」と「グループ」の概念を統合)
スキーマテーブルなどのオブジェクトを整理するためのデータベース内の論理的な名前空間
SERIAL主キー用の自動増分整数型(PostgreSQL固有の省略記法)
TIMESTAMPTZタイムゾーン付きタイムスタンプ。本番環境ではTIMESTAMPより推奨
search_pathテーブル名がスキーマ修飾されていない場合に検索するスキーマを決定
.psqlrcpsqlの起動設定ファイル。毎セッション実行される
\copyサーバーのファイルシステムへのアクセスなしでCSVをインポート/エクスポートするクライアント側COPYコマンド
EXPLAIN ANALYZE実行時統計情報付きの実際のクエリ実行計画を表示

次のステップ

  • pg_dump / pg_restore:開発スナップショットのためのデータベースのバックアップと復元。
  • 拡張機能pgcryptouuid-ossppg_trgmなどの拡張機能をインストールして高度な機能を利用。
  • pgAdminまたはDBeaver:ビジュアルなスキーマ管理のためにpsqlと並行してGUIツールを使用。
  • マイグレーション:FlywayまたはAlembicでバージョン管理されたスキーママイグレーションを設定。
  • コネクションプーリング:本番に近いローカル環境でPgBouncerを使用したコネクションプーリング。
  • レプリケーション:ローカルで本番のリードレプリカ構成をシミュレートするストリーミングレプリケーションの設定。