SQL 기본 쿼리 튜토리얼: 데이터베이스 입문자를 위한 완벽 가이드
SQL 소개
SQL(Structured Query Language)은 관계형 데이터베이스를 관리하고 조작하기 위한 표준 언어입니다. 데이터를 조회, 추가, 수정, 삭제하는 작업부터 데이터베이스 구조를 정의하고 관리하는 작업까지 모든 데이터베이스 작업을 SQL로 수행할 수 있습니다. 오늘날 거의 모든 관계형 데이터베이스 시스템(MySQL, PostgreSQL, Oracle, SQL Server 등)이 SQL을 지원하므로, SQL을 익히면 다양한 데이터베이스를 다룰 수 있습니다.
실습 환경 설정
이 튜토리얼에서는 MySQL을 기준으로 설명하지만, 대부분의 SQL 문법은 다른 데이터베이스에서도 동일하게 작동합니다. 실습을 위해 다음 중 하나의 방법을 선택할 수 있습니다:
- 온라인 SQL 실습 환경: W3Schools SQL Tryit Editor, SQLiteOnline
- 로컬 설치: MySQL, PostgreSQL, SQLite
- 클라우드 서비스: AWS RDS, Google Cloud SQL
1. 데이터베이스 기본 개념
데이터베이스 구조 이해하기
관계형 데이터베이스는 테이블(Table)로 구성되며, 각 테이블은 행(Row)과 열(Column)로 이루어져 있습니다. 열은 필드(Field) 또는 속성(Attribute)이라고도 하며, 행은 레코드(Record) 또는 튜플(Tuple)이라고도 합니다.
- 데이터베이스(Database): 테이블들의 집합
- 테이블(Table): 구조화된 데이터의 집합
- 열(Column): 데이터의 속성 (예: 이름, 나이, 주소)
- 행(Row): 하나의 데이터 레코드
- 기본키(Primary Key): 각 행을 고유하게 식별하는 열
- 외래키(Foreign Key): 다른 테이블과 연결하는 열
실습용 샘플 테이블 생성
이 튜토리얼에서 사용할 샘플 데이터베이스를 생성해보겠습니다. 온라인 쇼핑몰을 예제로 사용합니다.
-- 데이터베이스 생성
CREATE DATABASE shopping_mall;
USE shopping_mall;
-- 고객 테이블 생성
CREATE TABLE customers (
customer_id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
age INT,
city VARCHAR(50),
registration_date DATE
);
-- 제품 테이블 생성
CREATE TABLE products (
product_id INT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(100) NOT NULL,
category VARCHAR(50),
price DECIMAL(10, 2),
stock_quantity INT DEFAULT 0
);
-- 주문 테이블 생성
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT,
order_date DATETIME,
total_amount DECIMAL(10, 2),
status VARCHAR(20),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
샘플 데이터 입력
-- 고객 데이터 입력
INSERT INTO customers (name, email, age, city, registration_date) VALUES
('김철수', 'chulsoo@email.com', 28, '서울', '2024-01-15'),
('이영희', 'younghee@email.com', 32, '부산', '2024-02-20'),
('박민수', 'minsoo@email.com', 25, '대구', '2024-01-10'),
('정수진', 'sujin@email.com', 35, '서울', '2024-03-05'),
('최동현', 'donghyun@email.com', 29, '인천', '2024-02-15');
-- 제품 데이터 입력
INSERT INTO products (product_name, category, price, stock_quantity) VALUES
('노트북', '전자제품', 1200000, 50),
('무선마우스', '전자제품', 35000, 200),
('USB 케이블', '전자제품', 15000, 500),
('운동화', '의류', 89000, 150),
('백팩', '가방', 65000, 100),
('커피머신', '가전', 250000, 30),
('블루투스 스피커', '전자제품', 78000, 80),
('요가매트', '스포츠', 25000, 200);
-- 주문 데이터 입력
INSERT INTO orders (customer_id, order_date, total_amount, status) VALUES
(1, '2024-03-10 10:30:00', 1235000, '배송완료'),
(2, '2024-03-11 14:20:00', 89000, '배송중'),
(1, '2024-03-12 09:15:00', 50000, '주문확인'),
(3, '2024-03-13 16:45:00', 328000, '배송완료'),
(4, '2024-03-14 11:00:00', 65000, '배송준비'),
(5, '2024-03-15 13:30:00', 103000, '배송중');
2. SELECT – 데이터 조회하기
SELECT 문은 SQL에서 가장 많이 사용되는 명령어로, 데이터베이스에서 데이터를 조회할 때 사용합니다.
기본 SELECT 문법
-- 모든 열 조회
SELECT * FROM customers;
-- 특정 열만 조회
SELECT name, email, city FROM customers;
-- 열에 별칭(alias) 사용
SELECT
name AS '고객명',
age AS '나이',
city AS '거주도시'
FROM customers;
-- DISTINCT로 중복 제거
SELECT DISTINCT city FROM customers;
-- LIMIT로 결과 개수 제한
SELECT * FROM products LIMIT 5;
계산된 필드 사용하기
-- 계산된 필드 생성
SELECT
product_name,
price,
stock_quantity,
price * stock_quantity AS '총재고금액'
FROM products;
-- 문자열 연결 (MySQL의 경우 CONCAT 사용)
SELECT
CONCAT(name, ' (', city, ')') AS '고객정보',
age
FROM customers;
-- 날짜 함수 사용
SELECT
name,
registration_date,
YEAR(registration_date) AS '가입년도',
MONTH(registration_date) AS '가입월'
FROM customers;
3. WHERE – 조건으로 필터링하기
WHERE 절은 특정 조건을 만족하는 데이터만 조회할 때 사용합니다. 다양한 연산자와 함께 사용하여 정확한 데이터를 필터링할 수 있습니다.
비교 연산자
-- 같음 (=)
SELECT * FROM customers
WHERE city = '서울';
-- 같지 않음 (!=, <>)
SELECT * FROM products
WHERE category != '전자제품';
-- 크거나 같음 (>=)
SELECT * FROM products
WHERE price >= 50000;
-- 작음 (<)
SELECT * FROM customers
WHERE age < 30;
-- BETWEEN (범위)
SELECT * FROM products
WHERE price BETWEEN 30000 AND 100000;
-- IN (목록에 포함)
SELECT * FROM customers
WHERE city IN ('서울', '부산', '대구');
논리 연산자
-- AND (모든 조건 만족)
SELECT * FROM products
WHERE category = '전자제품'
AND price < 100000;
-- OR (하나 이상의 조건 만족)
SELECT * FROM customers
WHERE city = '서울'
OR age >= 35;
-- NOT (조건 부정)
SELECT * FROM products
WHERE NOT category = '전자제품';
-- 복합 조건
SELECT * FROM products
WHERE (category = '전자제품' OR category = '가전')
AND price < 100000
AND stock_quantity > 50;
패턴 매칭 (LIKE)
-- % : 0개 이상의 문자
-- _ : 정확히 1개의 문자
-- '김'으로 시작하는 이름
SELECT * FROM customers
WHERE name LIKE '김%';
-- '현'으로 끝나는 이름
SELECT * FROM customers
WHERE name LIKE '%현';
-- '무선'을 포함하는 제품명
SELECT * FROM products
WHERE product_name LIKE '%무선%';
-- 이메일이 gmail.com인 고객
SELECT * FROM customers
WHERE email LIKE '%@gmail.com';
NULL 값 처리
-- NULL 값 확인
SELECT * FROM customers
WHERE email IS NULL;
-- NULL이 아닌 값 확인
SELECT * FROM customers
WHERE email IS NOT NULL;
-- COALESCE로 NULL 값 대체
SELECT
name,
COALESCE(email, '이메일 없음') AS email_info
FROM customers;
4. ORDER BY – 데이터 정렬하기
ORDER BY 절은 조회 결과를 특정 열 기준으로 정렬할 때 사용합니다. 오름차순(ASC) 또는 내림차순(DESC)으로 정렬할 수 있습니다.
-- 오름차순 정렬 (기본값)
SELECT * FROM products
ORDER BY price;
-- 내림차순 정렬
SELECT * FROM products
ORDER BY price DESC;
-- 다중 열 정렬
SELECT * FROM products
ORDER BY category, price DESC;
-- 계산된 필드로 정렬
SELECT
product_name,
price,
stock_quantity,
price * stock_quantity AS total_value
FROM products
ORDER BY total_value DESC;
-- WHERE와 ORDER BY 함께 사용
SELECT * FROM customers
WHERE age >= 30
ORDER BY age DESC, name;
5. 집계 함수 – 데이터 요약하기
집계 함수는 여러 행의 값을 하나의 결과값으로 요약할 때 사용합니다. COUNT, SUM, AVG, MAX, MIN 등이 있습니다.
-- COUNT: 행 개수 세기
SELECT COUNT(*) AS '전체고객수'
FROM customers;
SELECT COUNT(DISTINCT city) AS '도시수'
FROM customers;
-- SUM: 합계 구하기
SELECT SUM(total_amount) AS '총매출'
FROM orders;
-- AVG: 평균 구하기
SELECT AVG(price) AS '평균가격'
FROM products;
SELECT ROUND(AVG(age), 1) AS '평균나이'
FROM customers;
-- MAX, MIN: 최대값, 최소값
SELECT
MAX(price) AS '최고가',
MIN(price) AS '최저가',
MAX(price) - MIN(price) AS '가격차이'
FROM products;
-- 조건과 함께 사용
SELECT
COUNT(*) AS '전자제품수',
AVG(price) AS '평균가격',
SUM(stock_quantity) AS '총재고'
FROM products
WHERE category = '전자제품';
6. GROUP BY – 그룹별 집계
GROUP BY 절은 데이터를 특정 열의 값으로 그룹화하여 각 그룹별로 집계 함수를 적용할 때 사용합니다.
-- 카테고리별 제품 수와 평균 가격
SELECT
category,
COUNT(*) AS '제품수',
AVG(price) AS '평균가격',
SUM(stock_quantity) AS '총재고'
FROM products
GROUP BY category;
-- 도시별 고객 수와 평균 나이
SELECT
city,
COUNT(*) AS '고객수',
ROUND(AVG(age), 1) AS '평균나이'
FROM customers
GROUP BY city
ORDER BY '고객수' DESC;
-- 고객별 주문 통계
SELECT
customer_id,
COUNT(*) AS '주문횟수',
SUM(total_amount) AS '총구매액',
AVG(total_amount) AS '평균구매액'
FROM orders
GROUP BY customer_id;
-- HAVING: 그룹화된 결과 필터링
SELECT
category,
COUNT(*) AS product_count,
AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING COUNT(*) >= 2;
-- 복잡한 GROUP BY 예제
SELECT
YEAR(order_date) AS '년도',
MONTH(order_date) AS '월',
COUNT(*) AS '주문건수',
SUM(total_amount) AS '월매출'
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date)
ORDER BY '년도', '월';
- WHERE: 그룹화 전에 개별 행을 필터링
- HAVING: 그룹화 후에 그룹을 필터링
7. JOIN – 테이블 결합하기
JOIN은 두 개 이상의 테이블을 연결하여 데이터를 조회할 때 사용합니다. 관계형 데이터베이스의 핵심 기능 중 하나입니다.
INNER JOIN
두 테이블에서 조건이 일치하는 행만 반환합니다.
-- 기본 INNER JOIN
SELECT
o.order_id,
o.order_date,
c.name,
c.email,
o.total_amount
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;
-- JOIN을 사용한 집계
SELECT
c.name,
COUNT(o.order_id) AS '주문횟수',
SUM(o.total_amount) AS '총구매액'
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name
ORDER BY '총구매액' DESC;
LEFT JOIN
왼쪽 테이블의 모든 행과 오른쪽 테이블의 일치하는 행을 반환합니다.
-- 주문이 없는 고객도 포함
SELECT
c.name,
c.email,
COUNT(o.order_id) AS '주문횟수',
COALESCE(SUM(o.total_amount), 0) AS '총구매액'
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.email;
-- 주문이 없는 고객 찾기
SELECT
c.name,
c.email,
c.registration_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_id IS NULL;
여러 테이블 JOIN
-- 주문 상세 테이블 생성 (실습용)
CREATE TABLE order_items (
item_id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10, 2),
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
-- 3개 테이블 JOIN
SELECT
c.name AS '고객명',
o.order_date AS '주문일',
p.product_name AS '제품명',
oi.quantity AS '수량',
oi.unit_price AS '단가',
oi.quantity * oi.unit_price AS '소계'
FROM order_items oi
INNER JOIN orders o ON oi.order_id = o.order_id
INNER JOIN customers c ON o.customer_id = c.customer_id
INNER JOIN products p ON oi.product_id = p.product_id
ORDER BY o.order_date DESC;
8. INSERT – 데이터 추가하기
INSERT 문은 테이블에 새로운 데이터를 추가할 때 사용합니다.
-- 모든 열에 값 입력
INSERT INTO customers
VALUES (6, '홍길동', 'hong@email.com', 30, '광주', '2024-03-20');
-- 특정 열만 지정하여 입력
INSERT INTO customers (name, email, age, city)
VALUES ('김영수', 'youngsu@email.com', 27, '대전');
-- 여러 행 한번에 입력
INSERT INTO products (product_name, category, price, stock_quantity)
VALUES
('무선키보드', '전자제품', 85000, 120),
('웹캠', '전자제품', 65000, 80),
('마우스패드', '전자제품', 15000, 300);
-- SELECT 결과를 INSERT
CREATE TABLE vip_customers LIKE customers;
INSERT INTO vip_customers
SELECT c.*
FROM customers c
INNER JOIN (
SELECT customer_id, SUM(total_amount) AS total
FROM orders
GROUP BY customer_id
HAVING total > 500000
) o ON c.customer_id = o.customer_id;
9. UPDATE – 데이터 수정하기
UPDATE 문은 기존 데이터를 수정할 때 사용합니다. WHERE 절을 사용하지 않으면 모든 행이 수정되므로 주의해야 합니다.
-- 단일 행 수정
UPDATE customers
SET email = 'newemail@email.com'
WHERE customer_id = 1;
-- 여러 열 수정
UPDATE products
SET
price = price * 1.1, -- 10% 인상
stock_quantity = stock_quantity + 50
WHERE category = '전자제품';
-- 조건부 수정
UPDATE orders
SET status = '배송완료'
WHERE status = '배송중'
AND DATEDIFF(NOW(), order_date) > 3;
-- CASE 문을 사용한 조건부 수정
UPDATE customers
SET grade =
CASE
WHEN age < 30 THEN 'Young'
WHEN age BETWEEN 30 AND 40 THEN 'Middle'
ELSE 'Senior'
END;
-- JOIN을 사용한 UPDATE (MySQL)
UPDATE products p
INNER JOIN (
SELECT product_id, COUNT(*) AS order_count
FROM order_items
GROUP BY product_id
) oi ON p.product_id = oi.product_id
SET p.popularity = 'High'
WHERE oi.order_count > 10;
10. DELETE – 데이터 삭제하기
DELETE 문은 테이블에서 데이터를 삭제할 때 사용합니다.
-- 특정 조건의 행 삭제
DELETE FROM orders
WHERE status = '취소';
-- 여러 조건으로 삭제
DELETE FROM products
WHERE stock_quantity = 0
AND category = '단종';
-- 서브쿼리를 사용한 삭제
DELETE FROM customers
WHERE customer_id NOT IN (
SELECT DISTINCT customer_id
FROM orders
) AND DATEDIFF(NOW(), registration_date) > 365;
-- 모든 데이터 삭제 (테이블 구조는 유지)
TRUNCATE TABLE table_name; -- DELETE보다 빠름
-- 테이블 자체를 삭제
DROP TABLE table_name;
11. CREATE TABLE – 테이블 생성하기
CREATE TABLE 문은 새로운 테이블을 생성할 때 사용합니다. 열의 이름, 데이터 타입, 제약조건 등을 정의합니다.
기본 테이블 생성
-- 기본 테이블 생성
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
hire_date DATE DEFAULT (CURRENT_DATE),
salary DECIMAL(10, 2) CHECK (salary > 0),
department_id INT,
is_active BOOLEAN DEFAULT TRUE
);
-- 외래키가 있는 테이블
CREATE TABLE departments (
department_id INT PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(100) NOT NULL,
manager_id INT,
location VARCHAR(100)
);
-- 외래키 추가
ALTER TABLE employees
ADD FOREIGN KEY (department_id)
REFERENCES departments(department_id);
-- 복합 기본키
CREATE TABLE course_enrollment (
student_id INT,
course_id INT,
enrollment_date DATE,
grade VARCHAR(2),
PRIMARY KEY (student_id, course_id)
);
데이터 타입
| 데이터 타입 | 설명 | 예시 |
|---|---|---|
| INT | 정수 | 나이, ID, 수량 |
| DECIMAL(p,s) | 고정 소수점 | 가격, 금액 |
| VARCHAR(n) | 가변 길이 문자열 | 이름, 이메일 |
| TEXT | 긴 문자열 | 설명, 내용 |
| DATE | 날짜 | 생일, 가입일 |
| DATETIME | 날짜와 시간 | 주문시각 |
| BOOLEAN | 참/거짓 | 활성화 여부 |
제약조건(Constraints)
-- 다양한 제약조건 예제
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
-- NOT NULL: 필수 입력
username VARCHAR(50) NOT NULL,
-- UNIQUE: 중복 불가
email VARCHAR(100) UNIQUE NOT NULL,
-- DEFAULT: 기본값
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT 'active',
-- CHECK: 조건 검사
age INT CHECK (age >= 18 AND age <= 120),
-- 복합 UNIQUE
first_name VARCHAR(50),
last_name VARCHAR(50),
UNIQUE KEY full_name (first_name, last_name)
);
-- 테이블 수정
-- 열 추가
ALTER TABLE users
ADD COLUMN phone VARCHAR(20);
-- 열 수정
ALTER TABLE users
MODIFY COLUMN phone VARCHAR(30) NOT NULL;
-- 열 삭제
ALTER TABLE users
DROP COLUMN phone;
-- 인덱스 생성 (검색 성능 향상)
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_name ON users(last_name, first_name);
12. 종합 실습 – 온라인 쇼핑몰 데이터베이스
지금까지 배운 내용을 종합하여 실제 온라인 쇼핑몰 데이터베이스를 분석하는 실습을 진행해보겠습니다.
실습 시나리오
온라인 쇼핑몰의 매출 분석, 고객 행동 분석, 재고 관리 등 실무에서 자주 사용되는 쿼리들을 작성해봅니다.
-- 1. 월별 매출 추이 분석
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS order_count,
SUM(total_amount) AS monthly_sales,
AVG(total_amount) AS avg_order_value
FROM orders
WHERE status != '취소'
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
-- 2. 고객 세그먼트 분석 (RFM 분석 간소화)
WITH customer_stats AS (
SELECT
c.customer_id,
c.name,
COUNT(o.order_id) AS frequency,
SUM(o.total_amount) AS monetary,
MAX(o.order_date) AS last_order_date,
DATEDIFF(NOW(), MAX(o.order_date)) AS recency
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name
)
SELECT
name,
frequency,
monetary,
recency,
CASE
WHEN frequency >= 5 AND monetary >= 500000 THEN 'VIP'
WHEN frequency >= 3 AND monetary >= 300000 THEN '우수고객'
WHEN frequency >= 1 THEN '일반고객'
ELSE '신규/휴면'
END AS customer_segment
FROM customer_stats
ORDER BY monetary DESC;
-- 3. 베스트셀러 제품 TOP 5
SELECT
p.product_name,
p.category,
COUNT(oi.item_id) AS sales_count,
SUM(oi.quantity) AS total_quantity,
SUM(oi.quantity * oi.unit_price) AS total_revenue
FROM products p
INNER JOIN order_items oi ON p.product_id = oi.product_id
INNER JOIN orders o ON oi.order_id = o.order_id
WHERE o.status = '배송완료'
GROUP BY p.product_id, p.product_name, p.category
ORDER BY total_revenue DESC
LIMIT 5;
-- 4. 재고 부족 경고 (안전재고 20개 기준)
SELECT
product_name,
category,
stock_quantity,
CASE
WHEN stock_quantity = 0 THEN '품절'
WHEN stock_quantity < 20 THEN '긴급보충'
WHEN stock_quantity < 50 THEN '보충필요'
ELSE '정상'
END AS stock_status
FROM products
WHERE stock_quantity < 50
ORDER BY stock_quantity;
-- 5. 고객별 구매 패턴 분석
SELECT
c.name,
c.city,
COUNT(DISTINCT DATE(o.order_date)) AS purchase_days,
COUNT(DISTINCT p.category) AS category_variety,
GROUP_CONCAT(DISTINCT p.category) AS purchased_categories,
AVG(o.total_amount) AS avg_order_value
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
GROUP BY c.customer_id, c.name, c.city
HAVING COUNT(DISTINCT o.order_id) >= 2
ORDER BY avg_order_value DESC;
-- 6. 시간대별 주문 패턴
SELECT
HOUR(order_date) AS order_hour,
COUNT(*) AS order_count,
AVG(total_amount) AS avg_amount,
CASE
WHEN HOUR(order_date) BETWEEN 6 AND 11 THEN '오전'
WHEN HOUR(order_date) BETWEEN 12 AND 17 THEN '오후'
WHEN HOUR(order_date) BETWEEN 18 AND 23 THEN '저녁'
ELSE '새벽'
END AS time_period
FROM orders
GROUP BY HOUR(order_date)
ORDER BY order_hour;
-- 7. 카테고리별 매출 기여도
WITH category_sales AS (
SELECT
p.category,
SUM(oi.quantity * oi.unit_price) AS category_revenue
FROM order_items oi
INNER JOIN products p ON oi.product_id = p.product_id
INNER JOIN orders o ON oi.order_id = o.order_id
WHERE o.status != '취소'
GROUP BY p.category
),
total_sales AS (
SELECT SUM(category_revenue) AS total FROM category_sales
)
SELECT
cs.category,
cs.category_revenue,
ROUND(cs.category_revenue / ts.total * 100, 2) AS revenue_percentage
FROM category_sales cs
CROSS JOIN total_sales ts
ORDER BY category_revenue DESC;
-- 8. 고객 생애 가치(CLV) 계산
SELECT
c.customer_id,
c.name,
c.registration_date,
DATEDIFF(NOW(), c.registration_date) AS customer_days,
COUNT(DISTINCT o.order_id) AS total_orders,
COALESCE(SUM(o.total_amount), 0) AS lifetime_value,
COALESCE(SUM(o.total_amount) / NULLIF(DATEDIFF(NOW(), c.registration_date), 0) * 365, 0)
AS annual_value
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.name, c.registration_date
ORDER BY lifetime_value DESC;
마무리
이 튜토리얼에서는 SQL의 기본적인 명령어와 실무에서 자주 사용되는 쿼리 패턴들을 학습했습니다. SQL은 데이터를 다루는 가장 기본적이면서도 강력한 도구입니다. 여기서 배운 내용은 SQL의 기초에 해당하며, 실제 업무에서는 더 복잡한 쿼리와 최적화 기법들이 필요할 수 있습니다.
- 중급 SQL: 서브쿼리, CTE, 윈도우 함수, 피벗
- 고급 SQL: 인덱스 최적화, 실행 계획 분석, 파티셔닝
- 데이터베이스 설계: 정규화, ERD, 관계 설계
- NoSQL: MongoDB, Redis 등 비관계형 데이터베이스
- 빅데이터 SQL: Spark SQL, Presto, BigQuery
- 실제 데이터로 연습하면서 쿼리 작성 능력을 향상시키세요
- 쿼리 실행 계획을 확인하여 성능을 최적화하는 습관을 기르세요
- 복잡한 쿼리는 단계별로 나누어 작성하고 테스트하세요
- 주석을 활용하여 쿼리의 의도를 명확히 문서화하세요