SQL(Structured Query Language)은 관계형 데이터베이스 관리 시스템(RDBMS)에서 데이터를 관리하기 위해 설계된 표준 언어입니다. ANSI SQL이 표준이지만, 각 DBMS(예: MySQL, PostgreSQL, Oracle)는 고유한 확장 기능(PL/SQL, T-SQL 등)을 제공합니다. 이 문서에서는 데이터 정의, 조작, 쿼리 및 제어에 필요한 핵심 SQL 문법을 예제와 함께 설명합니다.
1. 데이터베이스 및 테이블 기본
데이터베이스와 테이블은 데이터를 구조화하는 기본 단위입니다. 테이블은 행과 열로 구성되며, 각 테이블에는 데이터를 고유하게 식별하는 기본 키(Primary Key)가 있어야 합니다. 기본 키 값은 수정하거나 재사용할 수 없습니다. SQL은 대소문자를 구분하지 않지만, 테이블명, 컬럼명은 DBMS 설정에 따라 달라질 수 있습니다.
주석
-- 한 줄 주석
/*
여러 줄 주석
*/
SELECT * FROM users; -- 인라인 주석
데이터베이스 생성 및 선택
CREATE DATABASE shop;
USE shop;
2. 테이블 생성 (CREATE TABLE)
CREATE TABLE 문을 사용하여 새 테이블을 정의합니다. 각 컬럼의 데이터 타입, NULL 허용 여부, 기본값, 자동 증가 등을 지정할 수 있습니다.
CREATE TABLE products (
product_id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) DEFAULT 0.00,
created_at DATE NULL,
PRIMARY KEY (product_id)
);
3. 테이블 구조 변경 (ALTER TABLE)
이미 존재하는 테이블에 컬럼을 추가, 삭제하거나 테이블 자체를 제거합니다.
-- 컬럼 추가
ALTER TABLE products ADD quantity INT NOT NULL DEFAULT 0;
-- 컬럼 삭제
ALTER TABLE products DROP COLUMN quantity;
-- 테이블 삭제
DROP TABLE products;
4. 데이터 삽입 (INSERT)
새로운 데이터를 테이블에 추가합니다.
-- 단일 행 삽입
INSERT INTO products (name, price) VALUES ('노트북', 1500000);
-- 다른 테이블의 데이터를 조회하여 삽입
INSERT INTO discounted_products (product_id, name)
SELECT product_id, name FROM products WHERE price > 1000000;
-- 테이블 구조와 데이터를 복사하여 새 테이블 생성
CREATE TABLE products_backup AS SELECT * FROM products;
5. 데이터 갱신 (UPDATE)
기존 데이터를 수정합니다. 항상 WHERE 절을 사용하여 대상 행을 명확히 지정해야 합니다. 그렇지 않으면 모든 행이 수정됩니다.
UPDATE products
SET price = 1400000, name = '울트라북'
WHERE product_id = 1;
6. 데이터 삭제 (DELETE & TRUNCATE)
데이터를 삭제하는 두 가지 방법이 있습니다.
-- 조건에 맞는 특정 행 삭제
DELETE FROM products WHERE product_id = 5;
-- 테이블의 모든 행을 빠르게 삭제 (롤백 불가능)
TRUNCATE TABLE products;
7. 데이터 조회 (SELECT)
가장 자주 사용되는 명령어입니다. DISTINCT는 중복을 제거하고, LIMIT는 결과 행 수를 제한합니다.
-- 특정 컬럼 조회
SELECT name, price FROM products;
-- 중복 제거 (모든 선택된 컬럼 값이 동일해야 함)
SELECT DISTINCT category FROM products;
-- 결과 제한 (LIMIT)
-- 처음 5개 행
SELECT * FROM products LIMIT 5;
-- 3번째 행부터 2개 행 조회 (0부터 시작)
SELECT * FROM products LIMIT 2 OFFSET 2;
-- 또는 MySQL 단축 문법
SELECT * FROM products LIMIT 2, 2;
8. 정렬 (ORDER BY)
하나 이상의 컬럼을 기준으로 결과를 정렬합니다. ASC(오름차순, 기본값) 또는 DESC(내림차순)를 사용합니다.
SELECT * FROM products
ORDER BY price DESC, name ASC;
9. 데이터 필터링 (WHERE)
특정 조건을 만족하는 행만 선택합니다. 가능하면 모든 데이터를 가져온 후 애플리케이션에서 필터링하는 것보다 SQL 단계에서 필터링하는 것이 효율적입니다.
SELECT * FROM products
WHERE price BETWEEN 500000 AND 2000000;
-- NULL 체크
SELECT * FROM products WHERE description IS NULL;
-- 여러 조건 결합 (AND, OR, NOT)
SELECT * FROM products
WHERE (category = '전자기기' OR category = '컴퓨터')
AND price > 1000000;
10. 패턴 매칭 (LIKE & 와일드카드)
텍스트 필드에서 복잡한 패턴을 검색할 때 사용합니다. 성능 저하를 유발할 수 있으므로, 특히 패턴이 문자열 앞에 오는 경우 주의해야 합니다.
%: 0개 이상의 임의 문자_: 정확히 1개의 임의 문자
-- 'A'로 시작하는 모든 상품
SELECT * FROM products WHERE name LIKE 'A%';
-- 두 번째 글자가 'b'인 모든 상품
SELECT * FROM products WHERE name LIKE '_b%';
-- 'S' 또는 'T'로 시작하는 상품
SELECT * FROM products WHERE name LIKE '[ST]%';
11. 계산 필드 (Concatenation, Aliases)
서버 측에서 데이터를 가공하면 클라이언트의 부하를 줄이고 네트워크 트래픽을 감소시킵니다. AS 키워드로 별칭을 부여합니다.
-- 수학 연산
SELECT product_id, price * quantity AS total_value FROM products;
-- 문자열 연결
SELECT CONCAT(name, ' (', category, ')') AS product_info FROM products;
12. 내장 함수
DBMS별로 함수가 다르므로 이식성을 고려해야 합니다. 여기서는 MySQL 기준으로 설명합니다.
집계 함수
| 함수 | 설명 |
|---|---|
| COUNT() | 행의 개수 |
| SUM() | 합계 |
| AVG() | 평균 |
| MAX() | 최대값 |
| MIN() | 최소값 |
COUNT(*)는 NULL을 포함한 모든 행을 세고, COUNT(column)은 NULL이 아닌 값만 셉니다. AVG()는 NULL 행을 무시합니다.
SELECT AVG(DISTINCT price) AS avg_distinct_price FROM products;
문자열 함수
| 함수 | 설명 |
|---|---|
| LEFT() / RIGHT() | 왼쪽/오른쪽에서 문자 추출 |
| LOWER() / UPPER() | 소문자/대문자 변환 |
| LENGTH() | 문자열 길이 반환 |
| TRIM() | 앞뒤 공백 제거 |
SELECT * FROM products WHERE SOUNDEX(name) = SOUNDEX('appple');
날짜 및 시간 함수
| 함수 | 설명 |
|---|---|
| NOW() | 현재 날짜와 시간 |
| DATE() | 날짜 부분 추출 |
| YEAR() / MONTH() / DAY() | 각 구성 요소 추출 |
| DATEDIFF() | 두 날짜 간 차이 |
| DATE_ADD() | 날짜 더하기 |
13. 데이터 그룹화 (GROUP BY & HAVING)
GROUP BY는 동일한 값을 가진 행을 그룹으로 묶고, HAVING은 그룹화된 결과에 대한 필터 조건을 지정합니다. WHERE는 그룹화 전에 개별 행을 필터링합니다.
SELECT category, COUNT(*) AS product_count, AVG(price) AS avg_price
FROM products
WHERE price > 0
GROUP BY category
HAVING product_count >= 3
ORDER BY avg_price DESC;
14. 서브쿼리 (Subquery)
하나의 SQL 문 안에 다른 SQL 문을 중첩하여 사용합니다. 서브쿼리는 일반적으로 단일 컬럼과 단일 행 또는 여러 행을 반환합니다.
-- WHERE 절에서 사용
SELECT * FROM products
WHERE category_id IN (SELECT category_id FROM categories WHERE active = 1);
-- SELECT 절에서 사용 (스칼라 서브쿼리)
SELECT name,
(SELECT AVG(price) FROM products) AS avg_price
FROM products;
15. 테이블 조인 (JOIN)
여러 테이블을 연결하여 데이터를 검색합니다. ON 절을 사용하여 조인 조건을 지정합니다. 적절한 인덱스 없이 조인을 많이 사용하면 성능이 저하될 수 있습니다.
내부 조인 (INNER JOIN)
SELECT p.name, c.name AS category_name
FROM products p
INNER JOIN categories c ON p.category_id = c.category_id;
자체 조인 (Self-Join)
-- 같은 부서에 있는 직원 찾기
SELECT e1.name AS employee, e2.name AS colleague
FROM employees e1
INNER JOIN employees e2 ON e1.department_id = e2.department_id AND e1.employee_id != e2.employee_id;
자연 조인 (NATURAL JOIN)
-- 동일한 이름을 가진 컬럼을 자동으로 연결
SELECT p.name, c.name FROM products p NATURAL JOIN categories c;
외부 조인 (OUTER JOIN)
-- 왼쪽 테이블의 모든 행을 유지 (짝이 없는 오른쪽 테이블 값은 NULL)
SELECT c.name, o.order_id
FROM customers c
LEFT OUTER JOIN orders o ON c.cust_id = o.cust_id;
16. 집합 연산 (UNION)
두 개 이상의 SELECT 결과를 하나로 결합합니다. 각 쿼리는 동일한 개수의 컬럼을 가져야 합니다. UNION ALL은 중복을 제거하지 않습니다.
SELECT name, 'VIP' AS status FROM vip_customers
UNION ALL
SELECT name, 'Regular' FROM regular_customers;
17. 뷰 (View)
저장된 쿼리 결과를 가상의 테이블처럼 사용합니다. 데이터를 물리적으로 저장하지 않으며, 복잡한 쿼리를 단순화하고 보안을 강화하는 데 유용합니다.
CREATE VIEW product_summary AS
SELECT p.name, c.name AS category_name, p.price
FROM products p
JOIN categories c ON p.category_id = c.category_id
WHERE p.active = 1;
18. 저장 프로시저 (Stored Procedure)
하나의 이름으로 저장된 SQL 문들의 집합입니다. 미리 컴파일되어 실행 속도가 빠르고, 코드 재사용성과 보안을 높일 수 있습니다.
DELIMITER //
CREATE PROCEDURE GetProductCount(OUT total INT)
BEGIN
SELECT COUNT(*) INTO total FROM products;
END //
DELIMITER ;
CALL GetProductCount(@count);
SELECT @count;
19. 커서 (Cursor)
저장 프로시저 내에서 쿼리 결과 집합을 한 행씩 처리할 때 사용합니다. 명시적으로 선언, 열기, 인출, 닫기 과정을 거쳐야 합니다.
DELIMITER //
CREATE PROCEDURE ProcessProducts()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE p_name VARCHAR(100);
DECLARE cur CURSOR FOR SELECT name FROM products;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO p_name;
IF done THEN
LEAVE read_loop;
END IF;
-- 각 상품 이름을 처리
END LOOP;
CLOSE cur;
END //
DELIMITER ;
20. 트리거 (Trigger)
특정 테이블에서 INSERT, UPDATE, DELETE 이벤트가 발생할 때 자동으로 실행되는 코드입니다. 데이터 무결성 유지, 감사 로그 기록 등에 사용됩니다.
CREATE TRIGGER before_product_update
BEFORE UPDATE ON products
FOR EACH ROW
SET NEW.updated_at = NOW();
21. 트랜잭션 (Transaction)
하나의 논리적 작업 단위를 구성하는 SQL 문의 그룹입니다. COMMIT으로 영구 저장하고, ROLLBACK으로 작업을 취소할 수 있습니다. SAVEPOINT를 사용하면 트랜잭션 내 특정 지점으로만 롤백할 수 있습니다.
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE user_id = 2;
COMMIT;
-- 문제 발생 시: ROLLBACK;
22. 문자 집합 및 데이터 정렬 (Charset & Collation)
문자 집합은 데이터를 표현하는 문자들의 집합이며, 데이터 정렬(Collation)은 문자 비교 및 정렬 규칙을 정의합니다. 테이블이나 컬럼 레벨에서 설정할 수 있습니다.
CREATE TABLE articles (
title VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
) DEFAULT CHARACTER SET utf8mb4;
23. 사용자 및 권한 관리
데이터베이스 보안을 위해 사용자 계정을 생성하고 권한을 부여하거나 제거합니다.
-- 사용자 생성
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'secure_pass';
-- 권한 부여
GRANT SELECT, INSERT, UPDATE ON shop.* TO 'app_user'@'localhost';
-- 권한 제거
REVOKE DELETE ON shop.* FROM 'app_user'@'localhost';
-- 권한 확인
SHOW GRANTS FOR 'app_user'@'localhost';
-- 비밀번호 변경
SET PASSWORD FOR 'app_user'@'localhost' = 'new_secure_pass';