개요
지난 시간에는 PostgreSQL의 간략한 개요와 특징에 대해서 알아보았습니다. 이번 시간에는 PostgreSQL에서 제공하는 SQL Commands 들을 알아보고 간략하게 실습해보는 시간을 가져보겠습니다.
테이블 기초
- CREATE: 테이블, 인덱스, 뷰, 시퀀스 등과 같은 새로운 데이터베이스 개체를 만드는 데 사용됩니다.
// 출처: https://www.postgresql.org/docs/current/sql-createtable.html
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [
{ column_name data_type [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION compression_method ] [ COLLATE collation ] [ column_constraint [ ... ] ]
| table_constraint
| LIKE source_table [ like_option ... ] }
[, ... ]
] )
[ INHERITS ( parent_table [, ... ] ) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [, ... ] ) ]
[ USING method ]
[ WITH ( storage_parameter [= value] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE tablespace_name ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name
OF type_name [ (
{ column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ]
[ PARTITION BY { RANGE | LIST | HASH } ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [, ... ] ) ]
[ USING method ]
[ WITH ( storage_parameter [= value] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE tablespace_name ]
CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name
PARTITION OF parent_table [ (
{ column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ]
| table_constraint }
[, ... ]
) ] { FOR VALUES partition_bound_spec | DEFAULT }
[ PARTITION BY { RANGE | LIST | HASH } ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [, ... ] ) ]
[ USING method ]
[ WITH ( storage_parameter [= value] [, ... ] ) | WITHOUT OIDS ]
[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
[ TABLESPACE tablespace_name ]
where column_constraint is:
[ CONSTRAINT constraint_name ]
{ NOT NULL |
NULL |
CHECK ( expression ) [ NO INHERIT ] |
DEFAULT default_expr |
GENERATED ALWAYS AS ( generation_expr ) STORED |
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] index_parameters |
PRIMARY KEY index_parameters |
REFERENCES reftable [ ( refcolumn ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
[ ON DELETE referential_action ] [ ON UPDATE referential_action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
and table_constraint is:
[ CONSTRAINT constraint_name ]
{ CHECK ( expression ) [ NO INHERIT ] |
UNIQUE [ NULLS [ NOT ] DISTINCT ] ( column_name [, ... ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE referential_action ] [ ON UPDATE referential_action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
and like_option is:
{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
and partition_bound_spec is:
IN ( partition_bound_expr [, ...] ) |
FROM ( { partition_bound_expr | MINVALUE | MAXVALUE } [, ...] )
TO ( { partition_bound_expr | MINVALUE | MAXVALUE } [, ...] ) |
WITH ( MODULUS numeric_literal, REMAINDER numeric_literal )
index_parameters in UNIQUE, PRIMARY KEY, and EXCLUDE constraints are:
[ INCLUDE ( column_name [, ... ] ) ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ USING INDEX TABLESPACE tablespace_name ]
exclude_element in an EXCLUDE constraint is:
{ column_name | ( expression ) } [ COLLATE collation ] [ opclass [ ( opclass_parameter = value [, ... ] ) ] ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
referential_action in a FOREIGN KEY/REFERENCES constraint is:
{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( column_name [, ... ] ) ] | SET DEFAULT [ ( column_name [, ... ] ) ] }
- DROP: 생성된 테이블, 인덱스, 뷰, 시퀀스 등을 삭제하는 명령어입니다.
DROP TABLE my_first_table;
DROP TABLE products;
// 만약, 테이블이 존재하지 않는데 제거하려 한다면 에러가 발생합니다.
// 아래 명령어로 에러를 무시할 수 있지만, Standard SQL은 아닙니다
DROP TABLE IF EXISTS products;
Table Default Values
- 만약, 기본 값이 설정되어 있는 컬럼에 어떤 값도 INSERT 되지 않으면, PostgreSQL 에서는 해당 컬럼에 기본 값을 넣습니다.
CREATE TABLE products (
product_no integer,
name text,
price numeric DEFAULT 9.99
);
- 기본 값은 데이터 레코드가 INSERT 될 때마다 계산되어지는 식으로도 구현이 가능합니다. 가장 대표적인 예로는 created_at, updated_at 과 같은 TIMESTAMP 값이 있고, 또다른 예로는 SERIAL NUMBER 입니다.
CREATE TABLE products (
product_no integer DEFAULT nextval('products_product_no_seq'),
...
);
- 위의 표현식은 SERIAL 이라고 하는 데이터 타입으로 대체될 수 있습니다.
Generated Columns
- Generated Columns 는 항상 다른 열에서 계산되는 특수한 열입니다. 테이블에 대한 View로 치부되는 Column 에 대한 값이라고 할 수 있습니다.
- Generated Columns 에는 Stored Column 과 Virtual Column 두 가지가 있습니다.
- Stored Generated Column 은 작성(삽입 또는 업데이트)될 때 계산되며 일반 열인 것처럼 저장소를 차지합니다.
- Virtual Generated Column 은 저장소를 차지하지 않고 읽혀질 때 계산됩니다.
- 따라서 Virtual Generated Column 은 View 와 유사하고 Stored Generated Column 은 Materialized View 와 유사합니다(항상 자동으로 업데이트되는 것을 제외하고).
- 현재 PostgreSQL은 Stored Generated Column 만 지원합니다.
CREATE TABLE people (
...,
height_cm numeric,
height_in numeric GENERATED ALWAYS AS (height_cm / 2.54) STORED
);
// STORED 라는 키워드는 해당 Column이 STORED 인지 VIRTUAL 인지 구분하기 위하여 사용됩니다
Default Value VS Generated Columns
Default Value 가 설정된 Column과 Generated Column의 차이에 대해서 생각해봅시다.
- Column Default Value 는 데이터 레코드가 처음 INSERT 될 때에 유효한 값이 없을 때에만 한번 실행되며, Generated Column 은 데이터 레코드들이 변경될 때마다 업데이트 됩니다.
- Column Default Value 는 해당 Column 이외의 다른 Column 들에게 영향을 받지 않습니다. 하지만 Generated Column 은 일반적으로 그렇습니다.
- Column Default value 에는 휘발성 함수 (random() 혹은 현재 시간 참조 함수)를 사용할 수 있지만 Generated Column 에는 허용되지 않습니다.
Generated Column 과 Generated Column 을 포함하는 테이블 정의에는 다음과 같은 제한 사항들이 적용됩니다.
- 생성 식에는 오직 Immutable Function 만을 사용할 수 있으며, 현재 데이터 레코드 이외의 하위 쿼리를 사용하거나 참조할 수 없습니다.
- 생성 식은 또다른 Generated Column을 참조할 수 없습니다.
- 생성 식은 Tableoid 를 제외하고는 System Column 을 참조할 수 없습니다.
- Generated Column은 기본 값이나 ID 정의를 가질 수 없습니다. (ID 정의란, 시스템에 의해서 계산되는 SERIAL 값을 주로 말합니다)
- Generated Column은 Partition Key 의 일부가 될 수 없습니다.
- 상속 및 분할의 경우
- 부모 열이 Generated Column 인 경우 자식 열도 Generated Column 이어야 하지만 자식 열은 다른 생성 식을 가질 수 있습니다. 행의 삽입 또는 업데이트 중에 실제로 적용되는 생성 식은 행이 물리적으로 있는 테이블과 관련된 식입니다.
- 상위 열이 Generated Column 이 아닌 경우 하위 열도 아니어야 합니다.
- 상속된 테이블의 경우 CREATE TABLE ... INHERITS 에서 GENERATED 구문 없이 자식 열을 정의한다면, 해당 자식 열의 GENERATED 구문은 부모 열에서 자동으로 복사됩니다.
- ALTER TABLE ... INHERITS 에서 GENERATED 상태에 대해서 부모 열과 자식 열이 일치해야 하지만, 생성 식이 일치할 필요는 없습니다.
- 상속이 여러 개인 경우, 하나의 부모 열이 Generated Column 이면 모든 부모 열이 Generated Column 이어야 합니다.
Generated Column 의 추가 고려사항
- Generated Column 은 다른 기본 열과는 다른 액세스 권한을 가지고 있습니다. 따라서 Generated Column 에서는 특정 역할을 읽을 수 있지만 기본 열에서는 읽을 수 없도록 만들 수 있습니다.
- Generated Column 은 BEFORE 트리거가 실행된 후에 개념적으로 업데이트 됩니다. 따라서 BEFORE 트리거에서 기본 열에 대한 변경 사항은 Generated Column 에 반영됩니다. 그러나 반대로 BEFORE 트리거에서는 Generated Column에 접근할 수 없습니다.
Constraints (제약 조건)
Check Constraints
Check Constraint 는 가장 일반적인 제약 조건 유형입니다. 이를 통해 특정 열에 있는 값이 truth-value 식을 만족해야함을 지정할 수 있습니다. 예를 들어, 제품 가격을 양수로 제약하려면 아래와 같이 할 수 있습니다.
CREATE TABLE products (
product_no integer,
name text,
price numeric CHECK (price > 0)
);
물론, 다음과 같이 check 제약 조건에 이름을 붙일 수 있습니다.
CREATE TABLE products (
product_no integer,
name text,
price numeric CONSTRAINT positive_price CHECK (price > 0)
);
또한, check 제약 조건은 여러 열을 참조할 수도 있습니다. 다음 예제는 정가와 할인가를 저장하고 할인가가 정규 가격보다 낮은지 확인하는 예제입니다.
CREATE TABLE products (
product_no integer,
name text,
price numeric CHECK (price > 0),
discounted_price numeric CHECK (discounted_price > 0),
CHECK (price > discounted_price)
);
이전에 나왔던 두 제약 조건은 열 제약 조건인 반면, 세 번째 제약 조건은 하나의 열 정의와 별도로 작성되기 때문에 테이블 제약 조건이라고 말합니다. 열 제약 조건은 테이블 제약 조건으로 작성할 수도 있지만, 열 제약 조건은 그에 첨부된 열만 참조하도록 되어 있기 때문에, 테이블 제약 조건은 열 제약 조건으로 작성할 수 없습니다.
위 예제는 다음과 같이 테이블 제약 조건으로 작성될 수 있습니다.
CREATE TABLE products (
product_no integer,
name text,
price numeric,
CHECK (price > 0),
discounted_price numeric,
CHECK (discounted_price > 0),
CHECK (price > discounted_price)
);
// 혹은
CREATE TABLE products (
product_no integer,
name text,
price numeric CHECK (price > 0),
discounted_price numeric,
CHECK (discounted_price > 0 AND price > discounted_price)
);
Not-Null Constraints
Not-Null 제약 조건은 단순하게 한 열에 대해서 null 값을 부여할 수 없다는 것입니다.
CREATE TABLE products (
product_no integer NOT NULL,
name text NOT NULL,
price numeric
);
Not-Null 제약 조건은 오로지 열의 제약 조건으로써 작성됩니다. Not-Null 제약 조건은 기능적으로 Null 값과 비교하는 Check 제약 조건과 동일합니다.하지만, PostgreSQL 에서는 명시적인 Not-Null 제약 조건을 생성하는 것이 더 효율적입니다. 단점이라고 한다면 이렇게 생성된 Not-Null 제약 조건에 명시적인 이름을 부여할 수 없다는 것입니다.
Unique Constraints
Unique Constraint 제약 조건은 하나 혹은 여러 열에 속한 데이터 집합이 해당 테이블에서 유일무이한 값이라는 것을 보증합니다.
// Column Target Constraint
CREATE TABLE products (
product_no integer UNIQUE,
name text,
price numeric
);
// Table Target Constraint
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE (product_no)
);
// Group Unique Constraint
CREATE TABLE example (
a integer,
b integer,
c integer,
UNIQUE (a, c)
);
일반적으로 테이블에 제약 조건에 포함된 모든 열의 값이 동일한 행이 둘 이상 있으면 고유 제약 조건에 위반됩니다. 기본적으로 두 개의 널 값은 이 비교에서 동일한 것으로 간주되지 않습니다. 이는 고유 제약 조건이 있는 경우에도 제약 열 중 적어도 하나에 널 값을 포함하는 중복 행을 저장할 수 있음을 의미합니다. 이는 NULLS NOT DISTINCT 옵션으로 제어 가능합니다.
CREATE TABLE products (
product_no integer UNIQUE NULLS NOT DISTINCT,
name text,
price numeric
);
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE NULLS NOT DISTINCT (product_no)
);
Primary Keys
기본 키 제약 조건은 열 또는 열 그룹이 테이블의 행에 대한 고유 식별자로 사용될 수 있음을 나타냅니다. 이를 위해서는 값이 null 이 아닌 고유해야 합니다. 따라서 다음 두 테이블 정의는 동일한 데이터를 받아들입니다.
CREATE TABLE products (
product_no integer UNIQUE NOT NULL,
name text,
price numeric
);
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
// 기본 키 집합
CREATE TABLE example (
a integer,
b integer,
c integer,
PRIMARY KEY (a, c)
);
Foreign Keys
외래 키 제약 조건은 열 또는 열 그룹의 값이 다른 테이블의 일부 행에 나타나는 값과 일치해야 함을 지정합니다. 이 제약 조건은 관련된 두 테이블 사이의 참조 무결성을 유지한다고 말합니다.
계속 사용했던 products 테이블을 참고해보겠습니다.
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
또한 해당 제품의 주문을 저장하는 테이블이 있다고 가정 해보겠습니다. 주문 테이블에는 실제로 존재하는 제품의 주문만 포함되어 있는지 확인하고자 합니다. 따라서 주문 테이블에서 제품 테이블을 참조하는 외부 키 제약 조건을 정의합니다.
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products (product_no),
quantity integer
);
// 축약어 (이 버전에서는 자동으로 참조 테이블의 PK를 FK로 지정해줍니다)
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products,
quantity integer
);
일반적으로, 외래 키는 테이블 제약 형식으로 작성됩니다.
CREATE TABLE t1 (
a integer PRIMARY KEY,
b integer,
c integer,
FOREIGN KEY (b, c) REFERENCES other_table (c1, c2)
);
때로, 자기 참조 테이블이 필요할 때가 있습니다. 그럴 때는 이렇게 표기합니다.
CREATE TABLE tree (
node_id integer PRIMARY KEY,
parent_id integer REFERENCES tree,
name text,
...
);
해당 테이블에서 최상위 노드는 parent_id 가 NULL 이지만, parent_id 가 NULL 이 아닌 항목은 테이블의 유효한 행을 참조하도록 제한됩니다.
테이블은 하나 이상의 외부 키 제약 조건을 가질 수 있습니다. 이는 테이블 간의 다대다 관계를 구현하는 데 유용합니다. 제품 및 주문에 대한 테이블이 있지만 지금은 하나의 주문에 제품이 많이 포함되도록 허용하려고 합니다.
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
CREATE TABLE orders (
order_id integer PRIMARY KEY,
shipping_address text,
...
);
CREATE TABLE order_items (
product_no integer REFERENCES products,
order_id integer REFERENCES orders,
quantity integer,
PRIMARY KEY (product_no, order_id)
);
위의 구문에서는 외부 키로 인해서 어떤 제품과도 관련이 없는 주문을 만들 수 없습니다. 하지만 주문이 생성된 후 해당 제품을 참조하여 제거할 필요성이 있을 수 있는데, 다음과 같은 옵션들을 지원합니다.
1. 참조된 제품의 삭제를 허가하지 않는다. (RESTRICT)
2. 참조된 제품 삭제 시, 주문도 같이 삭제 (CASCADE)
3. 참조된 제품 삭제 시, 아무 것도 하지 않는다. (NO ACTION)
4. 참조된 제품 삭제 시, 참조 키를 설정한다 (SET NULL, SET DEFAULT)
CREATE TABLE tenants (
tenant_id integer PRIMARY KEY
);
CREATE TABLE users (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
user_id integer NOT NULL,
PRIMARY KEY (tenant_id, user_id)
);
CREATE TABLE posts (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
post_id integer NOT NULL,
author_id integer,
PRIMARY KEY (tenant_id, post_id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
);
ON DELETE 와 유사하게 참조 열이 변경될 때 호출되는 ON UPDATE도 있습니다.
Modifying Tables
Adding a Column
ALTER TABLE products ADD COLUMN description text;
열을 추가하면서 제약 조건 또한 같이 추가할 수 있습니다.
ALTER TABLE products ADD COLUMN description text CHECK (description <> '');
Removing a Column
열 삭제는 다음과 같이 실행시킬 수 있습니다.
ALTER TABLE products DROP COLUMN description;
테이블 제약 조건과 관련된 열이 삭제된다면, 그 제약 조건 또한 같이 삭제됩니다. 하지만 그 열이 FK 키 제약 조건과 연관된 값이라면, PostgreSQL 에서는 해당 값을 삭제시키지 않습니다.CASCADE 옵션을 통해 해당 열과 관련된 모든 값들을 DROP 시킬 수 있습니다.
ALTER TABLE products DROP COLUMN description CASCADE;
Adding a Constraint
테이블 제약 조건 문법이 사용됩니다.
ALTER TABLE products ADD CHECK (name <> '');
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;
Removing a Constraint
제약 조건을 삭제하기 위해서는 해당 제약 조건의 이름을 알고 있어야 합니다. 만약, 생성 시에 명시적으로 이름을 선언해주지 않았다면, 시스템이 할당해주었을 것이고 그것을 찾아내야 합니다.
ALTER TABLE products DROP CONSTRAINT some_name;
Changing a Column's Default Value
ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;
// default value 옵션 자체를 제거
ALTER TABLE products ALTER COLUMN price DROP DEFAULT;
Changing a Column's Data Type
ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);
위 명령은 해당 열의 기존 값들이 새로운 타입으로 전부 변환가능한 값들이어야지 성공합니다. 만약, 변환되지 않는 값들이 있다면, USING 구문을 통해 OLD 값이 어떻게 NEW 값으로 계산되어야 하는 가를 정의할 수 있습니다.
Renaming a Column
ALTER TABLE products RENAME COLUMN product_no TO product_number;
Renaming a Table
ALTER TABLE products RENAME TO items;
Schema
데이터베이스에는 이름이 지정된 스키마가 하나 이상 포함되며, 이 스키마는 테이블들을 포함합니다. 스키마는 데이터 유형, 함수 및 연산자를 포함한 다른 종류의 이름이 지정된 개체도 포함합니다. 같은 개체 이름을 다른 스키마에서 충돌 없이 사용할 수 있습니다. PostgreSQL 에서는 기본 스키마로 PUBLIC 이라는 이름을 가진 스키마를 제공합니다.
Why Schema?
- 여러 사용자가 서로 간섭하지 않고 하나의 데이터베이스를 사용할 수 있도록 합니다.
- 데이터베이스 개체를 논리적 그룹으로 구성하여 보다 쉽게 관리할 수 있도록 합니다.
- 타사 응용 프로그램은 다른 개체의 이름과 충돌하지 않도록 별도의 스키마에 넣을 수 있습니다.
- 스키마는 중첩할 수 없다는 점을 제외하고는 운영 체제 수준의 디렉토리와 유사합니다.
Inheritance (상속)
개요
다음과 같은 부모 테이블과 자식 테이블이 있다고 가정합니다.
CREATE TABLE cities (
name text,
population float,
elevation int -- in feet
);
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);
Capitals 테이블은 state 이외에 name, population, elevation 이라는 열을 추가로 가지게 됩니다. 또한, 쿼리 과정에서 테이블의 모든 행 또는 테이블의 모든 행과 그 하위 테이블을 참조할 수 있습니다. 다음 쿼리는 500 피트 이상의 고도에 포함된 모든 도시를 찾는 쿼리문 입니다.
SELECT name, elevation
FROM cities
WHERE elevation > 500;
name | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
하지만, 만약 수도가 아닌 도시 중에서 500피트 이상의 고도를 가진 도시들을 찾고 싶다면 다음과 같이 쿼리가 가능합니다.
SELECT name, elevation
FROM ONLY cities
WHERE elevation > 500;
name | elevation
-----------+-----------
Las Vegas | 2174
Mariposa | 1953
여기에서 ONLY 키워드는 쿼리가 상속 계층의 하위에 있는 테이블이 아니라 현재 테이블에만 적용되어야 한다는 것을 뜻합니다. 기본적인 DML 명령문들이 ONLY 키워드를 지원합니다.테이블 이름에 SUBFIX로 * 기호를 붙여 하위 테이블들이 포함되도록 명시적으로 지정할 수도 있습니다.
SELECT name, elevation
FROM cities*
WHERE elevation > 500;
만약 특정 행이 어느 테이블에서 나온 값인지를 알고 싶을 수도 있습니다. 각 테이블에는 TableOid 라는 값이 있어 해당 값을 볼 수 있습니다.
SELECT c.tableoid, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;
tableoid | name | elevation
----------+-----------+-----------
139793 | Las Vegas | 2174 // from cities
139793 | Mariposa | 1953 // from cities
139798 | Madison | 845 // from capitals
'Database' 카테고리의 다른 글
[Database] PostgreSQL Commands - DCL (0) | 2024.07.18 |
---|---|
[Database] PostgreSQL Commands - DML (0) | 2024.07.17 |
[Database] RDBMS - PostgreSQL (0) | 2024.07.10 |
[Database] NoSQL 이란? (0) | 2024.07.10 |
[Database] RDB (0) | 2024.07.04 |