PostgreSQL Notes

 

Remarks

본 글은 Learn PostgreSQL Tutorial - Full Course for Beginners (freeCodeCamp.org)을 참고하여 작성되었습니다.


1. Install

1.1 Docker

$ docker pull
$ docker run -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD="1234" --name postgres postgres -v /workspace:/workspace

2. Run

$ docker exec --user='root' -it postgres bash

3. Connect to database

$ su - postgres
$ psql
$ psql test  # connect to 'test' database

4. DDL

Notations

$: command in bash shell
#: command in postgres shell
CAPITAL: reserved words

Create database

CREATE DATABASE test;

Remove database

DROP DATABASE test;

List databases

$ psql -l
# \l

Connect to another database

# \c test

Create table

CREATE TABLE table_name (
  column name + data type + constraints if any
)
CREATE TABLE person (
  id int,
  first_name VARCHAR(50),
  last_name VARCHAR(50),
  gender VARCHAR(7),
  date_of_birth DATE
);

CREATE TABLE person (
  id BIGSERIAL NOT NULL PRIMARY KEY,  /* BIGSERIAL: autoincrementing 8 bytes integer */
  first_name VARCHAR(50) NOT NULL,
  last_name VARCHAR(50) NOT NULL,
  gender VARCHAR(7) NOT NULL,
  date_of_birth DATE NOT NULL,
  email VARCHAR(150)
);

Delete table

# DROP TABLE test;

List tables

# \d   /* with sequence */
# \dt  /* without sequence */

Describe table

# \d person

Foreign keys

Alt text

CREATE TABLE car(
    id BIGSERIAL NOT NULL PRIMARY KEY,
    make VARCHAR(100) NOT NULL,
    model VARCHAR(100) NOT NULL,
    price NUMERIC(19, 2) NOT NULL
);

CREATE TABLE person (
    id BIGSERIAL NOT NULL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    gender VARCHAR(7) NOT NULL,
    email VARCHAR(100),
    date_of_birth DATE NOT NULL,
    country_of_birth VARCHAR(50) NOT NULL,
    car_id BIGINT REFERENCES car (id) UNIQUE
);

insert into car (id, make, model, price) values (1, 'Ford', 'Econoline E150', '11479.96');
insert into car (id, make, model, price) values (2, 'Mazda', 'MX-6', '58238.86');

insert into person (first_name, last_name, email, gender, date_of_birth, country_of_birth) values ('Marina', 'D''Ugo', 'mdugo0@github.com', 'Female', '9/3/2023', 'Burundi');
insert into person (first_name, last_name, email, gender, date_of_birth, country_of_birth) values ('Flori', 'Delaney', 'fdelaney1@themeforest.net', 'Female', '3/2/2023', 'Poland');
insert into person (first_name, last_name, email, gender, date_of_birth, country_of_birth) values ('Hildy', 'Heister', 'hheister2@shareasale.com', 'Female', '7/27/2023', 'Brazil');
test=# UPDATE person SET car_id = 3 WHERE id = 2;
ERROR:  insert or update on table "person" violates foreign key constraint "person_car_id_fkey"
DETAIL:  Key (car_id)=(3) is not present in table "car".
test=# DELETE FROM car WHERE id = 1;
ERROR:  update or delete on table "car" violates foreign key constraint "person_car_id_fkey" on table "person"
DETAIL:  Key (id)=(1) is still referenced from table "person".

On delete cascade

CREATE TABLE car(
    car_uid UUID NOT NULL PRIMARY KEY,
    make VARCHAR(100) NOT NULL,
    model VARCHAR(100) NOT NULL,
    price NUMERIC(19, 2) NOT NULL CHECK (price > 0)
);

CREATE TABLE person (
    person_uid UUID NOT NULL PRIMARY KEY,
    first_name VARCHAR(50) NOT NULL,
    last_name VARCHAR(50) NOT NULL,
    gender VARCHAR(7) NOT NULL,
    email VARCHAR(100) UNIQUE,
    date_of_birth DATE NOT NULL,
    country_of_birth VARCHAR(50) NOT NULL,
    car_uid UUID REFERENCES car (car_uid) ON DELETE CASCADE
);

Constraints

Setting primary key when creating table

CREATE TABLE person (
  id BIGSERIAL NOT NULL PRIMARY KEY
)

Add primary key to existing table

ALTER TABLE person ADD PRIMARY KEY (id);

Alter primary key constraints

test=# \d person
                                        Table "public.person"
      Column      |         Type          | Collation | Nullable |              Default
------------------+-----------------------+-----------+----------+------------------------------------
 id               | bigint                |           | not null | nextval('person_id_seq'::regclass)
Indexes:
    "person_pkey" PRIMARY KEY, btree (id)

test=# ALTER TABLE person DROP CONSTRAINT person_pkey;
ALTER TABLE

test=# \d person
                                        Table "public.person"
      Column      |         Type          | Collation | Nullable |              Default
------------------+-----------------------+-----------+----------+------------------------------------
 id               | bigint                |           | not null | nextval('person_id_seq'::regclass)

Alter unique constraint

test=# ALTER TABLE person ADD CONSTRAINT person_email_key 
UNIQUE (email);
ALTER TABLE

test=# ALTER TABLE person ADD UNIQUE (email);
ALTER TABLE

test=# \d person
                                        Table "public.person"
      Column      |         Type          | Collation | Nullable |              Default
------------------+-----------------------+-----------+----------+------------------------------------
 id               | bigint                |           | not null | nextval('person_id_seq'::regclass)
 email            | character varying(50) |           |          |
Indexes:
    "person_pkey" PRIMARY KEY, btree (id)
    "unique_email_address" UNIQUE CONSTRAINT, btree (email)

test=# ALTER TABLE person DROP CONSTRAINT unique_email_address;
ALTER TABLE

test=# \d person
id               | bigint                |           | not null | nextval('person_id_seq'::regclass)
 email            | character varying(50) |           |          |
Indexes:
    "person_pkey" PRIMARY KEY, btree (id)

Check constraints

test=# CREATE TABLE car(
    id BIGSERIAL NOT NULL PRIMARY KEY,
    price NUMERIC(19, 2) NOT NULL CHECK (price > 0)
);

test=# ALTER TABLE person ADD CONSTRAINT gender_constraint CHECK (gender = 'Female' OR gender = 'Male');
ALTER TABLE

test=# \d person
                                        Table "public.person"
      Column      |         Type          | Collation | Nullable |              Default
------------------+-----------------------+-----------+----------+------------------------------------
 id               | bigint                |           | not null | nextval('person_id_seq'::regclass)
 email            | character varying(50) |           |          |
 gender           | character varying(50) |           |          |
Indexes:
    "person_pkey" PRIMARY KEY, btree (id)
    "person_email_key" UNIQUE CONSTRAINT, btree (email)
Check constraints:
    "gender_constraint" CHECK (gender::text = 'Female'::text OR gender::text = 'Male'::text)

Insert

Insert into table

INSERT INTO person (
  first_name,
  last_name,
  gender,
  date_of_birth)
VALUES ('Anne', 'Smith', 'FEMALE', DATE '1988-01-09');

INSERT INTO person (
  first_name,
  last_name,
  gender,
  date_of_birth,
  email)
VALUES ('Jake', 'Jones', 'MALE', DATE '1990-01-10', 'jake@gmail.com');

On conflict

Do nothing

INSERT INTO person
VALUES (1, 'a', 'b', 'd', 'Male', DATE '1952-09-25', 'e')
ON CONFLICT (id) DO NOTHING;

Do update set

INSERT INTO person
VALUES (1, 'a', 'b', 'd', 'Male', DATE '1952-09-25', 'e')
ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email;

Delete records

DELETE FROM person WHERE id = 1;

Update records

UPDATE person SET first_name = 'Omar', last_name = 'Montana', email = 'omar.montana@hotmail.com' WHERE id = 1;

BIGSERIAL

test=# SELECT * FROM person_id_seq;
 last_value | log_cnt | is_called
------------+---------+-----------
          3 |      30 | t
(1 row)

Export to CSV

test=# \copy (SELECT * FROM person LEFT JOIN car ON car.id = person.car_id) TO '/workspace/sql/results.csv' DELIMITER ',' CSV HEADER;
COPY 3

Data generator (mockaroo)

mockaroo.com

Execute SQL from file

# \i /workspace/sql/person.sql

5. DML

기본적으로는 MySQL query와 유사하므로, MySQL과 비교하여 주요한 특이점을 기술한다.

LIMIT, OFFSET, FETCH

SELECT * FROM table LIMIT 5;  /* ID: [1, 5] */
SELECT * FROM table FETCH FIRST 5 ROWS ONLY;  /* ID: [1, 5] */
SELECT * FROM table OFFSET 5 LIMIT 5;  /* ID: [6, 10] */

LIKE

동일하게 wildcard (%, _) 사용가능

COUNT

COUNT(*)  : number of input rows
COUNT(exp): number of input rows which the value of exp IS NOT NULL

COALESCE

Arguments를 받아 not null 값으로 채움

  • MySQL의 IFNULL과 같이 사용할 수 있음 (Oracle: NVL)
    SELECT COALESCE(1) as number;           -- 1
    SELECT COALESCE(1, 2) as number;        -- 1
    SELECT COALESCE(NULL, 2, 1) as number;  -- 2
    SELECT COALESCE(email, 'Email not provided');
    

NULLIF

SELECT NULLIF(a, b);  -- NULL if a == b else a

SELECT 10 / 0;     -- ERROR: division by zero
SELECT 10 / NULL;  -- SUCCESS: NULL
SELECT 10 / NULLIF(2, 5);  -- SUCCESS: 10/2 -> 5

SELECT COALESCE(10 / NULLIF(num, 0), 0);  -- how to handle division by zero

Join

test=# SELECT * FROM person JOIN car ON person.car_id = car.id;
 id | first_name | last_name | gender |           email           | date_of_birth | country_of_birth | car_id | id | make  |     model      |  price
----+------------+-----------+--------+---------------------------+---------------+------------------+--------+----+-------+----------------+----------
  2 | Flori      | Delaney   | Female | fdelaney1@themeforest.net | 2023-03-02    | Poland           |      1 |  1 | Ford  | Econoline E150 | 11479.96
  1 | Marina     | D'Ugo     | Female | mdugo0@github.com         | 2023-09-03    | Burundi          |      2 |  2 | Mazda | MX-6           | 58238.86
(2 rows)
test=# SELECT * FROM person LEFT JOIN car ON person.car_id = car.id;
 id | first_name | last_name | gender |           email           | date_of_birth | country_of_birth | car_id | id | make  |     model      |  price
----+------------+-----------+--------+---------------------------+---------------+------------------+--------+----+-------+----------------+----------
  2 | Flori      | Delaney   | Female | fdelaney1@themeforest.net | 2023-03-02    | Poland           |      1 |  1 | Ford  | Econoline E150 | 11479.96
  1 | Marina     | D'Ugo     | Female | mdugo0@github.com         | 2023-09-03    | Burundi          |      2 |  2 | Mazda | MX-6           | 58238.86
  3 | Hildy      | Heister   | Female | hheister2@shareasale.com  | 2023-07-27    | Brazil           |        |    |       |                |
(3 rows)

test=# SELECT * FROM person JOIN car ON person.car_id = car.id WHERE car.* IS NOT NULL;
test=# SELECT * FROM person FULL JOIN car ON person.car_id = car.id;
 id | first_name | last_name | gender |           email           | date_of_birth | country_of_birth | car_id | id | make  |     model      |  price
----+------------+-----------+--------+---------------------------+---------------+------------------+--------+----+-------+----------------+----------
  2 | Flori      | Delaney   | Female | fdelaney1@themeforest.net | 2023-03-02    | Poland           |      1 |  1 | Ford  | Econoline E150 | 11479.96
  1 | Marina     | D'Ugo     | Female | mdugo0@github.com         | 2023-09-03    | Burundi          |      2 |  2 | Mazda | MX-6           | 58238.86
  3 | Hildy      | Heister   | Female | hheister2@shareasale.com  | 2023-07-27    | Brazil           |        |    |       |                |
(3 rows)

Using

Key의 이름이 동일한 경우 사용가능.

test=# SELECT * FROM person;
              person_uid              | first_name | last_name | gender |           email           | date_of_birth | country_of_birth |               car_uid
--------------------------------------+------------+-----------+--------+---------------------------+---------------+------------------+--------------------------------------
 f3445c81-cf5f-456d-9f86-585ce34e618b | Hildy      | Heister   | Female | hheister2@shareasale.com  | 2023-07-27    | Brazil           |
 ca77b3e2-8395-40df-9cdb-a937c876a06b | Marina     | D'Ugo     | Female | mdugo0@github.com         | 2023-09-03    | Burundi          | 7065ad29-0b1e-480b-b1bc-bd0793f6566a
 460ac8f6-a8e2-4de9-bac7-a5e5d975170e | Flori      | Delaney   | Female | fdelaney1@themeforest.net | 2023-03-02    | Poland           | 4f27958e-b3cb-4eae-a305-2c61791e63e8
(3 rows)

test=# SELECT * FROM car;
               car_uid                | make  |     model      |  price
--------------------------------------+-------+----------------+----------
 7065ad29-0b1e-480b-b1bc-bd0793f6566a | Ford  | Econoline E150 | 11479.96
 4f27958e-b3cb-4eae-a305-2c61791e63e8 | Mazda | MX-6           | 58238.86
(2 rows)
test=# SELECT * FROM person JOIN car USING (car_uid);
               car_uid                |              person_uid              | first_name | last_name | gender |           email           | date_of_birth | country_of_birth | make  |     model      |  price
--------------------------------------+--------------------------------------+------------+-----------+--------+---------------------------+---------------+------------------+-------+----------------+----------
 7065ad29-0b1e-480b-b1bc-bd0793f6566a | ca77b3e2-8395-40df-9cdb-a937c876a06b | Marina     | D'Ugo     | Female | mdugo0@github.com         | 2023-09-03    | Burundi          | Ford  | Econoline E150 | 11479.96
 4f27958e-b3cb-4eae-a305-2c61791e63e8 | 460ac8f6-a8e2-4de9-bac7-a5e5d975170e | Flori      | Delaney   | Female | fdelaney1@themeforest.net | 2023-03-02    | Poland           | Mazda | MX-6           | 58238.86
(2 rows)

DATE

NOW()

test=# SELECT NOW();
              now
-------------------------------
 2023-10-11 09:40:23.326007+00
(1 row)
test=# SELECT NOW()::DATE;
    now
------------
 2023-10-11
(1 row)
test=# SELECT NOW()::TIME;
      now
----------------
 09:42:04.97737
(1 row)

INTERVAL

test=# SELECT NOW() - INTERVAL '10 YEARS';
           ?column?
-------------------------------
 2013-10-11 09:45:41.177401+00
(1 row)

test=# SELECT NOW() - INTERVAL '10 MONTHS';
           ?column?
-------------------------------
 2022-12-11 09:46:03.224482+00
(1 row)

test=# SELECT NOW() - INTERVAL '10 DAYS';
           ?column?
-------------------------------
 2023-10-01 09:46:09.939125+00
(1 row)

test=# SELECT (NOW()::DATE + INTERVAL '10 MONTHS')::DATE;
    date
------------
 2024-08-11
(1 row)

Extract fields

test=# SELECT EXTRACT(MONTH FROM NOW()); extract ——— 10 (1 row)

test=# SELECT EXTRACT(DAY FROM NOW()); extract ——— 11 (1 row)

test=# SELECT EXTRACT(HOUR FROM NOW()); extract ——— 9 (1 row)

test=# SELECT EXTRACT(MINUTE FROM NOW()); extract ——— 49 (1 row)

test=# SELECT EXTRACT(SECOND FROM NOW()); extract ———- 9.058259 (1 row)

test=# SELECT EXTRACT(DOW FROM NOW()); – 0(Sunday) to 6(Saturday) extract ——— 3 (1 row)

test=# SELECT EXTRACT(CENTURY FROM NOW()); extract ——— 21 (1 row)


## Age

test=# SELECT first_name, date_of_birth, AGE(NOW(), date_of_birth) age FROM person; first_name | date_of_birth | age —————-+—————+——————————– Marina | 2023-09-03 | 1 mon 8 days 09:59:09.89202 Flori | 2023-03-02 | 7 mons 9 days 09:59:09.89202 Hildy | 2023-07-27 | 2 mons 15 days 09:59:09.89202



# 6. Extensions

test=# SELECT * FROM pg_available_extensions; name | default_version | installed_version | comment ——————–+—————–+——————-+———————————————————————— pg_freespacemap | 1.2 | | examine the free space map (FSM) pg_walinspect | 1.1 | | functions to inspect contents of PostgreSQL Write-Ahead Log moddatetime | 1.0 | | functions for tracking last modification time adminpack | 2.1 | | administrative functions for PostgreSQL isn | 1.2 | | data types for international product numbering standards refint | 1.0 | | functions for implementing referential integrity (obsolete)


## UUID(Universally Unique IDentifier) data type
### UUID function
[https://www.postgresql.org/docs/current/uuid-ossp.html#UUID-OSSP-FUNCTIONS-SECT](https://www.postgresql.org/docs/current/uuid-ossp.html#UUID-OSSP-FUNCTIONS-SECT)

test=# CREATE EXTENSION IF NOT EXISTS “uuid-ossp”; CREATE EXTENSION

test=# \df List of functions Schema | Name | Result data type | Argument data types | Type ——–+——————–+——————+—————————+—— public | uuid_generate_v1 | uuid | | func public | uuid_generate_v1mc | uuid | | func public | uuid_generate_v3 | uuid | namespace uuid, name text | func public | uuid_generate_v4 | uuid | | func public | uuid_generate_v5 | uuid | namespace uuid, name text | func public | uuid_nil | uuid | | func public | uuid_ns_dns | uuid | | func public | uuid_ns_oid | uuid | | func public | uuid_ns_url | uuid | | func public | uuid_ns_x500 | uuid | | func (10 rows)

test=# SELECT uuid_generate_v4(); uuid_generate_v4 ————————————– d2acadb9-f68e-415f-9276-361cea7d9cb5 (1 row)


### UUID as primary keys

CREATE TABLE car( car_uid UUID NOT NULL PRIMARY KEY, make VARCHAR(100) NOT NULL, model VARCHAR(100) NOT NULL, price NUMERIC(19, 2) NOT NULL CHECK (price > 0) );

CREATE TABLE person ( person_uid UUID NOT NULL PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, gender VARCHAR(7) NOT NULL, email VARCHAR(100) UNIQUE, date_of_birth DATE NOT NULL, country_of_birth VARCHAR(50) NOT NULL, car_uid UUID REFERENCES car (car_uid) UNIQUE );

insert into car (car_uid, make, model, price) values (uuid_generate_v4(), ‘Ford’, ‘Econoline E150’, ‘11479.96’); insert into car (car_uid, make, model, price) values (uuid_generate_v4(), ‘Mazda’, ‘MX-6’, ‘58238.86’);

insert into person (person_uid, first_name, last_name, email, gender, date_of_birth, country_of_birth) values (uuid_generate_v4(), ‘Marina’, ‘D’‘Ugo’, ‘mdugo0@github.com’, ‘Female’, ‘9/3/2023’, ‘Burundi’); insert into person (person_uid, first_name, last_name, email, gender, date_of_birth, country_of_birth) values (uuid_generate_v4(), ‘Flori’, ‘Delaney’, ‘fdelaney1@themeforest.net’, ‘Female’, ‘3/2/2023’, ‘Poland’); insert into person (person_uid, first_name, last_name, email, gender, date_of_birth, country_of_birth) values (uuid_generate_v4(), ‘Hildy’, ‘Heister’, ‘hheister2@shareasale.com’, ‘Female’, ‘7/27/2023’, ‘Brazil’);


test=# UPDATE person SET car_uid = ‘7065ad29-0b1e-480b-b1bc-bd0793f6566a’ WHERE person_uid = ‘ca77b3e2-8395-40df-9cdb-a937c876a06b’; UPDATE 1

test=# UPDATE person SET car_uid = ‘4f27958e-b3cb-4eae-a305-2c61791e63e8’ WHERE person_uid = ‘460ac8f6-a8e2-4de9-bac7-a5e5d975170e’; UPDATE 1

test=# SELECT * FROM person; person_uid | first_name | last_name | gender | email | date_of_birth | country_of_birth | car_uid ————————————–+————+———–+——–+—————————+—————+——————+————————————– f3445c81-cf5f-456d-9f86-585ce34e618b | Hildy | Heister | Female | hheister2@shareasale.com | 2023-07-27 | Brazil | ca77b3e2-8395-40df-9cdb-a937c876a06b | Marina | D’Ugo | Female | mdugo0@github.com | 2023-09-03 | Burundi | 7065ad29-0b1e-480b-b1bc-bd0793f6566a 460ac8f6-a8e2-4de9-bac7-a5e5d975170e | Flori | Delaney | Female | fdelaney1@themeforest.net | 2023-03-02 | Poland | 4f27958e-b3cb-4eae-a305-2c61791e63e8 (3 rows)

test=# SELECT * FROM car; car_uid | make | model | price ————————————–+——-+—————-+———- 7065ad29-0b1e-480b-b1bc-bd0793f6566a | Ford | Econoline E150 | 11479.96 4f27958e-b3cb-4eae-a305-2c61791e63e8 | Mazda | MX-6 | 58238.86 (2 rows) ```