本文へスキップ
Typle
ログイン状態を確認中

プログラミング · PostgreSQL · SQL

PostgreSQL構文のタイピング練習

PostgreSQL の SQL 構文を、初級から上級までの4つのリスト(152個)で打って覚えます。AS・IN・AND/OR などの条件から、CREATE TABLE と制約まで、SQL を書く形のまま練習できます。

4リスト・152語

無料・ログインなしで練習できます。押すとこのパックをフォローし、すぐに練習が始まります。

収録している言葉

PostgreSQL構文(初級)

打つ言葉意味・補足
SHOW server_version;対象バージョン:例の利用条件(PostgreSQL 15以降を想定した主要構文。各例は独立した断片で、表・列・権限等の準備が必要)
SELECT 1; SELECT 2;;:文を区切る
SELECT 'Pomo';'文字列':文字列リテラル(文字列中の単一引用符は''で表す)
SELECT name AS "表示名" FROM users;"識別子":識別子の引用(引用しない識別子は小文字として扱う。文字列の引用とは異なる)
SELECT * FROM public.users;スキーマ.表:スキーマ修飾(同名オブジェクトの曖昧さを避ける)
SELECT id, name FROM users;SELECT / FROM:列を取得
SELECT * FROM users;SELECT *:全列を取得(列追加の影響を受けやすい)
SELECT * FROM users WHERE age >= 20;WHERE:条件で絞る
SELECT * FROM users WHERE age <> 20;比較演算子:比較(NULLとの通常比較は真偽がunknownになる)
SELECT * FROM users WHERE email IS NULL;IS NULL / IS NOT NULL:NULLの判定
SELECT DISTINCT department_id FROM users;DISTINCT:重複行を除く(選択した列全体で重複判定)
SELECT COUNT(*) FROM users;COUNT(*):行数
SELECT COUNT(email) FROM users;COUNT(列):非NULLの件数(NULLを数えない)
SELECT MIN(age), MAX(age) FROM users;MIN / MAX:最小・最大
TRUNCATE TABLE orders;TRUNCATE:表をまとめて空にする(強いロックを取る。DELETEトリガーは発火しない)
TRUNCATE TABLE orders RESTART IDENTITY;TRUNCATE ... RESTART IDENTITY:表を空にし採番も戻す(全データ削除。表に所有されるシーケンスが対象)
ALTER TABLE users ADD COLUMN nickname text;ALTER TABLE ADD COLUMN:列を追加
ALTER TABLE users DROP COLUMN nickname;ALTER TABLE DROP COLUMN:列を削除(列データを失う)
DROP TABLE notes;DROP TABLE:表を削除(表とデータを失う。CASCADEは依存オブジェクトにも影響)
CREATE SCHEMA app;CREATE SCHEMA:スキーマを作る
SELECT 123.45::numeric(12, 2);numeric(p,s):正確な小数(金額等。精度・スケールに制約)
SELECT 0.1::double precision;double precision:近似の浮動小数点(正確な10進小数ではない)
SELECT true, false, NULL::boolean;boolean:真偽値
SELECT INTERVAL '7 days';interval:期間
SELECT gen_random_uuid();uuid:UUID(例の関数はPostgreSQL 15以降で組込み)
SELECT decode('4869', 'hex');bytea:バイナリ
SELECT ROUND(123.456::numeric, 2);ROUND:小数を丸める(小数桁指定の例はnumeric)
SELECT CURRENT_DATE, NOW();CURRENT_DATE / NOW:現在の日付・日時(NOWはトランザクション開始時点)
SELECT clock_timestamp();clock_timestamp:実際の現在時刻(同一文の実行中にも変化し得る)
SELECT NOW() - INTERVAL '7 days';日時 + INTERVAL:日時の加減算
SELECT * FROM generate_series(1, 5);generate_series:連番等を生成
DROP INDEX users_age_idx;DROP INDEX:インデックス削除(その索引が存在する場合)
REFRESH MATERIALIZED VIEW user_totals;REFRESH MATERIALIZED VIEW:保存結果を更新(通常は参照をブロック。CONCURRENTLYには適切な一意索引等が必要)
ANALYZE users;ANALYZE:統計情報を更新(対象に適切な権限が必要)
VACUUM (ANALYZE) users;VACUUM:不要領域の再利用等(トランザクションブロック内不可。通常OSへ容量を返す処理ではない)
CREATE ROLE app_reader;CREATE ROLE:ロール作成(適切な管理権限が必要)
REVOKE SELECT ON users FROM app_reader;REVOKE:権限を取り消す(他ロール経由等の権限は残る場合がある)
SHOW TimeZone;SHOW:設定を確認

PostgreSQL構文(中級1)

打つ言葉意味・補足
SELECT name AS display_name FROM users AS u;AS:別名を付ける
SELECT * FROM users WHERE id IN (1, 2, 3);IN:候補に含まれるか
SELECT * FROM users WHERE id NOT IN (1, 2);NOT IN:候補に含まれないか(候補にNULLを含むと意図しない結果になり得る。NOT EXISTSも検討)
SELECT * FROM users WHERE age BETWEEN 20 AND 29;BETWEEN:両端を含む範囲(日時では>=と<の半開区間が便利)
SELECT * FROM users WHERE age IS DISTINCT FROM 20;IS DISTINCT FROM:NULLを含めた不等比較(NULLも比較可能な値として扱う)
SELECT * FROM users WHERE name LIKE 'Po%';LIKE:パターン一致(%は任意長、_は任意1文字)
SELECT * FROM users WHERE name ILIKE 'po%';ILIKE:大文字小文字を区別しない一致(大文字小文字の扱いはロケール等に依存)
SELECT * FROM users WHERE name ~* '^po';~ / ~*:正規表現一致(~*は大文字小文字を区別しない)
SELECT * FROM users ORDER BY age ASC NULLS LAST;NULLS FIRST / LAST:NULLの並び位置
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 40;LIMIT / OFFSET:取得件数・開始位置(一意に並ぶORDER BYを指定。大きなOFFSETは負荷になり得る)
SELECT COUNT(DISTINCT department_id) FROM users;COUNT(DISTINCT 列):非NULLの異なる値の数
SELECT SUM(amount), AVG(amount) FROM orders;SUM / AVG:合計・平均(対象なしの場合は通常NULL)
SELECT ARRAY_AGG(id ORDER BY id) FROM users;ARRAY_AGG:配列へ集約
SELECT JSONB_AGG(name ORDER BY id) FROM users;JSONB_AGG:JSON配列へ集約
SELECT id FROM users EXCEPT SELECT user_id FROM orders;INTERSECT / EXCEPT:共通部分・差集合(列数と型に互換性が必要)
INSERT INTO users (name) VALUES ('A'), ('B');複数行INSERT:複数行を追加
INSERT INTO counters DEFAULT VALUES RETURNING id;DEFAULT VALUES:既定値で追加(IDENTITYの例で作成するcounters表向け。全必須列に既定値がある表で使用)
DELETE FROM users WHERE id = 1 RETURNING id;DELETE / WHERE:行を削除(WHEREなしは全行削除。外部キー制約で拒否される場合がある)
CREATE TABLE items ( id bigint PRIMARY KEY );PRIMARY KEY:主キー(一意かつNOT NULL)
ALTER TABLE users ALTER COLUMN email SET NOT NULL;NOT NULL:NULL禁止(既存NULLがあると失敗)
ALTER TABLE users ALTER COLUMN active SET DEFAULT true;DEFAULT:既定値(既存行の値は変更しない)
ALTER TABLE users DROP CONSTRAINT users_name_unique;DROP CONSTRAINT:制約を削除(制約が存在する場合)
SELECT 42::integer, 10000000000::bigint;integer / bigint:整数
SELECT 'hello'::text, 'abc'::varchar(10);text / varchar(n):文字列(varchar(n)は長さ制限)
SELECT TIMESTAMPTZ '2026-01-01 12:00:00+09';timestamptz:時点を扱う日時(表示はセッションTimeZoneに依存。元の地域名は保持しない)
SELECT '{"name":"Pomo"}'::jsonb;json / jsonb:JSON(jsonbはインデックス対応。キー順・重複キーの保持等がjsonと異なる)
SELECT ARRAY['go', 'sql']::text[];型[] / ARRAY:配列(PostgreSQLの配列添字は通常1から)
SELECT COALESCE(email, '未登録') FROM users;COALESCE:最初の非NULL値
SELECT amount / NULLIF(0, 0) FROM orders;NULLIF:等しいならNULL(ゼロ除算回避等)
SELECT CAST('123' AS integer), '123'::integer;CAST / :::型変換(変換不可はエラー)
SELECT EXTRACT(YEAR FROM created_at) FROM users;EXTRACT:日時の要素を取得
SELECT NOW() AT TIME ZONE 'Asia/Tokyo';AT TIME ZONE:タイムゾーンの解釈・変換(入力型により戻り値型・意味が異なる)
SELECT profile #>> '{address,city}' FROM users;#> / #>>:JSONの深いパス
SELECT * FROM users WHERE profile ? 'name';?:JSONBのキー等の存在(ドライバーによってプレースホルダー記号との扱いに注意)
SELECT * FROM users WHERE 'sql' = ANY(tags);ANY:配列要素との比較
SELECT id, unnest(tags) AS tag FROM users;unnest:配列を行へ展開
BEGIN READ ONLY; SELECT * FROM users; COMMIT;READ ONLY:読み取り専用トランザクション
EXPLAIN SELECT * FROM users WHERE id = 1;EXPLAIN:実行計画を表示(通常は対象SQLを実行しない)

PostgreSQL構文(中級2)

打つ言葉意味・補足
/* 1行コメント */ SELECT /* ブロックコメント */ 1;-- / /* ... */:コメント
SELECT * FROM users WHERE active AND (age >= 20 OR age IS NULL);AND / OR / NOT:条件を組み合わせる(混在時は括弧で意図を明示)
SELECT * FROM users ORDER BY created_at DESC, id DESC;ORDER BY:並べ替え(指定しない取得順は保証されない)
SELECT * FROM users WHERE id > 100 ORDER BY id LIMIT 20;キ―セットページング:前回の続きから取得(前回の最終id等を使用)
SELECT department_id, COUNT(*) FROM users GROUP BY department_id;GROUP BY:グループ単位に集計
SELECT STRING_AGG(name, ', ' ORDER BY id) FROM users;STRING_AGG:文字列を連結(順番が必要なら集計内でORDER BY)
SELECT * FROM orders WHERE amount > ( SELECT AVG(amount) FROM orders );スカラーサブクエリ:単一値を式に使う(複数行を返すとエラー)
SELECT id FROM users WHERE active UNION ALL SELECT id FROM users WHERE NOT active;UNION / UNION ALL:結果を連結(UNIONは重複排除、UNION ALLは保持)
INSERT INTO users (name) VALUES ('Pomo') RETURNING id, name;RETURNING:変更した行を返す(UPDATE・DELETEでも使用可能)
UPDATE users SET age = age + 1 WHERE id = 1 RETURNING id, age;UPDATE / SET / WHERE:行を更新(WHEREなしは全行更新)
DELETE FROM orders o USING users u WHERE o.user_id = u.id AND NOT u.active;DELETE ... USING:結合条件で削除
CREATE TABLE counters ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY );IDENTITY:自動採番列(値の連続性・欠番なしを保証しない)
ALTER TABLE users ADD CONSTRAINT users_name_unique UNIQUE (name);UNIQUE:一意制約(既存の重複があると失敗。通常NULL同士は同値としない)
ALTER TABLE orders ADD CONSTRAINT orders_amount_positive CHECK (amount >= 0);CHECK:行の条件制約(条件がNULLの場合も通る。必要ならNOT NULLも併用)
ALTER TABLE users ALTER COLUMN age TYPE bigint USING age::bigint;ALTER COLUMN TYPE:列型を変更(ロック・書き換えの負荷が発生する場合がある)
ALTER TABLE users RENAME COLUMN nickname TO display_name;RENAME COLUMN / TO:列名・表名を変更(この例はnickname列がある場合)
CREATE TABLE user_snapshot AS SELECT id, name FROM users;CREATE TABLE AS:検索結果で表を作る(元表の制約・インデックス等は通常引き継がない)
SELECT DATE '2026-01-01', TIMESTAMP '2026-01-01 12:00:00';date / timestamp:日付・タイムゾーンなし日時(timestamp単独はwithout time zone)
SELECT name, CASE WHEN age >= 20 THEN '成人' ELSE 'その他' END FROM users;CASE WHEN:条件に応じた値
SELECT name || 'さん', CONCAT(name, 'さん') FROM users;|| / CONCAT:文字列連結(||はNULLがあると通常NULL。CONCATはNULL引数を無視)
SELECT LOWER(name), UPPER(name), TRIM(name) FROM users;LOWER / UPPER / TRIM:文字列加工
SELECT LENGTH(name), SUBSTRING(name FROM 1 FOR 2) FROM users;LENGTH / SUBSTRING:長さ・部分文字列(LENGTH(text)は文字数)
SELECT profile->'address', profile->>'name' FROM users;-> / ->>:JSON要素の取得(->はJSON、->>はtext)
SELECT * FROM users WHERE profile @ > '{"premium":true}'::jsonb;@>:JSONB包含
SELECT jsonb_build_object('id', id, 'name', name) FROM users;jsonb_build_object:JSONBオブジェクトを構築
SELECT * FROM users WHERE tags && ARRAY['go', 'sql'];&&:配列の重なり
CREATE INDEX users_created_at_idx ON users(created_at);CREATE INDEX:検索用インデックス(読み取りが速くなる場合があるが更新・容量コストが増える)
CREATE INDEX orders_user_created_idx ON orders(user_id, created_at DESC);複合インデックス:複数列のインデックス(列順・条件・並べ替えに合わせて設計)
CREATE INDEX users_active_idx ON users(id) WHERE active;部分インデックス:条件に合う行だけを索引化(クエリ条件と述語の関係が重要)
CREATE INDEX users_lower_email_idx ON users(LOWER(email));式インデックス:式の結果を索引化
CREATE INDEX users_profile_gin_idx ON users USING GIN(profile);USING GIN:JSONB等向けGIN索引(演算子・演算子クラスにより対応が異なる)
CREATE INDEX CONCURRENTLY users_age_idx ON users(age);CREATE INDEX CONCURRENTLY:書き込みを止めにくい索引作成(トランザクションブロック内不可。失敗時に無効索引が残り得る)
CREATE VIEW active_users AS SELECT id, name FROM users WHERE active;CREATE VIEW:クエリをビューとして保存(通常は結果を保存しない)
BEGIN; UPDATE users SET active = false WHERE id = 1; COMMIT;BEGIN / COMMIT:処理をまとめて確定(実行すると変更が確定)
BEGIN; UPDATE users SET active = false WHERE id = 1; ROLLBACK;ROLLBACK:変更を取り消す(シーケンスの採番等は巻き戻らない)
BEGIN ISOLATION LEVEL SERIALIZABLE; SELECT * FROM users WHERE id = 1; COMMIT;ISOLATION LEVEL:分離レベルを指定(直列化失敗時はトランザクション全体の再試行が必要)
BEGIN; SELECT * FROM users WHERE id = 1 FOR UPDATE; COMMIT;FOR UPDATE:取得行を更新用にロック(ロックは通常トランザクション終了まで)
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE id = 1;EXPLAIN ANALYZE:実行して実測計画を表示(対象SQLを実際に実行。UPDATE・DELETE等にも副作用がある)

PostgreSQL構文(上級)

打つ言葉意味・補足
CREATE TABLE users ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name text NOT NULL, email text UNIQUE, age integer CHECK (age >= 0), active boolean NOT NULL DEFAULT true, department_id bigint, created_at timestamptz NOT NULL DEFAULT now(), profile jsonb NOT NULL DEFAULT '{}'::jsonb, tags text[] NOT NULL DEFAULT '{}' ); CREATE TABLE orders ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id bigint REFERENCES users(id), amount numeric(12, 2) NOT NULL, status text NOT NULL DEFAULT 'pending', created_at timestamptz NOT NULL DEFAULT now() );サンプルテーブル:例に使う表を作成(空の検証用スキーマ向け。同名表があると失敗。以下の例を全部順番に実行する用途ではない)
PREPARE find_user(bigint) AS SELECT * FROM users WHERE id = $1; EXECUTE find_user(1); DEALLOCATE find_user;$1等のパラメーター:値をパラメーター化(アプリではドライバーのパラメーターAPIを使用。識別子は値パラメーターでは置き換えられない)
SELECT DISTINCT ON (department_id) department_id, id, created_at FROM users ORDER BY department_id, created_at DESC, id DESC;DISTINCT ON:グループごとに先頭行(PostgreSQL固有。DISTINCT ON式をORDER BYの左端に合わせる)
SELECT department_id, COUNT(*) FROM users GROUP BY department_id HAVING COUNT(*) >= 5;HAVING:集計後に絞る(WHEREは集計前の行に作用)
SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE active) AS active_count FROM users;FILTER:集計の対象を条件指定
SELECT u.name, o.amount FROM users u INNER JOIN orders o ON o.user_id = u.id;INNER JOIN:一致する行を結合
SELECT u.id, o.id AS order_id FROM users u LEFT JOIN orders o ON o.user_id = u.id;LEFT JOIN:左側を残して結合(右側へのWHERE条件で実質INNER JOINになる場合がある)
SELECT u.id, o.id AS order_id FROM users u FULL OUTER JOIN orders o ON o.user_id = u.id;FULL OUTER JOIN:両側の未一致行も残す
SELECT u.id, s.status FROM users u CROSS JOIN ( VALUES ('pending'), ('paid') ) AS s(status);CROSS JOIN:直積(行数が掛け算で増える)
SELECT u.id, x.total FROM users u JOIN ( SELECT user_id AS id, SUM(amount) AS total FROM orders GROUP BY user_id ) x USING (id);USING:同名の結合列を指定
SELECT * FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id );EXISTS:対応する行があるか
SELECT * FROM users u WHERE NOT EXISTS ( SELECT 1 FROM orders o WHERE o.user_id = u.id );NOT EXISTS:対応する行がないか
WITH totals AS ( SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id ) SELECT * FROM totals WHERE total >= 1000;WITH / CTE:クエリに名前を付ける(必ず実体化されるわけではない)
WITH RECURSIVE nums(n) AS ( VALUES (1) UNION ALL SELECT n + 1 FROM nums WHERE n < 5 ) SELECT n FROM nums;WITH RECURSIVE:再帰クエリ(終了条件が必要)
SELECT u.id, recent.id AS order_id FROM users u LEFT JOIN LATERAL ( SELECT id FROM orders o WHERE o.user_id = u.id ORDER BY created_at DESC, id DESC LIMIT 1 ) recent ON true;LATERAL:左側の行を参照する副問い合わせ(ユーザーごとの最新注文など)
SELECT id, user_id, amount, SUM(amount) OVER ( PARTITION BY user_id ) AS user_total FROM orders;OVER / PARTITION BY:行を残したまま集計(GROUP BYと異なり各行を残す)
SELECT id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY created_at, id ) AS n FROM orders;ROW_NUMBER:グループ内の連番(同順位の順番も決めるなら一意な並べ替え)
SELECT id, RANK() OVER ( ORDER BY amount DESC ), DENSE_RANK() OVER ( ORDER BY amount DESC ) FROM orders;RANK / DENSE_RANK:順位(RANKは同順位後に欠番、DENSE_RANKは欠番なし)
SELECT id, LAG(amount) OVER ( PARTITION BY user_id ORDER BY created_at, id ) FROM orders;LAG / LEAD:前後の行の値
SELECT id, SUM(amount) OVER ( ORDER BY created_at, id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS running_total FROM orders;ROWS BETWEEN:累積の窓枠(既定の窓枠との差に注意)
INSERT INTO users (name, email, age) VALUES ('Pomo', 'pomo@example.com', 20);INSERT INTO / VALUES:行を追加
INSERT INTO users (name) SELECT 'User ' || n FROM generate_series(1, 3) AS g(n);INSERT ... SELECT:検索結果を挿入
INSERT INTO users (name, email) VALUES ('Pomo', 'pomo@example.com') ON CONFLICT (email) DO NOTHING;ON CONFLICT DO NOTHING:一意競合時に追加しない(対象に適切な一意制約・インデックスが必要)
INSERT INTO users (name, email) VALUES ('New', 'pomo@example.com') ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name RETURNING id, name;ON CONFLICT DO UPDATE:一意競合時に更新(EXCLUDEDは挿入予定の行)
UPDATE users u SET active = true FROM ( SELECT DISTINCT user_id FROM orders WHERE status = 'paid' ) o WHERE u.id = o.user_id;UPDATE ... FROM:別の表や結果を使って更新(対象行が複数の結合行に一致しないようにする)
MERGE INTO users u USING ( VALUES ('pomo@example.com', 'Pomo') ) AS s(email, name) ON u.email = s.email WHEN MATCHED THEN UPDATE SET name = s.name WHEN NOT MATCHED THEN INSERT (email, name) VALUES (s.email, s.name);MERGE:一致条件に応じて更新・追加(PostgreSQL 15以降。ON CONFLICTと同じ競合処理保証ではない)
CREATE TABLE notes ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, body text NOT NULL );CREATE TABLE:表を作る
CREATE TABLE reviews ( id bigint PRIMARY KEY, user_id bigint REFERENCES users(id) );FOREIGN KEY:外部キー(参照側インデックスは自動作成されない)
CREATE TABLE sessions ( id bigint PRIMARY KEY, user_id bigint REFERENCES users(id) ON DELETE CASCADE );ON DELETE CASCADE:親削除時に子も削除(意図せぬ連鎖削除に注意)
SELECT DATE_TRUNC('month', created_at), COUNT(*) FROM users GROUP BY DATE_TRUNC('month', created_at);DATE_TRUNC:日時を指定単位へ切り捨て(timestamptzではTimeZoneに注意)
UPDATE users SET profile = jsonb_set(profile, '{premium}', 'true'::jsonb) WHERE id = 1;jsonb_set:JSONBの一部を変更(深いパスの途中のキーがない場合等に注意)
CREATE MATERIALIZED VIEW user_totals AS SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id;CREATE MATERIALIZED VIEW:結果を保存するビュー(元表変更だけでは内容が自動更新されない)
BEGIN; SAVEPOINT before_change; UPDATE users SET active = false WHERE id = 1; ROLLBACK TO SAVEPOINT before_change; COMMIT;SAVEPOINT:途中の復帰地点
BEGIN; SELECT * FROM orders WHERE status = 'pending' ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 1; COMMIT;SKIP LOCKED:ロック済み行を飛ばす(ジョブキュー等向け。一般的な整合した一覧取得には不向き)
BEGIN; SET LOCAL lock_timeout = '3s'; SELECT * FROM users WHERE id = 1 FOR UPDATE; COMMIT;SET LOCAL lock_timeout:ロック待機を制限(SET LOCALは現在トランザクション内だけ)
GRANT USAGE ON SCHEMA public TO app_reader; GRANT SELECT ON users TO app_reader;GRANT:権限付与(既存のusers表が対象。将来の表には別設定が必要)
COPY ( SELECT id, name FROM users ) TO '/tmp/users.csv' WITH (FORMAT csv, HEADER true);COPY:サーバー側CSV出力(サーバー上のパス。特別なファイル権限等が必要。クライアントのパスではない)
SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'users' ORDER BY ordinal_position;information_schema:表の構造等を参照(権限で見える範囲が変わる)

ほかの公式パックを見る