[PostgreSQL] チートシート

私の作業用として、よく使うSQLやTIPS、トラブルシューティング的なものをまとめました。

SQL

データベース操作

バージョン確認

SELECT * FROM version();

データベース一覧

select * from pg_database;

データベース作成

-- ユーザ作成
create role user_name with superuser login password 'password';

-- データベース削除
drop database database_name;

-- データベース作成
create database database_name
  with owner user_name
    template template0
    encoding 'UTF8'
    lc_collate 'C'
    lc_ctype 'C'
;
  • 文字コードセットはUTF8
  • lc_collage(文字の照合順序)、lc_ctype(大文字・小文字・数字の分類)に「C」を指定しています
    • この場合、文字の照合順序は文字コードのバイナリ順、分類は大文字・小文字を分けて処理されるようになります
    • 「ja_JP.UTF-8」のように言語を指定できますが、その場合は文字の照合順序が文字順、分類は大文字・小文字を区別しないようになります
    • パフォーマンスも若干下がるようです

データベースのサイズ確認

select datname, pg_size_pretty(pg_database_size(datname)) from pg_database;

登録されている拡張機能の一覧確認

SELECT * FROM pg_available_extensions;

ユーザー操作

ユーザー一覧確認

select * from pg_user;

テーブル操作

テーブル一覧(行数+サイズ)確認

SELECT * FROM information_schema.tables WHERE table_catalog = 'database_name';
 
SELECT
  t.table_name
  , pc.reltuples AS row_num
  , (pc.relpages * 8192) AS byte_size
FROM
  information_schema.tables t
  INNER JOIN pg_class pc
    ON pc.relname = t.table_name
WHERE
  t.table_catalog = 'database_name' 
  AND t.table_schema = 'public' 
  AND t.table_name NOT LIKE '\_%'
ORDER BY
  t.table_name
;

select句からテーブル作成

CREATE TABLE new_table_name AS SELECT * FROM old_table_name;

インデックス操作

そのうち書きます

カラム操作

カラム一覧の確認

SELECT
  c.*
FROM
  information_schema.tables t
  INNER JOIN information_schema.columns c
    ON c.table_catalog = t.table_catalog
    AND c.table_schema = t.table_schema
    AND c.table_name = t.table_name
WHERE
  t.table_catalog = 'database_name'
  AND t.table_name = 'table_name'
ORDER BY
  t.table_name
  , c.ordinal_position

ストアドプロシージャ操作

無名のコードブロック

無名コードブロック
do $$declare
  -- 変数宣言
BEGIN
  -- 処理コード
END;$$

無名のコードブロック(サンプル)

do $$declare
 
  -- 変数定義
  c_loganalyze cursor (p_logtype VARCHAR) IS
    SELECT filename, seq, logtype, message
    FROM LogAnalyze
    WHERE logtype = p_logtype
    ;
  v_rec               record;
  v_fields          CHARACTER VARYING(256)[];               -- MESSAGE列をスペースで区切った配列
 
BEGIN
 
  -- heapログのMESSAGE列に対して、スペースで分割しField1~4までデータを更新していく
  FOR v_rec IN c_loganalyze('vmstat') loop
 
    -- スペースで区切った配列をそれぞれのフィールドに格納
    v_fields := regexp_split_to_array(v_rec.message, E'\ +');
 
    -- ここまでで切り出した各FIELD用の変数を用いてUpdate
    UPDATE
      LogAnalyze
    SET
      field1 = v_fields[3],
      field2 = v_fields[4],
      field3 = v_fields[5],
      field4 = v_fields[6],
      field5 = v_fields[7],
      field6 = v_fields[8],
      field7 = v_fields[9],
      field8 = v_fields[10],
      field9 = v_fields[11],
      field10 = v_fields[12],
      field11 = v_fields[13],
      field12 = v_fields[14],
      field13 = v_fields[15],
      field14 = v_fields[16],
      field15 = v_fields[17],
      field16 = v_fields[18]
    WHERE
      filename = v_rec.filename AND
      seq = v_rec.seq AND
      logtype = v_rec.logtype
    ;
  END loop;
 
END;$$

SELECT

そのうち書きます

INSERT

INSERT INTO table_name (
  column_3
  , column_4
)
SELECT
  column_1
  , column_2
FROM
  table_name_1
;

UPDATE

UPDATE table_name SET column_name = 'value' WHERE KEY = 1;

UPDATE main_table
SET
  column1 = jt1.column1
FROM main_table a
  LEFT OUTER JOIN join_table_1 jt1
    ON jt1.key = a.key
WHERE
  a.key = main_table.key
  AND a.key = 1
;
  • PostgreSQLのupdate joinの制約
    • 更新対象のテーブル(update句の後に続くテーブル)に対してjoinをつけることができないようです
    • 更新対象のテーブルにエイリアスをつけてもset句の中で使うことができません
      • https://www.postgresql.jp/document/12/html/sql-update.html
        • column_name
          • table_nameで指名されたテーブル内の列名です。 必要に応じて、列名を副フィールド名や配列の指示子で修飾することも可能です。 対象列の指定にはテーブル名を含めないでください。 たとえば、UPDATE table_name SET table_name.col = 1は無効です。
    • select文のようにfrom句を指定しそこにjoinでテーブルを結合できますが、そのままではupdate句のテーブルと何の関係もなくなってしまいます
      • つまり全件更新されてしまいます
  • MySQL等からのSQL移植
    • updateのターゲットテーブルをfrom句に追加、エイリアスも
    • その下にjoinたちを移動
    • update xxxx alias のaliasは削除、set句の中にある更新対象テーブルのエイリアスを削除
    • where句にupdateのターゲットテーブルとfromのキーの結合条件を追加(しないと全テーブル更新されるためです)

DELETE

DELETE
FROM
  table_name
WHERE
  KEY = 1

コマンド

psql

pgdumpコマンドで出力したスクリプトをpsqlコマンドで実行

-- ファイルをリダイレクトで読み込み実行
psql -h localhost -U postgres -p 5432 -d database_name < backup.sql

-- -fオプションで実行
psql -h localhost -U postgres -p 5432 -d database_name -f backup.sql
  • psqlではコマンドラインオプションでパスワードを指定できませんが、パスワードファイルを用意しておくことで実行可能となります
    • Windows
      • ファイル %APPDATA%\postgresql\pgpass.conf 作成
      • 上記ファイルに「hostname:port:database:username:password」を記載し保存
    • Linux
      • homeディレクトリに.pgpassファイルを作成
      • 上記ファイルに「hostname:port:database:username:password」を記載し保存
  • 環境変数にパスワードやユーザー名を設定することも可能です
    • set PGUSER=xxx
    • set PGPASSWORD=xxx
    • set PGHOSTNAME=xxx
    • set PGPORT=xxx
    • set PGDATABASE=xxx
  • 読み込むSQLファイルがUTF8の場合は環境変数で文字コードを指定します
    • set PGCLIENTENCODING=UTF8

ファイルにあるSQLを実行してCSV出力

psql -h localhost -U postgres -p 5432 -d database_name -f test.sql -A -F"," -o output.csv
  • -A:均等割り付けを行わない
  • -F:フィールドセパレータ、タブを指定したい場合はctrl-vの後にタブキー入力

pgdump

エクスポート

-- リダイレクトでエクスポート
pg_dump -h localhost -U postgres database_name > backup.sql

-- -fオプションでファイルを指定しエクスポート
pg_dump -f backup.sql -h localhost -U postgres database_name

テーブルを指定してエクスポート

pg_dump -t table_name -f backup.sql -c -h localhost -p 5432 -U user_name database_name

vacuumdb

vacuumdb -U postgres database_name [option]
  • -full
    • FULL実行
  • -v
    • 詳細な情報を出力

パフォーマンスチェック/チューニング

SQLの確認

そのうち書きます

チューニング

そのうち書きます

TIPS

ログの出力先をsyslogに変更

log_destination = 'stderr,syslog'
 
syslog_facility = 'LOCAL0'
syslog_ident = 'postgres'
syslog_sequence_numbers = on
syslog_split_messages = on
 
log_min_messages = info
  • PostgreSQLはログの出力先を複数持てます
    • log_destinationにカンマ区切りで記載できるそうです
  • 設定反映にはデーモンの再起動が必要です
    • sudo systemctl restart postgresql-13 sudo systemctl status postgresql-13
  • エラーログが出力されているかどうかは、サーバー上でpsqlのログインにパスワード入力で失敗すれば分かります
    • sudo su – postgres -c “psql”

PostgreSQLのzipファイルを使用してインストール

こちらから「Binaries from installer Version xx.x」をダウンロードします

zipファイルを展開し以下のフォルダを作成(以下はC:\pgsqlに展開した例)

  • C:\pgsql
    • data
    • log

initdbを実行しスーパーユーザーを作成します

C:\WINDOWS\system32>initdb -U postgres -W -A password -D C:\pgsql\data --encoding=UTF8
データベースシステム内のファイルの所有者はユーザ"xxxxxx"となります。
このユーザをサーバプロセスの所有者とする必要があります。
 
データベースクラスタはロケール"Japanese_Japan.932"で初期化されます。
initdb: could not find suitable text search configuration for locale "Japanese_Japan.932"
デフォルトのテキスト検索構成は simple に設定されます。
 
データベージのチェックサムは無効です。
 
新しいスーパユーザのパスワードを入力してください:
再入力してください:
 
ディレクトリC:/pgsql/dataの権限を設定しています ... ok
サブディレクトリを作成しています ... ok
動的共有メモリの実装を選択しています ... windows
デフォルトのmax_connectionsを選択しています ... 100
デフォルトの shared_buffers を選択しています ... 128MB
selecting default time zone ... Asia/Tokyo
設定ファイルを作成しています ... ok
ブートストラップスクリプトを実行しています ... ok
ブートストラップ後の初期化を実行しています ... ok
データをディスクに同期しています ... ok
 
成功しました。以下のようにしてデータベースサーバを起動することができます:
 
    pg_ctl -D ^"C^:^\pgsql^\data^" -l ログファイル start

postgres のパスワードを忘れた時

pg_hba.conf を探してMETHODの指定をpassword→trustに変更します。(ファイルはdataディレクトリにあると思います)

# TYPE  DATABASE        USER            ADDRESS                 METHOD
 
# IPv4 local connections:
host    all             all             127.0.0.1/32            password
# IPv6 local connections:
host    all             all             ::1/128                 password
# Allow replication connections from localhost, by a user with the
# replication privilege.
host    replication     all             127.0.0.1/32            password
host    replication     all             ::1/128                 password
  • trustはパスワードなしで接続OKの設定です

PostgreSQLを再起動し、psqlでログイン後にパスワードを変更します。

C:\Windows\system32> psql -h localhost -U postgres
psql (12.4)
"help"でヘルプを表示します。
 
postgres=#
postgres=# \password
postgres=# alter role postgres with password 'new password';
postgres=# \q

編集したpg_hba.confを元に戻してPostgreSQLを再起動します。

SQLSTATE[42804]: Datatype mismatch: 7 ERROR: UNIONの型textとintegerを一致させることができません

PostgreSQLでは3つ以上のunionをする場合、先頭のSQLで指定されたnull as xxxxはTEXT型と判断されてしまうようです。

以下がエラーの発生する例です。

SELECT NULL AS col
UNION
SELECT NULL AS col
UNION
SELECT 1 AS col
;
  • 1・2番目のcolはnullですが、3番目のcolは数値なのでtext型にできずエラーとなってしまいます

型の分かるselectを先に持ってくることで対応可能です。

SELECT 1 AS col
UNION
SELECT NULL AS col
UNION
SELECT NULL AS col
;