【PostgreSQL】JOINの使い方とイロハ

### JOINとは?
テーブル同士を結合させるときに使用するSQL句

JOINには
– cross join
– inner join
– left join
– right join
– full outer join
がある。

postgres=# CREATE TABLE example1 (
        id integer primary key,
        name text NOT NULL,
        age integer
);
CREATE TABLE                              ^
postgres=# CREATE TABLE example2 (
        id integer primary key,
        user_id integer,
        hobby text,
        favorite_food text,
        FOREIGN KEY (user_id) references example1(id)
);
CREATE TABLE
postgres=# INSERT INTO example1(id, name, age)
VALUES
        (1, '山田太郎', 31),
        (2, '田中一郎', 25),
        (3, '小林幸子', 27);
INSERT 0 3
postgres=# INSERT INTO example2(id, user_id, hobby, favorite_food)
VALUES
        (1, 1, '散歩', 'りんご'),
        (2, 2, '手芸', '秋刀魚'),
        (3, null, 'サッカー', 'ラーメン'),
        (4, null, '映画鑑賞', '寿司');
INSERT 0 4

### CROSS JOIN
考えうる全ての組み合わせを作成
SELECT * FROM example1 CROSS JOIN example2;

### (INNER) JOIN
指定したレコードを軸に結合(一番イメージが近い)
postgres=# SELECT * FROM example1 JOIN example2 ON example2.user_id = example1.id;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+——-+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
2 | 田中一郎 | 25 | 2 | 2 | 手芸 | 秋刀魚
(2 rows)

### LEFT JOIN
joinで指定したテーブルに対応するレコードがない場合でも全て取得
postgres=# SELECT * FROM example1 LEFT JOIN example2 ON example2.user_id = example1.id;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+——-+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
2 | 田中一郎 | 25 | 2 | 2 | 手芸 | 秋刀魚
3 | 小林幸子 | 27 | | | |
(3 rows)

### RIGHT JOIN
left joinの反対
postgres=# SELECT * FROM example1 RIGHT JOIN example2 ON example2.user_id = example1.id;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+———-+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
2 | 田中一郎 | 25 | 2 | 2 | 手芸 | 秋刀魚
| | | 3 | | サッカー | ラーメン
| | | 4 | | 映画鑑賞 | 寿司

### FULL OUTER JOIN
left joinとright joinを組み合わせたようなもの
postgres=# SELECT * FROM example1 FULL OUTER JOIN example2 ON example2.user_id = example1.id;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+———-+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
2 | 田中一郎 | 25 | 2 | 2 | 手芸 | 秋刀魚
| | | 3 | | サッカー | ラーメン
| | | 4 | | 映画鑑賞 | 寿司
3 | 小林幸子 | 27 | | | |
(5 rows)

なるほど、、、1対1で対応してないとjoinはselectできないね。

更にデータを追加します。
INSERT INTO example2(id, user_id, hobby, favorite_food)
VALUES
(5, 1, ‘買い物’, ‘マンゴー’),
(6, 2, ‘旅行’, ‘パスタ’);

postgres=# SELECT * FROM example1 JOIN example2 ON example2.user_id = example1.id;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+——–+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
2 | 田中一郎 | 25 | 2 | 2 | 手芸 | 秋刀魚
1 | 山田太郎 | 31 | 5 | 1 | 買い物 | マンゴー
2 | 田中一郎 | 25 | 6 | 2 | 旅行 | パスタ
(4 rows)

joinでwhereをつけられる
postgres=# SELECT * FROM example1 JOIN example2 ON example2.user_id = example1.id WHERE example1.id = 1;
id | name | age | id | user_id | hobby | favorite_food
—-+———-+—–+—-+———+——–+—————
1 | 山田太郎 | 31 | 1 | 1 | 散歩 | りんご
1 | 山田太郎 | 31 | 5 | 1 | 買い物 | マンゴー

なるほど、雰囲気は大体わかりました。
これをtransactionとblockでやりたい

【Python】psycopg2で複数のsql文を同時に実行する

psycopg2 の execute は、commitの前に連続して実行できる。そのため、以下のように、sql文をforループで回すことができる。

import psycopg2
import os
from dotenv import load_dotenv

load_dotenv('../.env')
PSQL_PASSWORD = os.getenv('PSQL_PASSWORD')

connection = psycopg2.connect(
    host='localhost',
    user='postgres',
    password=PSQL_PASSWORD
)
cur = connection.cursor()

sql1 = "CREATE TABLE IF NOT EXISTS person1 ( \
            id SERIAL PRIMARY KEY, \
            name TEXT NOT NULL, \
            data BYTEA \
        );"
sql2 = "CREATE TABLE IF NOT EXISTS person2 ( \
            id SERIAL PRIMARY KEY, \
            name TEXT NOT NULL, \
            data BYTEA \
        );"
sqls = [sql1, sql2]

for sql in sqls:
    cur.execute(sql)

connection.commit()
cur.close()
connection.close()

$ python3 migration.py

# \dt
List of relations
Schema | Name | Type | Owner
——–+———-+——-+———-
public | person1 | table | postgres
public | person2 | table | postgres

なるほどー

【Rust】tokio_postgresの接続を外出しにする

「RustによるWebアプリケーション開発(豊田優貴著)」を読むと、axumのルーティングでwith_state(conn_pool)で接続情報を持たせてますね。まぁそういう方法もありかと思いますが、今回は、関数で呼び出す方法を採用することにした。
参考のソースコード:
https://github.com/kingluo/tokio-postgres-hello-world/blob/master/src/main.rs

async fn psql_connect() -> Result<Client, Box<dyn std::error::Error>>  {
    let (client, connection) = tokio_postgres::connect("host=localhost user=postgres password=hogehoge", NoTls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {}", e);
        }
    });
    Ok(client)
}

async fn check_users()-> Result<(), Box<dyn std::error::Error>>  {
    
    let client = psql_connect().await.unwrap();
    for row in client.query("SELECT id, username, password From users", &[]).await? {
        let id: i32 = row.get(0);
        let name: String = row.get(1);
        let password: String = row.get(2);

        println!("{} {} {}", id, name, password);
    }
    Ok(())
}

PostgreSQL シーケンスの利用方法

### シーケンスとは
シーケンスとは連番を払い出してくれるもの。テーブルのIDなどを挿入する際に使用することができる

### シーケンスの使用例
テーブルのIDにシーケンスを使用する例
(1)シーケンスを作成する
(2)シーケンスをテーブルのIDとして挿入する

### シーケンスは以下のように作成
オプションには一番最初に払い出す番号をしているSTARTオプション(START 10)がある。開始番号を指定しなかった場合は1となる

### (1)シーケンスを作成する
CREATE SEQUENCE test_seq;

### (2)シーケンスの確認
testdb=# \d test_seq;
Sequence “public.test_seq”
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
——–+——-+———+———————+———–+———+——-
bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1

### (3)シーケンスをテーブルのIDとして挿入する
testdb=# CREATE TABLE test_table (id int, name varchar(20));
CREATE TABLE
testdb=# INSERT INTO test_table VALUES (nextval(‘test_seq’), ‘suzuki’);
INSERT 0 1
testdb=# INSERT INTO test_table VALUES (nextval(‘test_seq’), ‘ito’);
INSERT 0 1
testdb=# INSERT INTO test_table VALUES (nextval(‘test_seq’), ‘sato’);
INSERT 0 1
testdb=# SELECT * FROM test_table;
id | name
—-+——–
1 | suzuki
2 | ito
3 | sato
(3 rows)

### (4)シーケンスの確認
testdb=# select currval(‘test_seq’);
currval
———
3
(1 row)

### シーケンスの値をリセット
select setval(‘test_seq’, 1);

なるほど、auto_incrementとは別に値を持つのね

sudo passwd postgres

[vagrant@localhost ~]$ sudo find / -name pg_hba.conf
/var/lib/pgsql/9.6/data/pg_hba.conf
[vagrant@localhost ~]$ sudo cat /var/lib/pgsql/9.6/data/pg_hba.conf
local all all peer
# IPv4 local connections:
host all all 127.0.0.1/32 ident
# IPv6 local connections:
host all all ::1/128 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
#local replication postgres peer
#host replication postgres 127.0.0.1/32 ident
#host replication postgres ::1/128 ident

[vagrant@localhost ~]$ sudo passwd postgres
ユーザー postgres のパスワードを変更。
新しいパスワード:
よくないパスワード: 辞書の単語に基づいています
新しいパスワードを再入力してください:
passwd: 全ての認証トークンが正しく更新できました。
[vagrant@localhost ~]$ su – postgres
パスワード:
-bash-4.1$ psql
psql (9.6.15)
“help” でヘルプを表示します.

postgres=# \q
-bash-4.1$ exit
logout

来た!事前準備にどれだけ時間かかってるんじゃ、という感じ。

postgresをインストールする

sudo yum install https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-6-x86_64/pgdg-centos96-9.6-3.noarch.rpm
sudo yum update
sudo yum install postgresql96 postgresql96-server postgresql96-libs
[vagrant@localhost ~]$ rpm -qa | grep postgres
postgresql96-server-9.6.15-1PGDG.rhel6.x86_64
postgresql96-9.6.15-1PGDG.rhel6.x86_64
postgresql96-libs-9.6.15-1PGDG.rhel6.x86_64

[vagrant@localhost ~]$ service postgresql-9.6 initdb
データベースを初期化中: mkdir: ディレクトリ `/var/lib/pgsql’ を作成できません: 許可がありません
[vagrant@localhost ~]$ sudo service postgresql-9.6 initdb
データベースを初期化中: [ OK ]

[vagrant@localhost ~]$ sudo service postgresql-9.6 start
postgresql-9.6 サービスを開始中: [ OK ]
[vagrant@localhost ~]$ service postgresql-9.6 status
postgresql-9.6 (pid 20665) を実行中…
[vagrant@localhost ~]$ chkconfig postgresql-9.6 –list
postgresql-9.6 0:off 1:off 2:off 3:off 4:off 5:off 6:off
[vagrant@localhost ~]$ chkconfig postgresql-9.6 on
この操作を実行するためには権限が十分ではありません。
[vagrant@localhost ~]$ sudo chkconfig postgresql-9.6 on
[vagrant@localhost ~]$ chkconfig postgresql-9.6 –list
postgresql-9.6 0:off 1:off 2:on 3:on 4:on 5:on 6:off

[vagrant@localhost ~]$ su – postgres
パスワード:
su: パスワードが違います
なにいいいいいいいいいいいい