codingstairs
노트에듀라이프연락
⌕검색⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

연락하기

로그인 없이도 보낼 수 있어요. 답변이 필요하면 이메일을 함께 적어 주세요.

  • 익명 폼으로 의견 남기기 →
  • ✉ warragon112@gmail.com
  • 카카오톡 오픈채팅 ↗

© 2026 codingstairs

  • 노트
  • 에듀
  • 검색
  • 라이프
  • 연락
  • 약관
  • RSS
  • GitHub
노트›data

SQL 기초

2026-04-28 게시· 2026-05-18 갱신·0회 조회

SQL 기초

PostgreSQL · MySQL · SQLite · SQL Server · Oracle 모두 핵심 문법은 비슷합니다. 한 번 배우면 평생 쓰는 도구가 됩니다.

1. SQL 에 대한 이야기

SQL 은 Structured Query Language 의 줄임말입니다. 1974 년 IBM 의 Donald Chamberlin 과 Raymond Boyce 가 SEQUEL 이라는 이름으로 발표했고, 상표 충돌로 SQL 로 개명됐습니다. 1986 년 ANSI, 1987 년 ISO 가 SQL-86 으로 표준화했고 이후 갱신이 이어집니다.

표준 시기 주요 추가
SQL-86 / 89 1986–89 기본 문법
SQL-92 1992 JOIN 종류 · 표준화 강화
SQL:1999 1999 OLAP · 트리거 · CTE
SQL:2003 2003 XML · 윈도우 함수
SQL:2008 2008 TRUNCATE · MERGE
SQL:2016 2016 JSON

DBMS 마다 표준 준수 정도와 방언이 다릅니다. PostgreSQL 의 ON CONFLICT 와 MySQL 의 ON DUPLICATE KEY UPDATE 처럼 구문 자체가 갈리는 자리도 있습니다.

2. DDL · DML · DCL · TCL

분류 의미 명령
DDL (Data Definition) 스키마 정의 CREATE · ALTER · DROP · TRUNCATE
DML (Data Manipulation) 데이터 조작 SELECT · INSERT · UPDATE · DELETE
DCL (Data Control) 권한 GRANT · REVOKE
TCL (Transaction Control) 트랜잭션 BEGIN · COMMIT · ROLLBACK

DDL 의 일부는 트랜잭션 안에서의 동작이 DBMS 마다 다릅니다. PostgreSQL 은 거의 모든 DDL 이 트랜잭션 안에서 롤백 가능하지만, MySQL · Oracle 은 일부 DDL 이 묵시적 커밋을 일으킵니다.

3. CREATE TABLE

CREATE TABLE users (
  id         BIGSERIAL PRIMARY KEY,
  email      TEXT NOT NULL UNIQUE,
  name       TEXT NOT NULL,
  age        INT CHECK (age >= 0),
  active     BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users (email);

자료형은 DBMS 마다 약간 다릅니다. INT · BIGINT · TEXT · VARCHAR(n) · BOOLEAN · DATE · TIMESTAMP · TIMESTAMPTZ · JSONB 정도가 자주 등장합니다.

4. SELECT 의 절들

SELECT name, COUNT(*) AS cnt
FROM   orders
WHERE  status = 'paid'
       AND created_at >= '2025-01-01'
GROUP BY name
HAVING COUNT(*) >= 5
ORDER BY cnt DESC
LIMIT  10
OFFSET 0;

논리적 처리 순서는 작성 순서와 다릅니다.

FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT/OFFSET

이 순서를 알면 왜 WHERE 에서는 별칭(alias) 을 못 쓰고 ORDER BY 에서는 쓸 수 있는지 같은 함정이 풀립니다.

WHERE 절의 자주 쓰는 모양들.

WHERE age BETWEEN 18 AND 30
WHERE name LIKE 'Lee%'              -- 'Lee' 로 시작
WHERE name ILIKE '%lee%'            -- 대소문자 무시 (Postgres)
WHERE id IN (1, 2, 3)
WHERE id NOT IN (SELECT user_id FROM banned)
WHERE email IS NULL
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = users.id)

5. JOIN 5 가지

A 테이블             B 테이블
+---+------+        +-----+------+
| 1 | foo  |        |  1  | x    |
| 2 | bar  |        |  3  | y    |
+---+------+        +-----+------+
JOIN 결과
INNER JOIN 양쪽 매칭만. (1, foo, x)
LEFT JOIN 왼쪽 전체, 오른쪽 없으면 NULL. (1, foo, x), (2, bar, NULL)
RIGHT JOIN 오른쪽 전체, 왼쪽 없으면 NULL. (1, foo, x), (NULL, NULL, y)
FULL OUTER JOIN 양쪽 전체
CROSS JOIN 데카르트 곱. m × n 행
SELECT u.name, o.amount
FROM   users u
INNER JOIN orders o ON o.user_id = u.id
WHERE  o.status = 'paid';

USING (col) 은 같은 컬럼명일 때 짧게 쓰는 형태입니다.

6. CTE (WITH)

WITH paid_users AS (
  SELECT DISTINCT user_id FROM orders WHERE status = 'paid'
)
SELECT u.name
FROM   users u
JOIN   paid_users p ON p.user_id = u.id;

재귀 CTE 는 트리·계층 처리에 유용합니다.

WITH RECURSIVE org AS (
  SELECT id, name, manager_id, 1 AS level
  FROM   employees WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, e.manager_id, o.level + 1
  FROM   employees e JOIN org o ON e.manager_id = o.id
)
SELECT * FROM org ORDER BY level;

CTE 는 SQL:1999 표준이며 MySQL 은 8.0+ 에서 지원합니다.

7. INSERT · UPDATE · DELETE · UPSERT

INSERT INTO users (email, name) VALUES ('a@b.com', 'lee');

UPDATE users SET active = FALSE WHERE last_login < '2024-01-01';

DELETE FROM users WHERE active = FALSE;

-- UPSERT (Postgres)
INSERT INTO users (email, name) VALUES ('a@b.com', 'lee')
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;

UPDATE · DELETE 시 WHERE 를 빠뜨리면 모든 행이 영향을 받습니다. 운영 DB 에서 가장 자주 일어나는 사고 자리입니다. 트랜잭션 안에서 같은 조건으로 SELECT 해 보고 BEGIN; UPDATE ...; COMMIT; 하는 습관이 도움이 됩니다.

8. 트랜잭션과 격리 수준

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;   -- 또는 ROLLBACK

ACID — 원자성·일관성·격리성·지속성. 격리 수준은 4 단계입니다.

READ UNCOMMITTED < READ COMMITTED < REPEATABLE READ < SERIALIZABLE

기본값은 DBMS 마다 다릅니다. Postgres 는 READ COMMITTED, MySQL/InnoDB 는 REPEATABLE READ.

9. NULL 의 3치 논리

NULL 은 "값 없음" 입니다. NULL 과의 비교는 TRUE / FALSE 가 아니라 UNKNOWN 이 결과입니다.

SELECT NULL = NULL;     -- NULL (UNKNOWN)
SELECT NULL = 1;        -- NULL
SELECT NULL <> NULL;    -- NULL
SELECT 1 IS NULL;       -- FALSE
SELECT NULL IS NULL;    -- TRUE

WHERE 절은 TRUE 인 행만 통과시킵니다. WHERE x = NULL 은 영원히 행을 안 돌려줍니다. 항상 IS NULL 또는 IS NOT NULL 을 씁니다.

COUNT(*) 는 NULL 도 세지만 COUNT(col) 은 그 컬럼이 NULL 인 행을 뺍니다. AVG(col) 도 NULL 행은 분모·분자에서 모두 빠집니다.

10. 자주 걸리는 자리

WHERE 빠뜨린 UPDATE/DELETE — 운영 사고 단골입니다. 항상 SELECT 로 먼저 검증합니다.

GROUP BY 누락 컬럼 — SELECT 에 있는 비집계 컬럼은 GROUP BY 에도 적습니다.

ORDER BY 없는 LIMIT — 매번 다른 행이 나올 수 있습니다.

N+1 쿼리 — 애플리케이션 코드가 루프 안에서 같은 패턴의 쿼리를 반복하는 경우. JOIN 또는 IN 절로 한 번에 가져옵니다.

인덱스 안 타는 쿼리 — WHERE func(col) = ... 같은 함수·연산이 인덱스를 무력화합니다. 식 인덱스를 만들거나 조건을 바꿉니다.

SELECT * — 운영 코드에서는 컬럼을 명시합니다. 스키마 변경 시 무너지지 않게.

방언 차이 — NOW() vs CURRENT_TIMESTAMP, LIMIT vs TOP. 처음 만나면 검색합니다.

11. 한 번 시도해 보기

명령줄에서 한 번 시도해 보려면 Docker 한 줄로 PostgreSQL 을 띄울 수 있습니다.

docker run --name pg -e POSTGRES_PASSWORD=secret -p 5432:5432 -d postgres:16
docker exec -it pg psql -U postgres

SQLite 는 설치 부담이 거의 없습니다.

sqlite3 test.db

하고픈 말

SQL 은 30 년 넘은 언어인데도 신규 시스템에서 여전히 첫 선택지입니다. 표준이 잘 정의돼 있고 도구·자료가 두텁기 때문입니다. 한 번 익숙해지면 DBMS 가 바뀌어도 80% 는 그대로 쓸 수 있습니다.

Next

  • postgres-first
  • postgres-deep

PostgreSQL 공식 튜토리얼 · Use The Index, Luke! · SQLBolt · Codd 의 1970 년 논문 을 참고합니다.

data 카테고리의 다른 글

카테고리 전체 보기 →
  • DB 시드 소스를 코드 트리 안에 두지 않는다
  • Supabase Storage — 파일 업로드와 권한
  • Kafka 실무 — 토픽 설계와 메시지 흐름
  • 여러 PostgreSQL 풀 한 앱에서 관리하기
  • 백업과 복구
  • 이미지 파이프라인