2025-10-07 13:41

  • SQL은 데이터를 정의, 조작, 제어하기 위한 표준 언어로, 관계형 데이터베이스 시스템의 핵심이다.
  • 선언적 언어의 특성 덕분에 사용자는 ‘무엇을’ 원하는지만 명시하면 되므로 복잡한 데이터 처리 과정을 몰라도 쉽게 데이터를 다룰 수 있다.
  • 조인, 집계 함수, 윈도우 함수 등 강력한 기능들을 통해 단순 조회부터 복잡한 데이터 분석까지 광범위하게 활용된다.

데이터베이스의 지배자 SQL 완전 정복 핸드북

우리가 매일 사용하는 앱, 웹사이트, 그리고 거의 모든 디지털 서비스의 이면에는 방대한 양의 데이터가 존재한다. 이 데이터를 저장하고, 관리하고, 필요한 순간에 꺼내 쓰는 기술의 중심에는 바로 SQL이 있다. SQL은 ‘Structured Query Language’의 약자로, 지난 50년간 데이터베이스 세계의 표준 언어로 군림해왔다. 개발자, 데이터 분석가, 기획자 등 데이터를 다루는 사람이라면 누구나 알아야 할 필수 지식인 SQL. 이 핸드북은 SQL이 왜 만들어졌는지 그 탄생 배경부터, 내부 구조, 핵심 사용법, 그리고 전문가로 나아가기 위한 심화 개념까지 모든 것을 담았다.

1. SQL의 탄생 배경 데이터 혼돈 속에서 질서를 찾다

1970년대 이전, 데이터를 관리하는 방식은 혼돈 그 자체였다. 각기 다른 제조사가 만든 데이터베이스 시스템들은 저마다 다른 데이터 모델(계층형, 네트워크형 등)과 독자적인 데이터 조작 언어를 사용했다. 이는 마치 도시마다 다른 언어와 교통 법규를 가진 것과 같았다. 개발자들은 새로운 시스템을 만날 때마다 해당 시스템의 복잡한 데이터 접근 방식을 처음부터 다시 배워야 했고, 시스템 간 데이터 이동은 거의 불가능에 가까웠다.

이러한 문제를 해결할 아이디어가 1970년, IBM의 연구원이었던 **에드거 F. 커드(Edgar F. Codd)**에 의해 제시되었다. 그는 ‘A Relational Model of Data for Large Shared Data Banks’라는 기념비적인 논문을 통해 **관계형 모델(Relational Model)**을 제안했다. 이 모델의 핵심은 데이터를 단순하고 직관적인 2차원 형태의 **테이블(Table)**로 표현하고, 이 테이블들 간의 관계를 통해 데이터를 구조화하는 것이었다. 마치 엑셀 시트 여러 개를 서로 연결해 사용하는 모습을 상상하면 된다.

이 혁신적인 모델을 실제로 구현하고 데이터를 쉽게 다룰 수 있는 언어를 만들기 위해 IBM은 ‘System R’이라는 프로젝트를 시작했다. 이 프로젝트에서 탄생한 언어가 바로 **SEQUEL(Structured English Query Language)**이었고, 이후 상표권 문제로 SQL이라는 이름으로 변경되었다.

SQL의 설계 철학은 명확했다.

  1. 선언적(Declarative) 언어: “어떻게(How)” 데이터를 가져올지 복잡한 절차를 명령하는 대신, “무엇을(What)” 원하는지만 선언하면 데이터베이스 시스템이 알아서 최적의 방법을 찾아 데이터를 가져오도록 하자. 이는 마치 레스토랑에서 “스테이크 미디엄 레어로 주세요”라고 주문하는 것과 같다. 주방장이 스테이크를 어떻게 구울지(절차) 일일이 지시할 필요가 없는 것이다.
  2. 쉬운 학습: 영문법과 유사한 구조로 비전문가도 쉽게 배워서 데이터를 조회할 수 있도록 하자.
  3. 표준화: 특정 데이터베이스 시스템에 종속되지 않는 표준 언어를 만들어 데이터베이스 세상에 질서를 부여하자.

이러한 철학 덕분에 SQL은 빠르게 개발자들 사이에서 인기를 얻었고, ANSI(미국 표준 협회)와 ISO(국제 표준화 기구)의 표준으로 채택되면서 명실상부 데이터베이스 언어의 제왕으로 자리 잡게 되었다.


2. SQL의 구조 언어로 데이터 세계를 건설하다

SQL은 단순히 데이터를 조회하는 언어가 아니다. 데이터베이스 세계를 창조하고(DDL), 그 안의 객체들을 조작하며(DML), 질서를 유지하는(DCL, TCL) 강력한 체계를 갖추고 있다. 이를 크게 4가지 하위 언어로 구분할 수 있다.

구분전체 이름역할주요 명령어비유
DDLData Definition Language데이터 정의 언어CREATE, ALTER, DROP, TRUNCATE건물의 설계도(청사진)를 만들고, 수정하고, 파기하는 행위
DMLData Manipulation Language데이터 조작 언어SELECT, INSERT, UPDATE, DELETE건물에 가구를 들이고(INSERT), 배치 바꾸고(UPDATE), 내보내는(DELETE) 행위
DCLData Control Language데이터 제어 언어GRANT, REVOKE건물 각 방에 출입 권한을 부여하거나(GRANT) 회수하는(REVOKE) 행위
TCLTransaction Control Language트랜잭션 제어 언어COMMIT, ROLLBACK, SAVEPOINT중요한 작업을 최종 확정하거나(COMMIT) 문제가 생겼을 때 원상 복구(ROLLBACK)하는 행위

2.1 DDL 데이터의 뼈대를 세우다

DDL은 데이터가 저장될 구조, 즉 테이블, 뷰, 인덱스 등 데이터베이스 객체의 스키마(Schema)를 정의하는 언어다.

  • CREATE: 새로운 테이블이나 데이터베이스 객체를 생성한다.
    CREATE TABLE Users (
        user_id INT PRIMARY KEY,
        username VARCHAR(50) NOT NULL,
        email VARCHAR(100) UNIQUE,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
  • ALTER: 기존 테이블의 구조를 수정한다. 새로운 컬럼을 추가하거나 기존 컬럼의 데이터 타입을 변경할 수 있다.
    ALTER TABLE Users ADD COLUMN last_login DATE;
  • DROP: 테이블이나 데이터베이스 객체를 완전히 삭제한다. 데이터는 물론 구조 자체가 사라지므로 신중하게 사용해야 한다.
    DROP TABLE Users;
  • TRUNCATE: 테이블의 모든 데이터를 빠르게 삭제한다. DROP과 달리 테이블 구조는 남겨두고 내부 데이터만 비운다. DELETE보다 훨씬 빠르지만 복구가 불가능하다.

2.2 DML 데이터에 생명을 불어넣다

DML은 테이블 내의 데이터를 직접 조작하는 언어로, SQL에서 가장 빈번하게 사용된다.

  • SELECT: SQL의 심장. 테이블에서 원하는 데이터를 조회한다.
    SELECT username, email FROM Users WHERE user_id = 1;
  • INSERT: 테이블에 새로운 데이터 행(Row)을 추가한다.
    INSERT INTO Users (user_id, username, email) VALUES (1, 'handbook_user', 'user@handbook.com');
  • UPDATE: 기존 데이터의 값을 수정한다. WHERE 절을 사용하지 않으면 모든 행의 데이터가 변경되는 대참사가 발생할 수 있으니 주의해야 한다.
    UPDATE Users SET email = 'new_email@handbook.com' WHERE user_id = 1;
  • DELETE: 특정 데이터 행을 삭제한다. UPDATE와 마찬가지로 WHERE 절이 필수적이다.
    DELETE FROM Users WHERE username = 'handbook_user';

2.3 DCL과 TCL 데이터의 무결성과 보안을 지키다

DCL과 TCL은 데이터베이스 관리자(DBA)가 주로 사용하지만, 개발자도 그 개념을 반드시 이해하고 있어야 한다.

  • DCL (데이터 제어): GRANT는 특정 사용자에게 테이블 접근이나 데이터 수정 권한을 부여하고, REVOKE는 그 권한을 회수한다. 이를 통해 민감한 데이터에 대한 접근을 통제하고 보안을 강화한다.
  • TCL (트랜잭션 제어): 트랜잭션(Transaction)은 ‘모두 성공하거나 모두 실패해야 하는’ 하나의 작업 단위를 의미한다. 예를 들어 계좌 이체는 ‘A 계좌 출금’과 ‘B 계좌 입금’이 하나의 트랜잭션으로 묶여야 한다. COMMIT은 트랜잭션의 모든 작업을 영구적으로 저장하고, ROLLBACK은 문제가 발생했을 때 트랜잭션 시작 전 상태로 모든 것을 되돌린다. 이는 데이터의 일관성무결성을 보장하는 핵심 메커니즘이다.

3. SQL 핵심 사용법 데이터와 대화하는 기술

이제 이론을 넘어 실제로 데이터를 다루는 방법을 알아보자. SQL의 진정한 힘은 여러 구문들을 조합하여 원하는 데이터를 정교하게 추출하고 가공하는 데서 나온다.

3.1 모든 것의 시작 SELECT와 WHERE

데이터 조회는 SELECTFROM으로 시작한다. SELECT 뒤에는 가져올 컬럼(열) 이름을, FROM 뒤에는 데이터를 가져올 테이블 이름을 명시한다.

-- Customers 테이블에서 모든 고객의 이름과 이메일을 조회
SELECT name, email FROM Customers;

하지만 수백만 건의 데이터 중 우리가 원하는 것은 단 몇 개일 수 있다. 이때 사용하는 것이 WHERE 절이다. WHERE는 특정 조건을 만족하는 데이터만 필터링하는 역할을 한다.

-- Customers 테이블에서 '서울'에 사는 고객만 조회
SELECT name, email FROM Customers WHERE city = '서울';
 
-- Orders 테이블에서 주문 금액이 50000원 이상인 주문 조회
SELECT order_id, amount FROM Orders WHERE amount >= 50000;
 
-- Products 테이블에서 상품 이름에 '노트북'이 포함된 상품 조회
SELECT product_name FROM Products WHERE product_name LIKE '%노트북%';

3.2 관계의 힘 JOIN

관계형 데이터베이스의 꽃은 JOIN이다. JOIN은 여러 테이블에 흩어져 있는 데이터를 공통된 키(Key)를 기준으로 합쳐서 하나의 결과로 보여주는 기능이다. 마치 고객 정보가 담긴 테이블과 주문 정보가 담긴 테이블을 ‘고객 ID’로 연결하여 ‘어떤 고객이 어떤 상품을 주문했는지’ 한눈에 파악하는 것과 같다.

  • INNER JOIN: 두 테이블에 공통으로 존재하는 데이터만 연결한다. 주문 기록이 있는 고객 정보만 가져온다. 가장 일반적으로 사용되는 조인이다.

    SELECT C.name, O.order_date, O.amount
    FROM Customers C
    INNER JOIN Orders O ON C.customer_id = O.customer_id;
  • LEFT JOIN (LEFT OUTER JOIN): 왼쪽 테이블의 모든 데이터를 기준으로, 오른쪽 테이블에 일치하는 데이터가 있으면 가져오고 없으면 NULL로 표시한다. 주문 기록이 없는 고객까지 모두 포함하여 조회할 때 유용하다.

    -- 모든 고객의 이름과, 주문했다면 주문 날짜를 조회 (주문 안 한 고객도 포함)
    SELECT C.name, O.order_date
    FROM Customers C
    LEFT JOIN Orders O ON C.customer_id = O.customer_id;
  • RIGHT JOINFULL OUTER JOIN도 있지만, LEFT JOIN의 원리를 이해하면 대부분의 상황에 대처할 수 있다.

3.3 데이터 요약과 통계 GROUP BY와 집계 함수

개별 데이터를 넘어 데이터의 전체적인 경향이나 통계를 파악하고 싶을 때 GROUP BY를 사용한다. GROUP BY는 특정 컬럼을 기준으로 데이터를 그룹화하고, 각 그룹에 대해 통계를 내는 데 사용된다. 이때 **집계 함수(Aggregate Function)**가 함께 쓰인다.

  • COUNT(): 그룹의 행 개수
  • SUM(): 그룹의 합계
  • AVG(): 그룹의 평균
  • MAX() / MIN(): 그룹의 최댓값 / 최솟값
-- 도시별 고객 수를 계산
SELECT city, COUNT(customer_id) AS customer_count
FROM Customers
GROUP BY city;
 
-- 상품 카테고리별 평균 가격을 계산
SELECT category, AVG(price) AS average_price
FROM Products
GROUP BY category;

WHERE가 그룹화하기 전의 개별 데이터를 필터링한다면, HAVING그룹화된 결과에 대해 조건을 적용한다.

-- 고객 수가 10명 이상인 도시만 조회
SELECT city, COUNT(customer_id) AS customer_count
FROM Customers
GROUP BY city
HAVING COUNT(customer_id) >= 10;

4. SQL 심화 전문가로 가는 길

기본적인 CRUD(Create, Read, Update, Delete)와 JOIN, GROUP BY를 마스터했다면 이제 SQL을 한 차원 높게 활용할 수 있는 심화 개념을 학습할 차례다.

4.1 쿼리 속의 쿼리 서브쿼리(Subquery)

서브쿼리는 하나의 SQL 문 안에 포함된 또 다른 SELECT 문이다. 러시아의 인형 마트료시카처럼 쿼리 안에 쿼리가 중첩된 구조다. 복잡한 조건을 걸거나, 다른 쿼리의 결과를 기반으로 현재 쿼리를 실행해야 할 때 매우 유용하다.

-- 평균 주문 금액보다 더 많은 금액을 주문한 주문 내역 조회
SELECT *
FROM Orders
WHERE amount > (SELECT AVG(amount) FROM Orders); -- () 안이 서브쿼리

4.2 분석의 새로운 지평 윈도우 함수(Window Function)

윈도우 함수는 데이터 분석의 판도를 바꾼 강력한 기능이다. 기존의 집계 함수가 GROUP BY를 통해 여러 행을 하나의 결과로 요약했다면, 윈도우 함수는 각 행은 그대로 유지하면서 특정 범위(Window)의 데이터를 기준으로 순위, 누적 합계, 이동 평균 등을 계산한다.

PARTITION BY로 그룹을 나누고 ORDER BY로 그룹 내 순서를 정한 뒤, 다양한 함수를 적용한다.

  • ROW_NUMBER(), RANK(), DENSE_RANK(): 순위를 매긴다.
  • SUM() OVER (...), AVG() OVER (...): 누적 합계, 이동 평균 등을 계산한다.
  • LEAD(), LAG(): 현재 행을 기준으로 이전 또는 다음 행의 값을 가져온다.
-- 부서별로 급여가 높은 순서대로 순위를 매김
SELECT
    employee_name,
    department,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as salary_rank
FROM Employees;

이 쿼리 결과는 모든 직원 정보를 그대로 보여주면서, 각 직원의 부서 내 급여 순위(salary_rank)를 새로운 컬럼으로 추가해 준다.

4.3 복잡한 쿼리를 깔끔하게 CTE(Common Table Expression)

긴 서브쿼리가 여러 번 반복되거나 쿼리가 너무 복잡해져서 읽기 어려울 때 CTE가 해답이 될 수 있다. WITH 절을 사용하여 쿼리의 일부를 마치 임시 테이블처럼 별도로 정의하고 이름을 붙여주는 기능이다. 쿼리의 가독성을 높이고 논리적인 흐름을 명확하게 만들어준다.

WITH RegionalSales AS (
    -- 1. 지역별 매출을 계산하는 임시 테이블 정의
    SELECT region, SUM(amount) as total_sales
    FROM Sales
    GROUP BY region
),
TopRegions AS (
    -- 2. 위 결과에서 상위 지역을 필터링하는 임시 테이블 정의
    SELECT region
    FROM RegionalSales
    WHERE total_sales > 1000000
)
-- 3. 최종적으로 상위 지역에 속한 직원 정보를 조회
SELECT *
FROM Employees
WHERE region IN (SELECT region FROM TopRegions);

4.4 보이지 않는 성능의 핵심 인덱스(Index)

수백만, 수억 건의 데이터 속에서 WHERE 조건에 맞는 데이터를 어떻게 빠르게 찾을 수 있을까? 데이터베이스는 인덱스를 사용한다. 인덱스는 책의 맨 뒤에 있는 ‘찾아보기’와 같다. 특정 단어가 몇 페이지에 있는지 미리 정리해두면 책 전체를 뒤지지 않고도 원하는 내용을 바로 찾을 수 있는 것처럼, 데이터베이스는 특정 컬럼의 값과 해당 데이터의 물리적 위치를 미리 정렬된 자료구조(주로 B-Tree)로 저장해둔다.

SELECT 문의 WHERE 절이나 JOIN 조건에 사용되는 컬럼에 인덱스를 생성하면 검색 속도가 극적으로 향상된다. 하지만 INSERT, UPDATE, DELETE 시에는 인덱스도 함께 수정되어야 하므로 쓰기 성능이 저하될 수 있다. 따라서 읽기 작업이 빈번한 컬럼에 적절히 사용하는 것이 중요하다.


5. 결론 시대를 초월한 데이터의 언어

SQL은 1970년대에 탄생했지만, 그 영향력은 빅데이터와 AI의 시대인 오늘날에도 여전히 막강하다. 관계형 데이터베이스는 물론, 최신 데이터 웨어하우스(Snowflake, BigQuery)와 일부 NoSQL 데이터베이스까지 SQL 인터페이스를 지원하고 있다. 이는 SQL이 배우기 쉽고, 강력하며, 데이터를 다루는 가장 직관적인 방법이라는 것을 증명한다.

이 핸드북을 통해 SQL의 탄생 이유부터 그 강력한 기능까지 살펴보았다. 하지만 진정한 실력은 직접 쿼리를 작성하고, 데이터를 분석하며, 성능 문제를 해결하는 경험을 통해 길러진다. SQL은 데이터를 위한 언어이자, 데이터 속에 숨겨진 가치를 발견하는 탐험 도구다. 이 강력한 도구를 손에 넣고 데이터의 세계를 자유롭게 항해하길 바란다.