psqlでクラシック音楽データベースを作ってみる

こんにちは、さるまりんです。

これまでここでも、PostgreSQL を使うときはクライアントコマンドの psql をよく利用してきました。
ただ、psql 自体の使い方 については、あまり触れてこなかった気がします。

GUI のクライアントも便利なのですが、
サーバーに SSH で入った先でサッと確認したいとき や、
Docker で立てた検証環境を軽く触りたいとき には psql を扱えたら便利です。

そこで今回は、psql だけを使って
クラシック音楽のミニデータベース を作ってみます。
一連の流れで psql の使い方を復習できればと思います。

1. DockerでPostgreSQLを立ち上げる

まずは検証用に、Docker で PostgreSQL コンテナを立ち上げます。

DockerでPostgreSQLコンテナを起動する
docker run --name pg-psql-demo \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_DB=postgres \
  -p 5432:5432 \
  -d postgres:16

起動できたら、ローカルにインストールした psql から接続します。

ローカルのpsqlから接続する
psql -h localhost -U postgres -d postgres

実行すると、こんな感じで psql のプロンプトに入れます。

psql起動直後の画面
Password for user postgres: 
psql (18.1, server 16.9 (Debian 16.9-1.pgdg120+1))
Type "help" for help.

postgres=# 

2. psqlのメタコマンドで「今どこにいるか」を確認する

psql には、SQL とは別に メタコマンド が用意されています。
\ から始まるコマンドです。

まずは、データベース一覧と接続情報を見てみます。

\lでデータベース一覧を表示
postgres=# \l
                                                    List of databases
   Name    |  Owner   | Encoding | Locale Provider |  Collate   |   Ctype    | Locale | ICU Rules |   Access privileges   
-----------+----------+----------+-----------------+------------+------------+--------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |        |           | 
 template0 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |        |           | =c/postgres          +
           |          |          |                 |            |            |        |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |        |           | =c/postgres          +
           |          |          |                 |            |            |        |           | postgres=CTc/postgres
(3 rows)

\conninfo で、今どこに接続しているかを確認します。

\conninfoで接続情報を確認
postgres=# \conninfo
      Connection Information
      Parameter       |   Value   
----------------------+-----------
 Database             | postgres
 Client User          | postgres
 Host                 | localhost
 Host Address         | ::1
 Server Port          | 5432
 Options              | 
 Protocol Version     | 3.0
 Password Used        | true
 GSSAPI Authenticated | false
 Backend PID          | 77
 SSL Connection       | false
 Superuser            | on
 Hot Standby          | off
(13 rows)

テーブル一覧(まだ何もない状態)も見ておきます。

テーブルがまだない状態の\dt
postgres=# \dt
Did not find any tables.

3. クラシック音楽用のデータベースを作る

次に、クラシック音楽用のデータベースを 1 つ作ります。

クラシック音楽用データベースを作成して接続
postgres=# CREATE DATABASE classical_demo;
CREATE DATABASE

postgres=# \c classical_demo
psql (18.1, server 16.9 (Debian 16.9-1.pgdg120+1))
You are now connected to database "classical_demo" as user "postgres".
classical_demo=# 

psql では、\c でデータベースを切り替えられるのが便利ですね。

4. 作曲家テーブル & 作品テーブルを作る

クラシック音楽らしく、

  • 作曲家テーブル composers
  • 作品テーブル works

の 2 つを作ります。

作曲家テーブルと作品テーブルを作成
CREATE TABLE composers (
  id            SERIAL PRIMARY KEY,
  name          TEXT NOT NULL,
  born          INT,
  died          INT,
  nationality   TEXT
);

CREATE TABLE works (
  id            SERIAL PRIMARY KEY,
  composer_id   INT REFERENCES composers(id),
  title         TEXT NOT NULL,
  genre         TEXT,
  year          INT,
  duration_min  INT
);

psql 上ではこんなふうに見えます。

\dtでテーブル一覧を確認
classical_demo=# \dt
            List of tables
 Schema |   Name    | Type  |  Owner   
--------+-----------+-------+----------
 public | composers | table | postgres
 public | works     | table | postgres
(2 rows)

`\d` でテーブル定義の詳細も確認できます。

\d composersでテーブル定義を確認
classical_demo=# \d composers
                               Table "public.composers"
   Column    |  Type   | Collation | Nullable |                Default                
-------------+---------+-----------+----------+---------------------------------------
 id          | integer |           | not null | nextval('composers_id_seq'::regclass)
 name        | text    |           | not null | 
 born        | integer |           |          | 
 died        | integer |           |          | 
 nationality | text    |           |          | 
Indexes:
    "composers_pkey" PRIMARY KEY, btree (id)
Referenced by:
    TABLE "works" CONSTRAINT "works_composer_id_fkey" FOREIGN KEY (composer_id) REFERENCES composers(id)
\d worksでテーブル定義を確認
classical_demo=# \d works
                               Table "public.works"
    Column    |  Type   | Collation | Nullable |              Default              
--------------+---------+-----------+----------+-----------------------------------
 id           | integer |           | not null | nextval('works_id_seq'::regclass)
 composer_id  | integer |           |          | 
 title        | text    |           | not null | 
 genre        | text    |           |          | 
 year         | integer |           |          | 
 duration_min | integer |           |          | 
Indexes:
    "works_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
    "works_composer_id_fkey" FOREIGN KEY (composer_id) REFERENCES composers(id)

DDL を書いたあとにすぐ \d で確認できる psql は気持ちがいいですね〜。

5. データをINSERTしてクラシック音楽DBっぽくする

まずは作曲家を 3 人だけ入れてみます。

作曲家データをINSERTして確認
classical_demo=# INSERT INTO composers (name, born, died, nationality)
VALUES
  ('Ludwig van Beethoven', 1770, 1827, 'German'),
  ('Wolfgang Amadeus Mozart', 1756, 1791, 'Austrian'),
  ('Frédéric Chopin', 1810, 1849, 'Polish');
INSERT 0 3

classical_demo=# SELECT * FROM composers;
 id |          name           | born | died | nationality 
----+-------------------------+------+------+-------------
  1 | Ludwig van Beethoven    | 1770 | 1827 | German
  2 | Wolfgang Amadeus Mozart | 1756 | 1791 | Austrian
  3 | Frédéric Chopin         | 1810 | 1849 | Polish
(3 rows)

続いて、代表的な作品をいくつか登録します。

作品データをINSERTして確認
classical_demo=# INSERT INTO works (composer_id, title, genre, year, duration_min)
VALUES
  (1, 'Symphony No.5 in C minor, Op.67', 'Symphony', 1808, 35),
  (1, 'Piano Sonata No.14 "Moonlight"', 'Piano Sonata', 1801, 15),
  (2, 'Requiem in D minor, K.626', 'Requiem', 1791, 50),
  (2, 'The Magic Flute, K.620', 'Opera', 1791, 160),
  (3, 'Nocturne in E-flat major, Op.9-2', 'Nocturne', 1832, 5),
  (3, 'Piano Concerto No.1 in E minor, Op.11', 'Piano Concerto', 1830, 40);
INSERT 0 6

classical_demo=# SELECT * FROM works;
 id | composer_id |                 title                 |     genre      | year | duration_min 
----+-------------+---------------------------------------+----------------+------+--------------
  1 |           1 | Symphony No.5 in C minor, Op.67       | Symphony       | 1808 |           35
  2 |           1 | Piano Sonata No.14 "Moonlight"        | Piano Sonata   | 1801 |           15
  3 |           2 | Requiem in D minor, K.626             | Requiem        | 1791 |           50
  4 |           2 | The Magic Flute, K.620                | Opera          | 1791 |          160
  5 |           3 | Nocturne in E-flat major, Op.9-2      | Nocturne       | 1832 |            5
  6 |           3 | Piano Concerto No.1 in E minor, Op.11 | Piano Concerto | 1830 |           40
(6 rows)

6. JOINで「作曲家+曲名」を一覧する

せっかくテーブルを分けたので、JOIN して一覧を出してみます。

JOINで作曲家+曲名を一覧表示
classical_demo=# SELECT
  w.title,
  w.genre,
  c.name AS composer,
  w.year
FROM works w
JOIN composers c ON w.composer_id = c.id
ORDER BY w.year;
                 title                 |     genre      |        composer         | year 
---------------------------------------+----------------+-------------------------+------
 Requiem in D minor, K.626             | Requiem        | Wolfgang Amadeus Mozart | 1791
 The Magic Flute, K.620                | Opera          | Wolfgang Amadeus Mozart | 1791
 Piano Sonata No.14 "Moonlight"        | Piano Sonata   | Ludwig van Beethoven    | 1801
 Symphony No.5 in C minor, Op.67       | Symphony       | Ludwig van Beethoven    | 1808
 Piano Concerto No.1 in E minor, Op.11 | Piano Concerto | Frédéric Chopin         | 1830
 Nocturne in E-flat major, Op.9-2      | Nocturne       | Frédéric Chopin         | 1832
(6 rows)

ここまで来ると、
「自分で作ったクラシック音楽DBを psql で操作できている」 実感が湧いてきますね。

7. psqlらしい小技:\x(縦表示)と \timing

psql には、表示を少し便利にしてくれる小技もあります。

7-1. \x で縦表示にする

曲名が長い行を縦表示にすると、行を折り返さずに読めて便利です。

\xを使って縦表示にする
classical_demo=# \x
Expanded display is on.

classical_demo=# SELECT * FROM works WHERE title LIKE '%Piano%';
-[ RECORD 1 ]+--------------------------------------
id           | 2
composer_id  | 1
title        | Piano Sonata No.14 "Moonlight"
genre        | Piano Sonata
year         | 1801
duration_min | 15
-[ RECORD 2 ]+--------------------------------------
id           | 6
composer_id  | 3
title        | Piano Concerto No.1 in E minor, Op.11
genre        | Piano Concerto
year         | 1830
duration_min | 40

classical_demo=# \x
Expanded display is off.

7-2. \timing でクエリ時間を表示する

クエリがどれくらいかかったか、簡単に測ることもできます。

\timingでクエリ時間を表示
classical_demo=# \timing on
Timing is on.

classical_demo=# SELECT * FROM works;
 id | composer_id |                 title                 |     genre      | year | duration_min 
----+-------------+---------------------------------------+----------------+------+--------------
  1 |           1 | Symphony No.5 in C minor, Op.67       | Symphony       | 1808 |           35
  2 |           1 | Piano Sonata No.14 "Moonlight"        | Piano Sonata   | 1801 |           15
  3 |           2 | Requiem in D minor, K.626             | Requiem        | 1791 |           50
  4 |           2 | The Magic Flute, K.620                | Opera          | 1791 |          160
  5 |           3 | Nocturne in E-flat major, Op.9-2      | Nocturne       | 1832 |            5
  6 |           3 | Piano Concerto No.1 in E minor, Op.11 | Piano Concerto | 1830 |           40
(6 rows)

Time: 1.068 ms

classical_demo=# \timing off
Timing is off.

本番環境での軽いチェックや、
「インデックスを貼る前後でどれくらい違うか試してみる」といった用途にも使えます。

8. おわりに

今回は、psql だけを使って

  • Docker 上の PostgreSQL に接続する
  • メタコマンド(\l, \c, \dt, \d など)で中身を眺める
  • クラシック音楽のミニ DB を作る(CREATE TABLE / INSERT / SELECT / JOIN
  • \x\timing といった小技で見やすく・測りやすくする

ところまでを一気にやってみました。

GUI ツールも便利ですが、
「とりあえず SSH 先で psql を叩ける」 というだけで、
トラブルシュートや確認作業のスピードがだいぶ変わってきます。

次回は、psql の他のメタコマンド \dt+\di を使ってみたり、\copy を使って CSV に書き出してみたり、
.psqlrc を使って「いつも使う設定」を仕込んでおくあたりも触ってみようかなと思います。

読んでくださってありがとうございました。
それではまた!