본문으로 건너뛰기
김신건의 로그

[SQL] Data Types

· 수정 · 📖 약 4분 · 1,399자/단어 #sql #data-types #schema #database
SQL Data Types, SQL 데이터 타입, SQL Types, DATE, TIMESTAMP, JSON, JSONB, VARCHAR, NUMERIC

정의

SQL 의 컬럼은 타입 을 가진다. 가능한 값의 도메인, 가능한 연산, 정렬 순서, 인덱스 종류, 저장 크기가 타입에 따라 결정된다.

같은 의미의 타입도 dialect 마다 이름과 동작이 다르다. 표준 SQL 의 타입을 기준으로, PostgreSQL / MySQL 의 실용 매핑을 정리.

전체 SQL 체계는 SQL 참고.

타입 분류

그룹주요 타입대표 연산
숫자INTEGER, BIGINT, NUMERIC, DOUBLE산술, 집계, 반올림
문자열VARCHAR, TEXT, CHARLIKE, 정규식, 연결, 추출
부울BOOLEANAND/OR/NOT
날짜·시간DATE, TIME, TIMESTAMP, TIMESTAMPTZ, INTERVAL차이, 추출, 절단
이진BYTEA (PG), BLOB (MySQL)길이, 해시, 인코딩
UUIDUUID (PG), BINARY(16) / CHAR(36) (MySQL)생성, 비교
화폐NUMERIC(p, s), MONEY (PG)정밀 산술
JSONJSON, JSONB (PG)경로 추출, containment
배열INTEGER[], TEXT[] (PG)슬라이싱, ANY/ALL, unnest
범위INT4RANGE, TSRANGE (PG)포함, 겹침
공간GEOMETRY, GEOGRAPHY (PostGIS)거리, 포함, 교차
열거ENUM (PG/MySQL)정렬, 비교

숫자 타입

표준 / PGMySQL크기범위
SMALLINTSMALLINT2 B-32,768 ~ 32,767
INTEGER / INTINT4 B-2³¹ ~ 2³¹ - 1
BIGINTBIGINT8 B-2⁶³ ~ 2⁶³ - 1
NUMERIC(p, s) / DECIMALDECIMAL(p, s)가변정밀 (p 자릿수, s 소수점)
REAL / FLOAT4FLOAT4 B단정밀 부동소수
DOUBLE PRECISIONDOUBLE8 B배정밀 부동소수

NUMERIC vs DOUBLE

-- 화폐, 회계 → NUMERIC (정밀, 느림)
CREATE TABLE invoices (total NUMERIC(12, 2));
INSERT INTO invoices VALUES (10.10), (20.20);
SELECT SUM(total) FROM invoices;        -- 정확히 30.30

-- 과학 계산, 통계 → DOUBLE (빠름, 부동소수 오차 OK)
CREATE TABLE measurements (val DOUBLE PRECISION);
-- 0.1 + 0.2 = 0.30000000000000004

IMPORTANT

돈에 FLOAT/DOUBLE 절대 금지. 누적 오차 발생.

자주 쓰는 연산

SELECT ABS(-3),                              -- 3
       CEIL(2.3), FLOOR(2.7),                -- 3, 2
       ROUND(2.345, 2),                      -- 2.35
       TRUNC(2.99),                          -- 2 (PG)
       MOD(10, 3),    10 % 3,                -- 1
       POWER(2, 10),                         -- 1024
       SQRT(16), LN(2.718), LOG(10, 1000),
       RANDOM();                             -- PG: 0~1 / MySQL: RAND()

집계 함수는 SQL GROUP BY 참고.

문자열 타입

표준PostgreSQLMySQL비고
CHARACTER(n)CHAR(n)CHAR(n)고정, 짧으면 공백 패딩
CHARACTER VARYING(n)VARCHAR(n)VARCHAR(n)가변
CHARACTER LARGE OBJECTTEXTTEXT, LONGTEXT사실상 무제한

PostgreSQL 에선 VARCHAR, TEXT, CHAR 내부 성능 동일, 길이 제약만 차이. MySQL 에선 VARCHAR 가 행 안에 저장, TEXT 는 별도 저장 영역, 성능 차이 있음.

자주 쓰는 연산

-- 길이
SELECT LENGTH('hello');                        -- 5 (PG, 문자 수)
SELECT CHAR_LENGTH('hello'), OCTET_LENGTH('한');  -- 글자 수 vs 바이트 수

-- 연결
SELECT 'Hello, ' || 'World';                   -- PG, MySQL (ANSI_MODE)
SELECT CONCAT('Hello, ', 'World');             -- 양쪽 모두

-- 자르기
SELECT SUBSTRING('hello world' FROM 7 FOR 5);  -- 'world'
SELECT SUBSTR('hello world', 7, 5);            -- 같음

-- 변환
SELECT UPPER('abc'), LOWER('ABC'), INITCAP('hello world');     -- PG
SELECT REPLACE('a-b-c', '-', '_');             -- 'a_b_c'

-- 트림
SELECT TRIM('  hello  '), LTRIM('  hello'), RTRIM('hello  ');

-- 패딩
SELECT LPAD('7', 3, '0');                      -- '007'

LIKE vs ILIKE vs 정규식

연산의미dialect
LIKE '...'%, _ 와일드카드표준
ILIKE '...'LIKE + 대소문자 무시PostgreSQL
~, ~*정규식 (대소문자 / 무시)PostgreSQL
REGEXP_MATCHES, REGEXP_REPLACE정규식 함수PostgreSQL
REGEXP / RLIKE정규식MySQL
REGEXP_LIKE, REGEXP_SUBSTR정규식 함수MySQL 8+
SELECT * FROM users WHERE name LIKE 'A%';                       -- 표준
SELECT * FROM users WHERE name ILIKE 'a%';                      -- PG
SELECT * FROM users WHERE name ~* '^a';                         -- PG 정규식
SELECT * FROM users WHERE name REGEXP '^A';                     -- MySQL

부울

-- PostgreSQL
CREATE TABLE flags (active BOOLEAN NOT NULL DEFAULT FALSE);
INSERT INTO flags VALUES (TRUE), (FALSE), (NULL);
SELECT * FROM flags WHERE active;     -- TRUE 만, NULL 제외

-- MySQL
-- BOOLEAN ≡ TINYINT(1), TRUE=1, FALSE=0
CREATE TABLE flags (active TINYINT(1) NOT NULL DEFAULT 0);
SELECT * FROM flags WHERE active = 1;

날짜·시간

가장 함정이 많은 타입. 표준 + dialect 차이가 크다.

표준 / PGMySQL의미
DATEDATE’YYYY-MM-DD’
TIMETIME’HH:MM:SS’ (PG: 6 자리 마이크로초)
TIMESTAMPDATETIME시각 (시간대 없음)
TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ)TIMESTAMP시각 + UTC 변환 저장
INTERVAL(없음)기간 (예: INTERVAL '1 day')

MySQL 의 TIMESTAMP

  • 범위 1970-2038 (32-bit Unix time)
  • INSERT 시 세션 timezone 으로 해석 → UTC 저장 → SELECT 시 다시 세션 timezone
  • DATETIME 은 timezone 없음, 그대로 저장

PostgreSQL 의 TIMESTAMPTZ

  • 항상 UTC 로 저장
  • 표시는 세션 timezone (SET TIME ZONE 'Asia/Seoul';)
  • 권장: 시각은 항상 TIMESTAMPTZ

현재 시각

SELECT NOW();                          -- PG: 트랜잭션 시작 시점
SELECT CURRENT_TIMESTAMP;              -- 표준
SELECT CURRENT_DATE, CURRENT_TIME;
SELECT CLOCK_TIMESTAMP();              -- PG: 진짜 wall clock
SELECT STATEMENT_TIMESTAMP();          -- PG: statement 시작 시점

MySQL: NOW(), CURRENT_TIMESTAMP(), SYSDATE() (실시간), UTC_TIMESTAMP().

산술

-- 날짜 + 기간
SELECT DATE '2024-01-15' + INTERVAL '7 day';        -- 2024-01-22
SELECT DATE '2024-01-15' - INTERVAL '1 month';      -- 2023-12-15
SELECT TIMESTAMPTZ '2024-01-15 10:00+09' + INTERVAL '3 hour';

-- 두 날짜 차이
SELECT DATE '2024-12-31' - DATE '2024-01-01';       -- 365 (일 수)
SELECT AGE(TIMESTAMP '2024-12-31', TIMESTAMP '2024-01-01');
-- → '11 mons 30 days'

-- 추출
SELECT EXTRACT(YEAR  FROM created_at),
     EXTRACT(MONTH FROM created_at),
     EXTRACT(DOW   FROM created_at);     -- 일요일=0

-- 절단 (truncate)
SELECT DATE_TRUNC('month', created_at);    -- 월의 1일 00:00
SELECT DATE_TRUNC('day', created_at);
비고
-- PostgreSQL 의 EXTRACT 와 INTERVAL 표준 SQL 에 충실
-- MySQL 은 함수 기반 API
-- DAYOFWEEK 시작 요일이 dialect 마다 다름!
--   PG: 0=일 1=월 ... 6=토
--   MySQL: 1=일 2=월 ... 7=토

시간대 함정

-- PG, TIMESTAMP (timezone 없음) 에 TIMESTAMPTZ 를 넣으면 세션 timezone 으로 변환되어 저장
INSERT INTO logs (ts) VALUES ('2024-01-15 10:00 UTC');
-- 세션이 Asia/Seoul 이면 '2024-01-15 19:00' 으로 저장 (KST)

-- 권장: 항상 TIMESTAMPTZ
ALTER TABLE logs ALTER COLUMN ts TYPE TIMESTAMPTZ USING ts AT TIME ZONE 'UTC';

JSON

웹 백엔드 어디서나 만난다. 인덱싱 가능 여부가 dialect 의 가장 큰 차이.

PostgreSQLMySQL
타입JSON (텍스트), JSONB (바이너리)JSON (바이너리만)
인덱싱JSONB GIN, expression 인덱스generated column + 인덱스
containment @>✗ (대안: JSON_CONTAINS)
경로 추출->, ->>, #>, #>>->, ->>, JSON_EXTRACT
CREATE TABLE events (id SERIAL PK, payload JSONB);

INSERT INTO events (payload) VALUES
('{"user": 42, "tags": ["a","b"], "meta": {"ip": "1.2.3.4"}}'::jsonb);

-- 경로 추출
SELECT payload -> 'user'        FROM events;     -- 42 (jsonb)
SELECT payload ->> 'user'       FROM events;     -- '42' (text)
SELECT payload #> '{meta, ip}'  FROM events;     -- 깊은 경로
SELECT payload #>> '{meta, ip}' FROM events;     -- '1.2.3.4' (text)

-- 포함 (containment)
SELECT * FROM events WHERE payload @> '{"user": 42}';

-- 인덱스
CREATE INDEX idx_events_payload ON events USING GIN (payload);
CREATE INDEX idx_events_user ON events ((payload ->> 'user'));
비고
-- PG 의 JSONB 는 인덱싱과 containment 가 강력
-- MySQL 의 JSON 은 generated column 트릭이 필요
-- JSON 에 무거운 쿼리를 자주 돌리면 별도 컬럼으로 정규화 검토

JSON 의 tags 배열을 펼쳐 행으로:

-- PostgreSQL
SELECT id, tag
FROM events, jsonb_array_elements_text(payload -> 'tags') AS tag;

-- MySQL 8+
SELECT id, jt.tag
FROM events,
JSON_TABLE(payload, '$.tags[*]' COLUMNS (tag VARCHAR(50) PATH '$')) AS jt;

Pandas explode 와 같은 개념.

배열 (PostgreSQL)

CREATE TABLE posts (id SERIAL PK, tags TEXT[]);

INSERT INTO posts (tags) VALUES (ARRAY['db', 'sql']), ('{"db","cache"}');

SELECT * FROM posts WHERE tags @> ARRAY['db'];                    -- 포함
SELECT * FROM posts WHERE 'sql' = ANY(tags);                       -- 멤버
SELECT array_length(tags, 1) FROM posts;                           -- 길이
SELECT id, t FROM posts, unnest(tags) AS t;                        -- 펼침

CREATE INDEX idx_posts_tags ON posts USING GIN (tags);

MySQL 에는 배열 타입이 없다, 보통 JSON 으로 흉내.

UUID

-- PostgreSQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE users (id UUID PRIMARY KEY DEFAULT uuid_generate_v4());

-- MySQL 8+
CREATE TABLE users (id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())));

UUID v4 (랜덤) 는 인덱스 지역성이 나쁘다. 시간 정렬 UUID (v7, ULID) 가 B-tree 친화적.

ENUM

-- PostgreSQL
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');
ALTER TABLE orders ADD COLUMN status order_status;
ALTER TYPE order_status ADD VALUE 'refunded';     -- 추가
-- 값 제거는 직접 불가, 우회 필요

-- MySQL
CREATE TABLE orders (
  ...,
  status ENUM('pending', 'paid', 'shipped', 'cancelled')
);
ALTER TABLE orders MODIFY COLUMN status
  ENUM('pending', 'paid', 'shipped', 'cancelled', 'refunded');     -- 풀 리라이트

ENUM 은 추가는 쉬워도 변경/제거가 까다롭다. 상태가 자주 변하면 lookup 테이블 + FK 가 더 유연.

타입 변환 (CAST)

SELECT CAST('42' AS INTEGER);
SELECT '42'::INTEGER;     -- PostgreSQL 단축

-- 안전한 변환 (실패 시 NULL, PG 17+)
SELECT CAST('abc' AS INTEGER ON ERROR NULL);
-- 또는 사용자 함수, TRY_CAST 같은 패턴

-- TO_CHAR / TO_DATE / TO_NUMBER (PG, Oracle 영향)
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI');
SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD');

CAUTION

묵시적 형변환은 인덱스를 깬다. WHERE id = '42' 가 id 가 정수면 양쪽 모두 INTEGER 로 변환, 인덱스 OK. 하지만 WHERE CAST(id AS TEXT) = '42' 는 인덱스 못 쓴다.

pandas 와 타입 대응

SQLpandasdtype
INTEGER, BIGINTint64, Int64 (nullable)int
NUMERICDecimal (object) 또는 float64object/float
DOUBLEfloat64float
BOOLEANbool, boolean (nullable)bool
VARCHAR, TEXTobject, stringstring
DATEdatetime64[ns] (또는 datetime64[ns, tz])datetime
TIMESTAMPdatetime64[ns]datetime
TIMESTAMPTZdatetime64[ns, UTC]datetime tz-aware
INTERVALtimedelta64[ns]timedelta
JSON, JSONBobject (dict)object
ARRAYobject (list)object
UUIDobject (str)object

DB 와 DataFrame 의 타입 매핑은 Pandas read_sqldtype_backend, parse_dates, dtype= 옵션 참고.

Pandas nullable (Int64, boolean) 가 NULL 을 정수/부울에 보존하기 위해 도입되었다.

함정

1. VARCHAR(N) 의 N

  • PG: 글자 수, 초과 시 에러
  • MySQL: 글자 수, 초과 시 에러 (strict_mode) 또는 잘림 (non_strict)
  • 인코딩 (utf8mb4) 차이로 바이트 길이 ≠ 글자 수

2. TIMESTAMP vs TIMESTAMPTZ

PG 에서 TIMESTAMPTIMESTAMPTZ 는 다른 타입. 운영에서 시각은 항상 TIMESTAMPTZ.

3. NUMERIC 의 정밀도

NUMERIC(10, 2)     -- 전체 10 자리, 소수점 이하 2 자리
-- 정수부 최대 8 자리, 99999999.99 까지

4. FLOAT 의 비교

SELECT 0.1::FLOAT + 0.2::FLOAT = 0.3::FLOAT;     -- FALSE!
-- 같음 비교는 위험, ABS(a - b) < epsilon

5. NULL 의 정렬

ORDER BY salary DESC NULLS LAST     -- PG (표준)
ORDER BY salary IS NULL, salary DESC     -- MySQL 우회

6. 묵시적 변환 (MySQL)

-- MySQL: 문자열 ↔ 숫자 묵시적 변환
SELECT '1' + 1;          -- 2
SELECT '1a' + 1;         -- 2 (경고만)
SELECT 'a' + 1;          -- 1 (조용히 0 으로 변환!)

PG 는 같은 케이스에서 에러. 둘 다 운영 코드에선 명시적 변환 권장.

관련 위키

이 글의 용어 (10개)
[DB] MySQL / InnoDB: clustered index, redo log, MVCCdatabase-internals
정의 MySQL + InnoDB (기본 스토리지 엔진) 의 clustered index + redo log 기반 행 기반 RDBMS. 웹 / SaaS 의 가장 흔한 backbon…
[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
[Pandas] explodepandas
정의 는 list / set / tuple 등의 시퀀스 값을 가진 컬럼을 각 원소가 별도 행 이 되도록 펼친다. JSON / 로그 / 다중 태그 데이터 처리의 핵심. 기본 <Co…
[Pandas] Nullable Types (Int64, boolean, string[pyarrow])pandas
정의 Nullable dtypes 는 NaN/None 을 타입 그대로 표현할 수 있는 pandas 의 새 타입 시스템. NumPy 기반의 (NaN 미허용) 의 한계를 극복. - …
[Pandas] read_sql / to_sqlpandas
정의 - : SQL 결과 → DataFrame - : DataFrame → DB 테이블 SQLAlchemy 의 connection / engine 객체를 사용. 직접 sqlite…
[Pandas] to_datetimepandas
정의 는 문자열/숫자를 datetime 으로 변환. pandas 의 시계열 작업의 시작. 기본 <CodeWithOutput language="python" outputLangua…
[SQL] DDLsql
정의 DDL (Data Definition Language) 은 스키마를 정의/변경/제거 하는 SQL 의 하위 집합. 대상은 테이블, 인덱스, 뷰, 시퀀스, 스키마, 도메인 등.…
[SQL] DMLsql
정의 DML (Data Manipulation Language) 은 데이터 자체를 조회/삽입/수정/삭제 하는 SQL 의 하위 집합. 일상 쿼리의 99% 가 여기에 속한다. | 명…
[SQL] GROUP BYsql
정의 는 데이터를 키별로 그룹으로 나누고 각 그룹에 집계 함수 (aggregate) 를 적용해 그룹당 한 행으로 줄이는 연산. SQL 의 split-apply-combine. 와…
SQLsql
정의 SQL (Structured Query Language) 은 관계형 데이터베이스 (RDBMS) 와 대화하기 위한 선언형 (declarative) 질의 언어. 1970 년 E…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기