◆ \if で分岐できる
◆ 変数も使えて :foo 形式で参照できる
  ◆ psql コマンドの -v オプションで変数をセットできる
  ◆ SELECT と \gset を使って実行中に SELECT の結果を変数にセットもできる

SQL ファイルの中で 分岐したり変数を使ったりしたいことがあります
環境ごとにスキーマやテーブル名が少し違ったり 特定の環境のみ追加のテーブルが必要だったり 権限の関係で追加の GRANT が必要だったり そういうのです

以前は 適当なテンプレートエンジンを使って そのテンプレートエンジンの構文で変数や分岐を処理して環境ごとの SQL ファイルを一時的に生成してそれを実行ということをやってました
これが複雑になるので SQL だけでやりたいと思うのですが PL/pgSQL はこれはこれで複雑です
ここまで高機能じゃなくていいんですよね

探すと psql の中のメタコマンドだけで実現できそうだったのでやってみます
一応メタコマンドの if は以前から存在は知ってたものの 機能不足でやりたいことができなさそうと思って使ってなかったのですが ちゃんとドキュメントを見るとちょっと面倒なだけで機能としては実現できました

サンプル

まずサンプルです
foo という変数の値によって分岐します

[a.sql]
\if :{?foo}
\else
\echo '変数 foo を入力してください'
\q
\endif

SELECT :'foo' = 'A' as is_a, :'foo' = 'B' as is_b \gset

\if :is_a
\echo 'foo は A です'
\elif :is_b
\echo 'foo は B です'
\else
\echo 'foo はその他です'
\endif

コマンドラインから foo に色々な値を入れて実行してみます

root@b3c9f1fa843b:/# psql -U postgres -f a.sql
変数 foo を入力してください
root@b3c9f1fa843b:/# psql -U postgres -f a.sql -v foo=A
foo は A です
root@b3c9f1fa843b:/# psql -U postgres -f a.sql -v foo=B
foo は B です
root@b3c9f1fa843b:/# psql -U postgres -f a.sql -v foo=C
foo はその他です
root@b3c9f1fa843b:/# psql -U postgres -f a.sql -v foo=
foo はその他です

メタコマンド

\ から始まるのが psql のメタコマンドです
ここだと通常の SQL を実行してませんが \echo のところに INSERT とか書くと特定の条件の場合だけ SQL を実行できます

変数をコマンドラインから指定する場合は -v を使って foo=bar 形式で指定します
複数の変数を指定するなら -v を複数回使います

変数の指定を必須にしたい場合は 変数が存在することを確認して存在しないならエラーにします
「:{?foo}」 のように書くと 変数 foo が存在するかで TRUE/FALSE になります

\if や \elif には expression を指定できるのですが expression といいつつ ここで計算ができません
= とか < みたいなものも使えないです
変数名が指定できるだけで 変数が TRUE/FALSE で分岐するだけです
TRUE/FALSE の反転が簡単にできず unless のようなものもないので FALSE のときだけしたい処理が簡単にできないです
\if は空にして \else の方を使うみたいなことになります

\if を使うには変数に TRUE/FALSE を入れないといけないです
SQL ファイルの処理中に変数に代入するには SELECT と \gset を使います

SELECT 1 as var1 \gset

のように ; の代わりに \gset を書くと SELECT の結果を変数に入れられます
as で指定した名前が変数名になります
SELECT で列数を増やせば 同時に複数の変数へ代入できますし SELECT で実行できる処理は何でもできるので TRUE/FALSE を反転したり足し算したり比較したり色々できます

他にも \set を使って変数に代入することもできます

postgres=# \set foo bar
postgres=# \echo :foo
bar

ただこれは固定値になるのであまり使いどころがなくて \if の分岐の中で固定値をセットするような使い道です

ユーザー入力

\prompt を使ってユーザー入力を受け取って変数に代入もできます

\prompt 'password: ' pw
\echo :pw
password: foo
foo

プロンプトとして 「password: 」 までが表示されてその後の foo はユーザーが入力したものです
次の行の foo が変数を表示したものです

繰り返し

psql のメタコマンドはそこまで高機能ではないので 分岐はできますが繰り返しや関数のような機能はありません
そこまでのことをするなら PL/pgSQL を使ったほうがいいんじゃないかと思います

ただ include 機能があるのでそれっぽいことは一応できなくはないです

[main.sql]
drop table if exists foo;
create table foo (id integer primary key);

\set i 0

\ir loop.sql

select * from foo;

[loop.sql]
insert into foo (id) values (:'i');

select :'i'::integer + 1 as i \gset
select :'i'::integer < 10 as continue \gset

\if :continue
\ir loop.sql
\else
\echo 'done'
\endif

root@2676da1df87f:/# psql -U postgres -f main.sql
DROP TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
done
id
----
0
1
2
3
4
5
6
7
8
9
(10 rows)

繰り返したい部分を別の SQL ファイルに切り出して 条件に一致する限り再帰的に include し続けます

繰り返し 2

別の考え方で SQL を作って実行させるという方法もあります
\gexec を使うと SELECT の結果を実行できます

postgres=# select 'insert into foo (id) values (' || generate_series(0, 9) || ')';
?column?
---------------------------------
insert into foo (id) values (0)
insert into foo (id) values (1)
insert into foo (id) values (2)
insert into foo (id) values (3)
insert into foo (id) values (4)
insert into foo (id) values (5)
insert into foo (id) values (6)
insert into foo (id) values (7)
insert into foo (id) values (8)
insert into foo (id) values (9)
(10 rows)

これを実行します

postgres=# truncate foo;
TRUNCATE TABLE
postgres=# select 'insert into foo (id) values (' || generate_series(0, 9) || ')'\gexec
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
postgres=# select * from foo;
id
----
0
1
2
3
4
5
6
7
8
9
(10 rows)

ただ やることがこれなら \gexec を通さなくても INSERT を使ってできます

insert into foo (id) values (generate_series(0, 9));

その他

psql のメタコマンドは他にも色々あってドキュメントにまとまってます
https://www.postgresql.org/docs/current/app-psql.html