SQL 문법 총 정리! - SQL에서 응용이 가능한 대부분의 명령어들
SQL 문법이라고 한다면, 제일 기본적인 언어로 소개되는 3가지 분류가 있습니다.
이번 글의 시작은 기초라고 할 수 있는 그 세가지 문법에 대해서 설명하면서 시작하고자 합니다.
※ 간단 Tip, ( Ctrl + K + C ) → 드래그 범위 주석 처리 / (Ctrl + K + U ) → 드래그 범위 주석 해제
📌 1. 데이터 정의 언어 (DDL)
먼저, DDL은 데이터 정의 언어로 데이터베이스의 구조를 정의하거나 수정하는 데 사용하는 명령어들을 얘기합니다.
간단하게 테이블을 만들고, 수정하고, 삭제하는 형태의 쿼리들을 DDL이라고 볼 수 있습니다.
바로가기 - SQL 문법 분류: DDL, DML, DCL (+ 트랜잭션 제어)
1️⃣ CREATE - 테이블 or DB 생성
-- Example
CREATE DATABASE 데베명
CREATE TABLE 테이블명 (
필드명 자료형,
필드명 자료형,
필드명 자료형
-- , PRIMARY KEY(필드명, 필드명) 으로 복합 기본키 설정이 가능.
);
-- 자료형 뒤에 PRIMARY KEY가 들어갈 수 있음.
-- 만약 NOT NULL을 넣는다면, PRIMARY KEY는 기본적으로 NOT NULL 이지만,
-- 그래도 굳이 넣고 싶다면 NOT NULL PRIMARY KEY 순서로 적어서 넣을 수 있음.
--
-- Simple Example
CREATE DATABASE database_name; -- 데이터베이스 생성
CREATE TABLE table_name ( -- 테이블 생성
int_id INT PRIMARY KEY,
string_field NVARCHAR(50),
date_field DATE
);
새로운 DB를 생성하거나, 테이블을 생성한다.
2️⃣ ALTER - 테이블 구조 변경
-- Example
ALTER TABLE 테이블명 ADD 필드명 자료형 -- 필드 추가
ALTER TABLE 테이블명 DROP COLUMN 필드명 -- 필드 제거
-- Simple Example
ALTER TABLE table_name ADD int_field2 INT; -- 정수 필드 추가
ALTER TABLE table_name DROP COLUMN int_field2; -- 정수 필드 제거
DB, 테이블에 대한 내용을 수정한다.
3️⃣ DROP - 테이블 or DB 삭제
-- Example
DROP TABLE 테이블명 -- 테이블 삭제
DROP DATABASE 데베명 -- 데이터베이스 삭제
-- Simple Example
DROP TABLE table_name; -- 테이블 삭제
DROP DATABASE database_name; -- DB 삭제
DB, 테이블을 삭제한다.
내용만 삭제하는 명령어가 아닌 테이블과 DB 자체를 삭제하기 때문에 주의를 요함.
4️⃣ TRUNCATE - 테이블 내용만 삭제
TRUNCATE TABLE 테이블명 -- 테이블 내용 삭제
TRUNCATE TABLE table_name -- 해당 테이블 내용 삭제 (구조 유지)
DROP 명령어와 다르게 DB 혹은 테이블 내의 내용만을 삭제한다.
5️⃣ RENAME - 객체 이름 변경
EXEC sp_raname '기존 테이블명', '바꿀 테이블명' -- 바꿀 테이블명 으로 테이블 이름 변경
EXEC sp_raname 'old_name_table', 'new_name_table' -- new_name_table 로 테이블 이름 변경
※ SQL Server에서는 별도의 명령어 대신 시스템 저장 프로시저를 사용한다.
📌 2. 데이터 조작 언어 (DML)
데이터를 조작하는 데 사용되는 SQL 명령어입니다.
데이터를 삽입, 수정, 삭제, 조회하는 것들로 SQL에서 많이 사용되는 명령어입니다.
1️⃣ SELECT - 데이터 조회
-- Example 1 -- 테이블의 모든 데이터 상위 10개를 조회
SELECT TOP 10 * FROM 테이블명
-- Example 2
SELECT 필드1, 필드2, 필드3 ... -- 보고 싶은 필드
FROM 테이블명 -- 참조할 테이블
WHERE 조건 -- 조건식
ORDER BY 필드1 [ASC, DESC], 필드2 [ASC, DESC] -- 정렬할 필드
-- Simple Example 2
SELECT int_field, string_field
FROM Table_name
WHERE int_field > 5
-- Example 3 -- 페이징 처리
SELECT * FROM 테이블명
ORDER BY 필드
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
-- OFFSET: 건너뛸 행 수 / FETCH NEXT: 가져올 행 수
-- ORDER BY 가 무조건 필요합니다. 건너뛰기 전 조건이 필요하기 때문.
-- Simple Example 3
SELECT * FROM table_name -- table_name 테이블에서 모든 값을 호출
ORDER BY int_field -- int_field 필드를 기준으로 값을 정렬
OFFSET 3 ROWS
FETCH NEXT 2 ROWS ONLY
-- 정렬된 값을 기준으로 3개의 값을 건너뛰고, 2개의 값만 호출
테이블에서 데이터를 조회합니다.
2️⃣ INSERT - 데이터 삽입
-- Example 1
INSERT INTO 테이블명 (필드1, 필드2) -- 테이블과 필드를 대조
VALUES (값1, 값2) -- 적어둔 필드의 순서에 따라 값을 적용
-- Example 2
INSERT INTO 테이블명 -- 테이블만 대조하고 필드를 자동으로 가져옴
VALUES (값1, 값2) -- 테이블의 필드 설정 값에 따라 값을 적용
-- Simple Example
INSERT INTO table_name (int_field, string_field)
VALUES (1, N'파이썬')
새로운 데이터를 추가합니다.
필드에 맞춰서 값을 적어줘야하며,
예제 1번의 경우 테이블에 있는 모든 필드를 적지 않아도 값을 넣을 수 있습니다.
만약 모든 필드를 가져오지 않아 다른 필드에 공백이 생길 경우 NULL 값으로 자동 등록됩니다.
3️⃣ UPDATE - 데이터 수정
-- Example
UPDATE 테이블명 -- 값을 수정 할 테이블 명을 대조
SET 필드1 = 값1, 필드2 = 값2 -- 변경하고 싶은 필드의 값을 입력
WHERE 조건 -- 변경할 값을 받을 조건을 작성
-- ※ WHERE에 조건이 없을 경우, 테이블 내의 모든 값이 변경됩니다.
-- Simple Example
UPDATE table_name
SET int_field = 5, string_filed = N'사실자바'
WHERE int_field = 1
기존 데이터를 수정합니다.
만약 WHERE가 없으면 모든 값이 수정됩니다.
4️⃣ DELETE - 데이터 삭제
-- Example
DELETE FROM 테이블명 -- 값을 제거할 테이블을 대조
WHERE 조건 -- 조건에 맞는 값들을 모조리 제거
-- ※ 만약에 WHERE에 조건이 없을 경우, TRUNCATE TABLE을 사용한 것처럼 모두 초기화.
-- Simple Example
DELETE FROM table_name
where int_field = 5
특정 데이터를 삭제합니다.
만약 WHERE가 없으면 모든 값이 삭제됩니다.
📌 3. 데이터 제어 언어 (DCL)
DCL은 데이터베이스에 대한 접근 권한을 제어하는 명령어입니다.
쉽게 말해서, 누가 어떤 데이터에 접근하고 조작할 수 있는지를 설정하거나 해제하는 역할을 할 수 있습니다.
1️⃣ GRANT - 권한 부여
-- Example
GRANT 권한 ON 테이블명 TO 사용자
-- Simple Example
GRANT SELECT, INSERT ON table_name TO user1
-- user1은 table_name 테이블에 대해 SELECT, INSERT 권한을 부여받습니다.
데이터에 대한 권한을 부여합니다.
2️⃣ REVOKE - 권한 취소
-- Example
REVOKE 권한 ON 테이블명 FROM 사용자
-- Simple Example
REVOKE INSERT ON table_name FROM user;
-- user1의 INSERT 권한을 회수합니다.
데이터에 대한 권한을 취소합니다.
3️⃣ COMMIT - 변경 사항 확정
BEGIN TRAN
UPDATE table_name SET int_field = 2 WHERE int_field = 1
COMMIT
-- 보통 COMMIT 선언을 하면 수정한 내용을 되돌릴 수 없다.
데이터베이스에 실행한 변경 사항을 최종적으로 승인하는 느낌의 명령어.
4️⃣ ROLLBACK - 변경 사항 되돌리기
BEGIN TRAN -- 롤백 지점 / 롤백을 사용할 경우 이 위치로 이동.
UPDATE table_name SET int_field = 2 WHERE int_field = 1
ROLLBACK
-- 바로 업데이트 받아치기! / 하지만 COMMIT 선언을 했다면 되돌릴 수 없음
만약 최종 승인을 내리지 않았다면, 수정한 내용을 되돌릴 수 있습니다.
✅ 트랜잭션 제어 명령어 (TCL)
사실 COMMIT, ROLLBACK은 DCL이라고 보기 보다는 TCL에 분류됩니다.
하지만 실제 권한/데이터 변경 작업과 함께 자주 쓰이기 때문에 함께 설명하는 경우가 많습니다.
또한, 교육을 진행하는 책에서도 같이 묶여서 설명이 진행되고 있기에 저희 또한 똑같이 진행하겠습니다.
5️⃣ 간단한 ROLLBACK 심화
BEGIN TRAN
UPDATE table_name SET int_field = 2 WHERE int_field = 1
SAVE TRAN step1 -- SAVE 지점 / step1 이라는 이름의 저장지점
DALETE FROM table_name WHERE int_field = 2
ROLLBACK TRAN step1
COMMIT
-- 결과적으로 DELETE는 취소되고, UPDATE는 유지.
SQL Server에서는 트랜잭션을 중첩해도 실제로는 하나의 물리적 트랜잭션만 존재합니다.
하지만 SAVEPOINT를 사용하면 중간 지점으로 부분 롤백이 가능합니다.
꼭 게임에서의 세이브 포인트 같은 느낌이죠?
📌 4. 와일드카드로 문자열 검색하기
SELECT문에서 LIKE는 문자열 패턴 매칭(부분 검색)을 할 때 사용하는 연산자입니다.
예를 들어, 이름에 '김'이 들어간 사람을 찾는다거나, 어떠한 단어로 시작하는 문자열을 찾는다고 할 때 유용하게 사용할 수 있습니다.
0️⃣ SELECT문 - LIKE 기본 문법
SELECT 필드
FROM 테이블명
WHERE 필드 LIKE '패턴'
❓ 와일드 패턴은 무엇이 있나요
패턴 기호 | 의미 | 예시 |
% | 0개 이상의 문자 | '김%' → "김"으로 시작하는 값들 |
_ | 정확히 한 개의 문자 | '이_희' → "이OO희" (2글자 사이 문자) |
[] | 지정한 문자 중 하나 | '[김이박]%' → 김, 이, 박으로 시작하는 |
[^] | 지정한 문자를 제외한 하나 | '[^김]%' → "김"으로 시작하지 않는 |
1️⃣ 예제 : 특정 글자로 시작할 때
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파%'
string_field 필드 안의 값 중, '파' 로 시작하는 데이터만 호출
2️⃣ 예제 : 특정 글자로 끝날 때
SELECT string_field
FROM table_name
WHERE string_field LIKE N'%이썬'
string_field 필드 안의 값 중, '이썬' 으로 끝나는 데이터만 호출
3️⃣ 예제 : 특정 글자를 포함할 때
SELECT string_field
FROM table_name
WHERE string_field LIKE N'%이%'
string_field 필드 안의 값 중, '이' 라는 글자를 포함하고 있는 데이터만 호출
4️⃣ 예제 : 자리 수 고정 검색
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파__'
string_field 필드 안의 값 중, '파' 로 시작하고 3글자인 데이터만 호출
5️⃣ 예제 : ESCAPE 문법을 사용하여 패턴에 포함되는 글자 검색
SELECT string_field
FROM table_name
WHERE string_field LIKE '%50!%%' ESCAPE '!'
-- ESCAPE 안에는 단 한글자만 들어갈 수 있음
string_field 필드 안의 값 중, '50%'를 포함하고 있는 데이터만 호출
ESCAPE '!' 의 영향으로 50%라고 인식되는 것이다.
6️⃣ 예제 : 특정 글자로 시작하고, 그 다음에는 특정 글자가 와야하며, 자리수가 정해진 단어 찾기
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파%이_'
string_field 필드 안의 값 중, '파' 로 시작하고 두번째 글자가 '이' 이며 세글자인 데이터만 호출
7️⃣ 예제 : 특정 글자로 시작하고, 그 다음에는 복수의 특정 글자 중 한 글자가 오는 단어 찾기
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파[파, 이, 썬]%'
string_field 필드 안의 값 중, '파'로 시작하고 두번째 글자가 '파', '이', '썬' 중 한 글자가 오는 모든 데이터를 호출
8️⃣ 예제 : 특정 글자로 시작하고, 그 다음에는 복수의 특정 글자 중 한 글자도 오지 않는 단어 찾기
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파[^자,^바]%'
string_field 필드 안의 값 중, '파'로 시작하고 두번째 글자가 '자', '바' 중 한 글자도 오지 않는 모든 데이터를 호출
9️⃣ 예제 : 와일드카드 응용 1
SELECT string_field
FROM table_name
WHERE string_field LIKE N'파[이,썬][^자,^바]%썬'
string_field 필드 안의 값 중, '파'로 시작하고 두번째 글자가 '이', '썬' 중 한 글자가 오고, 세번째 글자가 '자', '바' 중 한 글자가 오지 않으며, 썬으로 끝나는 모든 데이터를 호출
🔟 예제 : 와일드카드 응용 2
SELECT string_field
FROM table_name
WHERE string_field LIKE N'자바%[부]_%다'
string_field 필드 안의 값 중, '자바'로 시작하고 '부' + [한글자] 를 포함하고 있으며, '다' 로 끝나는 데이터를 모두 호출
📌 5. 데이터 그룹화 (GROUP BY, HAVING)
데이터를 그룹으로 묶어서 요약하는 방법으로 GROUP BY, HAVING이 있습니다.
이 둘은 집계 함수(SUM, COUNT, AVG 등)와 함께 사용되며, 데이터 분석에 있어 핵심적인 도구입니다.
1️⃣ GROUP BY에 대해서
비슷한 값을 가진 행을 하나의 그룹으로 묶고, 그 그룹마다 집계 연산을 적용할 때 사용합니다.
-- Example
SELECT 기준필드, 집계함수
FROM 테이블명
GROUP BY 기준필드
-- Simple Example
SELECT dept_id, AVG(salary) AS avg_salary -- dept_id : 부서 번호 / avg_salary : 해당 부서의 평균 연봉
FROM employees
GROUP BY dept_id -- dept_id 그룹화
-- 부서별로 그룹화를 하고, avg_salary에서 부서별 평균 연봉을 출력한다.
2️⃣ HAVING에 대해서
-- Example
SELECT 기준필드, 집계함수
FROM 테이블명
GROUP BY 기준필드
HAVING 집계함수조건
-- Simple Example
SELECT dept_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_id
HAVING AVG(salary) >= 5000
-- 부서별 평균 연봉이 5000(단위:만) 이상인 부서만 호출
HAVING은 그룹핑(GROUP BY)된 결과에 조건을 걸 때 사용하는 절입니다.
즉, WHERE이 행을 필터링한다면, HAVING는 그룹을 필터링 하는 명령어입니다.
※ 절(Clause) : SQL문을 구성하는 각 구간 혹은 단위를 말함, 절이 모여 하나의 쿼리를 구성.
3️⃣ WHERE와 HAVING를 모두 사용하는 경우
SELECT dept_id, COUNT(*) AS emp_count -- 부서 번호를 조회하여 emp_count라는 필드를 만들어 부서별 인원수를 호출
FROM employees
WHERE position = N'사원' -- 각 부서의 인원을 호출할 때, 등급이 '사원'인 사람만 수를 센다.
GROUP BY dept_id -- 부서 번호를 기준으로 그룹화
HAVING COUNT(*) > 5;
-- 부서 번호를 기준으로 '사원' 등급 이상인 사람들만 호출하여 emp_count에 출력한다.
-- 이후 HAVING에서 dept_id를 기준으로 '사원' 등급의 인원이 5명 이상인 부서만 호출한다.
✅ 절에 대한 간단한 설명
SELECT dept_id, COUNT(*) AS emp_count -- SELECT 절
FROM employees -- FROM 절
WHERE position = N'사원' -- WHERE 절
GROUP BY dept_id -- GROUP BY 절
HAVING COUNT(*) > 5; -- HAVING 절
-- 해당 절들이 모여 1-5줄까지를 통틀어 쿼리라고 부릅니다.
-- SQL 엔진이 처리할 때, 일반적으로 실행되는 순서
1. FROM
2. JOIN (필요 시)
3. WHERE
4. GROUP BY
5. HAVING
6. SELECT
7. ORDER BY
8. LIMIT / OFFSET / TOP
📌 6. 중복 제거 (DISTINCT)
-- Example
SELECT DISTINCT 필드
FROM 테이블명
-- Simple Example
SELECT DISTINCT dept_id
FROM employees
-- employees 테이블에서 dept_id가 중복되더라도
-- 고유한 부서 ID 목록만 호출하여 출력.
-- Simple Example 2
SELECT DISTINCT dept_id, position
FROM employees
-- 필드의 개수가 복수일 경우,
-- 호출한 모든 열의 조합이 동일해야 중복으로 간주한다.
DISTINCT는 모든 열을 기준으로 판단하기 때문에,
SELECT DISTINCT * 는 모든 필드가 동일해야만 제거합니다.
많은 필드를 포함한 경우 성능 저하를 일으킬 수 있으니 주의해서 사용해야함.
정렬을 하고싶다면 ORDER BY와 함께 사용이 가능합니다.
📌 7. 외래키(Foreign Key)를 이용하기
외래키는 다른 테이블의 기본키(Primary Key)를 참조하는 제약 조건입니다.
"이 값은 다른 테이블에 실제 존재하는 값이어야 한다."는 걸 보장해주는 기능입니다.
1️⃣ 외래키 사용을 위한 테이블 생성해보기
✅ departments 테이블 (부모 테이블)
CREATE TABLE departments (
dept_id INT PRIMARY KEY, -- dept_id에 PRIMARY KEY(기본키)를 지정한 것을 볼 수 있다.
dept_name NVARCHAR(50)
)
✅ employees 테이블 (자식 테이블, 외래키 사용)
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
name NVARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
-- 해당 테이블의 부서 번호(dept_id)는 부모 테이블(departments)의 부서 번호를 참조.
-- 즉, 부모 테이블(departments)에 먼저 새로운 부서가 없는 이상
-- 자식 테이블(employees)에 새로운 부서를 넣으면 에러가 생김.
)
2️⃣ 부모 테이블과 자식 테이블 데이터를 입력하는 순서
1. 부모 테이블(departments)에서 부서를 먼저 생성
INSERT INTO departments (dept_id, dept_name)
VALUES (1, N'개발팀'), (2, N'인사팀')
-- 먼저 개발팀(1)과 인사팀(2)을 신설합니다.
만약에 자식 테이블에서 부서가 존재하지 않는 부서번호(dept_id)를 입력하면,
오류가 발생하는 것을 확인할 수 있습니다.
2. 자식 테이블에 사원의 부서와 사원 번호를 입력
INSERT INTO employees (emp_id, name, dept_id)
VALUES (101, N'홍길동', 1), (102, N'이순신', 2)
-- 이러면
-- ( 사원번호 101, 개발팀 홍길동 )
-- ( 사원번호 102, 인사팀 이순신 )
-- 위 내용으로 등록되는 것이다.
3️⃣ 외래키는 삭제에도 순서가 있다?
DELETE FROM departments WHERE dept_id = 1
-- 해당 쿼리는 departments 테이블에서 개발팀을 없애버린다는 쿼리입니다.
-- 하지만 employees 테이블에서 홍길동 사원이 개발팀에 참조되어 있어서,
-- 오류가 발생하고 삭제되지 않는 것을 확인할 수 있습니다.
-- 만약 삭제를 하고 싶다면 employees 테이블에서 홍길동 사원의 부서를 변경하거나 삭제하고,
-- departments 테이블에서 개발팀을 삭제해야 정상적으로 삭제될 것입니다.
부모 테이블인 부서테이블(departments)에서 부서를 삭제하려면,
해당 부서에 사원들이 없어야만 삭제가 가능합니다.
자식 테이블에서 참조를 하지 않고 있어야 삭제가 가능하기 때문입니다.
📌 8. 데이터 베이스의 꽃, 조인(Join) 사용해보기
SQL에서 사용하는 조인(JOIN)이란, 두 개 이상의 테이블을 특정 조건에 따라 결합해서 하나의 결과를 보여주는 기능입니다.
이를 쉽게 표현하자면, "이 테이블에는 직원 이름이 있고, 저 테이블에는 부서명이 있다면, 직원과 부서를 연결해서 보여줘!"
→ 같은 형태를 JOIN이라고 합니다.
0️⃣ JOIN은 왜 필요합니까?
정규화된 데이터베이스는 데이터를 나눠서 저장합니다.
→ 직원 정보는 employees 테이블에,
→ 부서 정보는 departments 테이블에 저장되어 있다고 쳐봅시다.
이럴 때 "직원이 어떤 부서에 속했는지" 같은 정보는 두 테이블을 동시에 봐야 알 수 있습니다.
이러한 데이터를 처리하기 위해서 필요한 것이 JOIN의 역할이라고 볼 수 있습니다.
1️⃣ INNER JOIN - 테이블의 기준 값이 서로 같아야함 (교집합 조인)
-- Example
SELECT A.*, B.*
FROM 테이블명1 AS A -- 여기서 선언한 A가 위의 SELECT에서 호출됨.
INNER JOIN 테이블명2 AS B ON A.동일필드 = B.동일필드 -- 여기서 선언한 B가 위에서 호출됨
-- 테이블명1과 테이블명2에 똑같이 존재하는 필드를 기준으로
-- 둘이 서로 같은 필드에서 같은 값이 존재할 경우 데이터를 호출합니다.
-- 그리고 호출한 데이터를 SELECT절에서 출력합니다.
-- Simple Example : 직원 + 부서 + 지역 이름 한번에 보기
SELECT e.name, d.dept_name, l.city -- e 테이블의 이름, d 테이블의 부서명, l 테이블의 도시 호출
FROM employees AS e -- employees 테이블을 e로 선언
INNER JOIN departments AS d ON e.dept_id = d.dept_id
-- departments 테이블을 d로 선언하고, 부서 번호(d.dept)를 기준으로 데이터를 호출
INNER JOIN locations AS l ON d.location_id = l.location_id
-- locations 테이블을 l로 선언하고, 지역 번호(d.location_id)를 기준으로 데이터를 호출
WHERE l.city = 'Seoul'; -- 대신 지역 번호(d.location_id)를 받을 지역을 서울로 제한함.
-- 해당 예제에서 여러 테이블을 연쇄적으로 조인하여 확장된 정보를 통합해보았습니다.
-- INNER JOIN은 반드시 조건에 맞는 행만 반환되므로 필터링이 되는 효과도 있습니다.
양쪽 테이블에서 조인 조건에 일치하는 행만 결과로 가져옵니다.
두 테이블의 기준이 되는 값이 서로 일치하지 않으면 호출하지 않습니다.
2️⃣ LEFT OUTER JOIN - 왼쪽 테이블을 기준으로 오른쪽 테이블에 일치하는 값을 호출
-- Example
SELECT A.*, B.*
FROM 테이블명1 AS A
LEFT OUTER JOIN 테이블명2 AS B ON A.동일필드 = B.동일필드
-- Simple Example : 부서가 없는 직원도 포함하여, 누락된 직원을 출력
SELECT e.name, d.dept_name -- 직원 이름과 부서 이름만을 호출
FROM employees AS e
LEFT JOIN departments AS d ON e.dept_id = d.dept_id;
-- 부서 번호(e.dept_id)를 기준으로 호출하지만,
-- LEFT 조인이라서 employees 테이블을 기준으로 데이터를 호출합니다.
-- 그렇기 때문에 부서 번호(dept_id)가 없는 사람의 데이터도 호출되는 모습을 볼 수 있습니다.
교집합 조인이 기준이 되는 값이 두 테이블이 서로 같아(A=B)야 했다면,
LEFT 조인은 왼쪽 테이블(A)의 모든 행을 호출하고, 오른쪽 테이블(B)을 A에 맞춰서 값을 호출.
만약 값이 없을 경우 A의 해당 필드 값은 NULL이 됩니다.
3️⃣ RIGHT OUTER JOIN - 오른쪽 테이블을 기준으로 왼쪽 테이블에 일치하는 값을 호출
SELECT A.*, B.*
FROM 테이블명1 AS A
RIGHT OUTER JOIN 테이블명2 AS B ON A.동일필드 = B.동일필드
LEFT 조인이 A가 기준이었다면,
RIGHT 조인은 B가 기준.
4️⃣ FULL OUTER JOIN - 양쪽의 모든 값을 호출 (합집합 조인)
-- Example
SELECT A.*, B.*
FROM 테이블명1 AS A
FULL OUTER JOIN 테이블명2 AS B ON A.id = B.id;
-- Simple Example : 부서가 없는 직원이 있거나, 부서 정보는 있는데 직원의 정보가 없는 경우
SELECT e.name, d.dept_name
FROM employees AS e
FULL OUTER JOIN departments AS d ON e.dept_id = d.dept_id;
-- 이런 조건으로 왜 호출을 하는가?
-- 조인 조건이 맞는 것도, 맞지 않는 것도 모두 보고 싶을 때 사용합니다.
-- 예를 들어서, 부서 없이 등록된 직원이 있거나,
-- 부서가 등록되었지만 아직 직원이 배정되지 않은 부서가 있을 경우
-- 이런 누락된 데이터를 확인하기 위해서 사용하기 좋습니다.
일단 기준을 맞춰서 기준의 값이 같으면 그거에 맞춰서 데이터를 넣어주긴 할건데,
없어도 넣어줄거고 다 가져온다는 마인드.
5️⃣ CROSS JOIN - 곱집합 조인
-- Example
SELECT A.*, B.*
FROM 테이블명1 AS A
CROSS JOIN 테이블명2 AS B;
-- Simple Example : 모든 직원 × 모든 근무일 확인
SELECT e.name, w.work_date
FROM employees AS e
CROSS JOIN work_days AS w;
-- 이처럼 출근 체크표를 초기화하기 위해서 호출한다거나,
-- 상품과 옵션 조합을 생성해보거나,
-- 일정 템플릿을 미리 구성해볼 때 사용하기 좋습니다.
양쪽 테이블의 모든 행 조합을 출력합니다.
조건 없이 → A테이블의 행 수 × B테이블의 행 수
📌 9. 한 단계 더 나아가서! 서브쿼리 사용해보기
서브쿼리는 SELECT문 안에서 또 다른 SELECT문을 포함한 쿼리입니다.
괄호 안에 들어가고, 결과를 외부 쿼리의 조건으로 사용할 수 있습니다.
1️⃣ WHERE 절에서 사용하는 서브쿼리
-- Example
SELECT 필드
FROM 테이블명
WHERE 필드 비교연산자 (
SELECT ...
FROM ...
WHERE ...
);
✅ 단일 값만 비교할 때, =
-- Simple Example : 개발팀인 사원만 조회합니다.
SELECT name
FROM employees
WHERE dept_id = (
SELECT dept_id
FROM departments
WHERE dept_name = N'개발팀'
);
-- departments 테이블에서 부서 이름(dept_name)이 개발팀인 부서 번호(dept_id)를 호출
-- employees 테이블에서 같은 부서 번호(dept_id)를 가진 사원을 조회합니다.
-- ※ 서브쿼리의 결과는 단 1개만 호출되어야 합니다. 복수라면 오류가 발생합니다.
-- departments 테이블에서 개발팀이라는 이름을 가진 부서의 행은 단 1개라서 호출이 가능하지만,
-- 만약 행이 2개 이상이라면 오류가 발생하게됩니다.
해당 쿼리는 먼저 괄호() 안에 있는 것이 계산됩니다.
1. SELECT 부서번호 FROM 부서테이블 WHERE 부서이름 = 개발팀
→ 개발팀의 부서번호요? 1번이 개발팀 부서번호 입니다. / 이런 느낌으로 1을 호출합니다.
2. SELECT 이름 FROM 사원테이블 WHERE 부서번호 = 1(개발팀번호)
→ 개발팀인 사원의 목록을 전부 호출합니다.
✅ 2개 이상의 값을 비교할 때, IN
SELECT name
FROM employees
WHERE dept_id IN (
SELECT dept_id
FROM departments
WHERE location = N'서울'
);
-- departments 테이블에서 서울(location)에서 근무하는 부서 번호(dept_id)를 호출합니다.
-- 한 지역에서 근무하는 부서이기 때문에, 2개 이상의 부서가 나올 확률이 높습니다.
-- employees 테이블에서 서울에서 근무하는 부서 직원 이름(name)을 호출합니다.
-- 이걸로 서울에서 근무하고 있는 부서의 직원들을 조회할 수 있습니다.
1. SELECT 부서번호 FROM 부서테이블 WHERE 지역 = 서울
→ 서울에서 근무하고 있는 부서번호는 1(개발팀), 2(인사팀) 입니다.
2. SELECT 이름 FROM 사원테이블 WHERE 부서번호 IN 1(개발팀), 2(인사팀)
→ 개발팀과 인사팀에 소속되어 있는 직원들을 호출합니다.
→ 결론적으로 서울에서 근무중인 직원들을 조회할 수 있게 됩니다.
※ 저기서 IN 대신에 NOT IN이 들어가게 되면, 서울에서 근무하지 않는 직원들을 조회할 수 있습니다.
✅ 결과가 1개라도 존재한다면, EXISTS
SELECT name
FROM employees AS e
WHERE EXISTS (
SELECT 1
FROM departments AS d
WHERE d.dept_id = e.dept_id
AND d.location = N'서울'
);
-- 위와 같은 서울에서 근무하는 직원의 이름을 조회하는 쿼리입니다.
-- 다른 점은 WHERE에서 모든 것을 해결한다는 것입니다.
-- EXISTS는 실제 SELECT 1처럼 어떤 값을 반환하는지가 중요하지 않습니다.
-- 그저 조건에 맞는 행이 존재하느냐만을 판단합니다.
-- 즉 SELECT에는 무엇이 들어가든 정상적으로 작동하기 때문에 중요하지 않습니다.
1. FROM employees AS e WHERE EXISTS
( ~ FROM departments AS d WHERE d.dept_id = e.dept_id AND d.location = N'서울');
→ 중요한 것은 EXISTS 안에 있는 WHERE 절입니다.
→ departments 테이블의 부서 번호(dept_id)와 일치해야하고, 지역(location)이 서울인 사원이 조건입니다.
2. SELECT name
→ 위 조건에 맞는 사원의 이름을 조회합니다.
✅ ANY의 기본적인 사용 예시
-- Simple Example : 급여가 5000 or 6000 or 7000(만) 보다 큰 사람
SELECT name
FROM employees
WHERE salary > ANY (
SELECT salary
FROM employees
WHERE salary >= 5000
);
-- 예를 들어서 서브 쿼리 안의 결과가 5000, 6000, 7000이라고 가정해보겠습니다.
-- 그럼 Salary > ANY (5000, 6000, 7000)이 되는데,
-- Salary > 5000 OR Salary > 6000 OR Salary > 7000 같은 형태로 풀어서 볼 수 있습니다.
-- 네, 그렇습니다. 사실 이거는 WHERE salary > 5000이랑 결과가 다를바가 없었습니다.
-- 그냥 ANY가 이런 식으로 풀어진다는 것만 확인해주시면 됩니다.
가지고 있는 값들 중, 최소 하나보다 큰 값을 호출합니다.
✅ ALL의 기본적인 사용 예시
-- Simple Example : 급여가 5000 and 6000 and 7000(만) 보다 큰 사람
SELECT name
FROM employees
WHERE salary > ALL (
SELECT salary
FROM employees
WHERE salary >= 5000
);
-- 예를 들어서 서브 쿼리 안의 결과가 5000, 6000, 7000이라고 가정해보겠습니다.
-- 그럼 Salary > ALL (5000, 6000, 7000)이 되는데,
-- Salary > 5000 AND Salary > 6000 AND Salary > 7000 같은 형태로 풀어서 볼 수 있습니다.
-- 네, 그렇습니다. 사실 이거는 WHERE salary > 7000이랑 결과가 다를바가 없었습니다.
-- 그냥 ALL이 이런 식으로 풀어진다는 것만 확인해주시면 됩니다.
가지고 있는 값들 전부랑 비교해도 TRUE가 나와야함
✅ 그 이외로
= ANY는 사실 IN과 동일하기 때문에 굳이 따로 사용해주지 않아도 괜찮습니다.
!= ANY 또한 NOT IN과 동일합니다.
2️⃣ FROM 절에서 사용하는 서브쿼리
-- Example
SELECT 필드
FROM (
SELECT ...
FROM ...
WHERE ...
) AS 임시테이블명;
"FROM (서브 쿼리) AS 임시테이블명" 형태로 작성되며, 이 서브쿼리 결과를 하나의 임시 테이블로 간주해서 사용합니다.
✅ 예제 1: 평균 급여보다 높은 사원 조회해보기
SELECT e.name, e.salary
FROM employees AS e -- employees 테이블을 e로 선언합니다.
INNER JOIN (
SELECT AVG(salary) AS avg_salary -- 연봉 평균(avg_salary)을 호출합니다.
FROM employees
) AS sub ON e.salary > sub.avg_salary; -- sub라는 이름의 임시 테이블을 작성합니다.
-- sub 테이블은 연봉 평균(avg_salary)보다 높은 연봉을 가진 사람들의 데이터를 가진 테이블입니다.
-- 이렇게 sub 테이블은 연봉 평균보다 높은 연봉을 가진 사람들만 있는 테이블이 됩니다.
-- 값 자체는 기존의 employees와 데이터가 같기 때문에,
-- 교집합으로 연결하면 WHERE salary > avg_salary과 같은 효과를 볼 수 있는 것입니다.
1. SELECT AVG(salary) AS avg_salary FROM employees
→ employees 테이블에서 연봉(salary)의 평균을 구하고, 필드 이름을 avg_salary라고 정하겠습니다.
→ 즉, avg_salary = 연봉 평균 이라는 의미가 됩니다.
2. INNER JOIN () AS sub ON e.salary > sub.avg_salary
→ sub 테이블에 있는 연봉 평균보다 큰 사람들만 교집합으로 골라냅니다.
→ 현재 위 사진같은 구조이기 때문에 교집합을 골라내도 WHERE를 사용한 효과를 볼 수 있는 것이다.
3. SELECT e.name, e.salary
→ 이렇게 평균 연봉보다 높은 사람들의 이름과 연봉을 조회합니다.
✅ 예제 2: 부서별 최고급여를 계산한 후, 이름까지 함께 보기
-- Simple Example : 부서별 최고 급여를 받는 사람을 조회
SELECT e.name, e.salary, t.max_salary
FROM employees AS e -- employees 테이블을 e로 선언.
INNER JOIN (
SELECT dept_id, MAX(salary) AS max_salary -- 부서별 최고 연봉 호출
FROM employees
GROUP BY dept_id -- 그룹 기준은 부서번호
) AS t ON e.dept_id = t.dept_id AND e.salary = t.max_salary;
-- 부서 번호와 최고 연봉을 기준으로 호출
-- ON 조건식이 2개라 헷갈릴 수 있는데
-- 그냥 부서 번호가 있는 놈들을 호출하고
-- 두번째론 먼저 구해둔 연봉 제일 높은 놈을 호출합니다.
-- 하지만 따로따로 호출하면 다른 부서의 연봉 이 비슷한 사람이 잡힐 수가 있습니다.
-- 그렇기 때문에 AND로 부서 번호가 같으면서 연봉 값도 같은 사람을 조건으로 잡은 것입니다.
1. SELECT 부서번호, MAX(연봉) AS 최대연봉 FROM 부서테이블 GROUP BY 부서번호
→ 부서 번호를 기준으로 연봉이 제일 높은 값을 최대 연봉이라는 필드를 만들어 넣습니다.
→ 부서 번호를 기준으로 최대 연봉을 구했기 때문에 부서별 최고 연봉 값이 출력됩니다.
2. INNER JOIN () AS 임시테이블명 ON 부서번호가 같은가? AND 연봉이 최대연봉값이랑 같은가?
→ 결국 얘도 위의 예제의 그림과 같습니다.
→ 교집합으로 값을 걸러내지만 결국 WHERE문을 사용한 것과 비슷합니다.
3. SELECT e.name, e.salary, t.max_salary
→ 최대 연봉을 받는 사람들의 이름과 연봉, 출력한 최대 연봉이 맞는 확인하기 위해서 한 번 더 출력하는 것까지 출력합니다.
3️⃣ SELECT 절에서 사용하는 서브쿼리
SELECT 필드,
(SELECT ... FROM ... WHERE 조건) AS 새_필드명
FROM 테이블명;
각 행마다 특정한 계산 결과를 붙이기 위해서 사용합니다.
예를 들어서 직원의 급여와 평균 급여를 비교한다거나,
직원 테이블에 없는 부서명을 가져올 때 사용하는 식으로 사용할 수 있습니다.
✅ 예제 1: 직원들이 받는 급여와 평균 급여를 같이 조회하기
SELECT name, salary,
(SELECT AVG(salary) FROM employees) AS avg_salary -- SELECT문 서브쿼리
-- 사원 테이블에서 평균 연봉을 구해서 avg_salary 필드로 임시 선언하고 조회.
FROM employees;
1. SELECT AVG(연봉) FROM 사원테이블) AS 평균연봉
→ 평균 연봉을 구해서 임시 필드를 생성함.
2. SELECT 이름, 연봉, 평균연봉 FROM 사원테이블
→ 결국 순서대로 진행되면 위와 같은 간단한 형태가 되는 것을 볼 수 있다.
→ 평범하게 이름과 연봉, 평균 연봉을 조회할 수 있다.
✅ 예제 2: 직원들이 받는 급여 옆에 부서 이름도 띄워서 같이 조회하기
SELECT name, salary,
(SELECT dept_name
FROM departments
WHERE departments.dept_id = employees.dept_id) AS dept_name
-- WHERE 절에서 부서 번호가 같으면 SELECT문을 통해서 부서명(dept_name) 호출
FROM employees;
1. SELECT 부서명 FROM 부서테이블 WHERE 부서번호가 같은가? AS 부서명으로 선언
→ 부서명을 띄우기 위해서 부서테이블에서 서로 필드를 공유하는 부서번호를 비교하여 부서명을 검색.
→ 그렇게 검색된 부서명을 'AS 부서명' 을 사용하여 임시 필드로 부서명을 선언함.
2. SELECT 이름, 연봉, 부서명 FROM 사원테이블
→ 결국 이러한 간단한 모습으로 이름과 연봉, 부서명이 순서대로 출력되는 것을 확인할 수 있다.
✅ 예제 3: 보고있는 이 직원보다 높은 급여를 받는 인원 수 표시
SELECT name, salary,
(SELECT COUNT(*) FROM employees AS e2
WHERE e2.salary > e1.salary) AS higher_salary_count
-- COUNT를 사용하여 WHERE절에서 연봉을 비교하고 TRUE 값의 수를 셈
-- 그것을 higher_salary_count 필드로 임시 선언
FROM employees AS e1;
1. SELECT COUNT(*) FROM 사원테이블 WHERE 이 사원은 다른 사원들보다 연봉이 큰가?
→ WHERE 절에서 두 테이블의 사원을 비교하는 느낌이라고 보면 된다. (사실 이중 FOR문 같은 느낌)
→ 비교하고 TRUE로 나온 값은 COUNT++ 되어 값이 1개씩 증가.
→ 그렇게 증가한 COUNT를 higher_salary_count 라는 임시 필드로 선언
2. SELECT 이름, 연봉, 얘보다많이받는인원 FROM 사원테이블
→ 결국 마지막에는 이렇게 간단한 모습으로 이름과 연봉, 티배깅 필드까지 출력되는 것을 확인할 수 있다.
📌 10. 데이터 베이스에도 함수가 있다고?
1️⃣ 역시 시작은 문자열 연결부터, CONCAT
-- Example
CONCAT(문자열1, 문자열2, 문자열3...)
그냥 간단하게 문자열을 이어붙이는 함수.
NULL이 포함되더라도 무시하지 않고 빈 문자열로 처리됩니다.
✅ 예제 1: 이름 (직책) 형태로 같이 출력하기
SELECT name, position, CONCAT(name, ' (', position, ')') AS display_name
FROM employees;
1. CONCAT(이름, ' (', 직책, ')') as 직책이름
→ 직책이라는 필드에 이제부터 '이름 (직책)' 형식으로 값을 출력합니다
→ 예시 : 홍길동 (의적)
2. SELECT 이름, 직책, 직책이름 FROM 사원테이블
→ 평범한 SELECT절이 된 것을 확인할 수 있다.
✅ 예제 2: 숫자 + 문자열 연결하기
SELECT CONCAT(N'사원번호: ', emp_id) AS label
FROM employees;
1. CONCAT('사원번호: ', 사원번호) AS 라벨
→ 라벨이라는 필드에 '사원번호: 101' 같은 형식으로 값을 출력합니다
2. SELECT 라벨 FROM 사원테이블
→ 평범한 SELECT절이 되어 평범하게 출력하는 것을 확인할 수 있습니다
✅ 예제 3: NULL이 있을 때
SELECT CONCAT(N'이름: ', name, N', 부서: ', dept_name)
FROM employees;
1. CONCAT('이름: ', 이름, ', 부서: ', 부서명)
→ 필드 명은 따로 정하지 않았지만 '이름: 홍길동, 부서: 개발부' 같은 형식으로 값을 출력합니다
2. SELECT 필드명없음 FROM 사원테이블
→ 비록 필드명은 없이 뜨지만 이름과 부서가 정해둔대로 정리되어 출력되는 것을 확인할 수 있습니다
2️⃣ 데이터의 자료형을 변환하자, CAST
CAST(데이터 AS 목표_자료형)
특정 데이터를 원하는 자료형으로 명시적 변환할 수 있게 해줍니다.
→ 명시적 변환이란? 프로그래머가 직접 지정해서 데이터의 타입을 변환하는 것입니다.
✅ 예제 1: 문자열을 정수로 변환할 때
SELECT CAST('123' AS INT) AS int_value
'123' (문자열) → 123 (정수형)
✅ 예제 2: 숫자를 문자열로 변환할 때
SELECT CAST(5000 AS VARCHAR(10)) AS str_value;
5000 (정수형) → '5000' (문자열)
✅ 예제 3: 날짜 포맷 변경하기
SELECT GETDATE() as [getdate], CAST(GETDATE() AS DATE) AS castdate
✅ 심화 예제: 부서별 평균 급여를 구하고, 일정 급여 이상 부서만 조회
SELECT
d.dept_name,
CAST(AVG(e.salary) AS INT) AS avg_salary
-- 부서 이름과 평균 급여를 호출
FROM employees AS e
INNER JOIN departments AS d ON e.dept_id = d.dept_id
-- 교집합 조인 기준을 사원테이블 부서번호와 부서테이블 부서번호를 대칭
GROUP BY d.dept_name -- 그룹핑 기준
HAVING AVG(e.salary) >= 5000; -- 집계함수 조건
1. INNER JOIN 부서테이블 ON 사원테이블의 부서번호 = 부서테이블의 부서번호
→ 교집합 조인을 사용하여 부서에 포함되어 있는 사원을 호출.
2. CAST(AVG(사원테이블의 연봉) AS INT) AS 평균연봉
~ GROUP BY 부서테이블의 부서이름
~ HAVING AVG(사원테이블의 연봉) >= 5000
→ 부서테이블의 부서이름을 기준으로 연봉 평균을 구합니다. (GROUP BY절)
→ 부서번호가 같은 사원들의 연봉 평균이 5000 이상만 호출 (HAVING절)
→ 호출한 평균연봉을 FLOAT → INT로 변경하고, 평균연봉이라는 임시 필드로 선언 (CAST절)
3. SELECT 부서이름, 부서의 평균연봉 (5000▲)
→ 이렇게 2개의 필드를 호출하여 조회할 수 있습니다.
3️⃣ 데이터의 자료형을 변환하자2, CONVERT
CONVERT(목표_자료형, 데이터 [, 스타일])
CONVERT를 보시면, CAST와 똑같은 것이 아닌가? 할 수 있습니다.
어느정도 맞는 말이기는 합니다만, 서로의 차이점이 존재하지 않는 것은 아닙니다.
CAST → 표준 SQL 변환 함수이며, 여러 곳에서 사용되는 만큼 범용성이 높고, 그렇기에 가독성이 좋습니다.
CONVERT → 보통 날짜 형식 같은 것을 바꿀 때만 사용하는 편
( 해당 비교는 레딧의 글을 참고하였습니다. 글을 클릭하면 해당 링크로 이동됩니다.)
✅ 예제 1: 정수를 문자열로 바꿀 때
SELECT CONVERT(VARCHAR, 12345) AS str_value;
✅ 예제 2: 문자열을 날짜로 바꿀 때
SELECT CONVERT(DATE, '2024-12-25') AS date_value;
✅ 예제 3: 날짜를 문자열로 바꿀 때 (스타일 적용)
SELECT CONVERT(VARCHAR, GETDATE(), 120) AS date_str;
→ 적용된 120 스타일은 YYYY-MM-DD HH:MI:SS 입니다.
✅ 예제 4: 밀리초까지 표기할 때 (스타일 적용)
SELECT CONVERT(VARCHAR, GETDATE(), 121) AS with_milliseconds;
→ 적용된 121 스타일은 YYYY-MM-DD HH:MI:SS.mmm 입니다.
✅ 심화 예제: 날짜 형식을 변환하고 특정 조건에 맞는 데이터의 평균을 조회
SELECT
d.dept_name,
CONVERT(VARCHAR, d.created_at, 120) AS created_date, -- CONVERT
AVG(e.salary) AS avg_salary -- AVG
FROM employees AS e
INNER JOIN departments AS d ON e.dept_id = d.dept_id
WHERE d.created_at >= '2023-01-01'
GROUP BY d.dept_name, d.created_at; -- GROUP BY
1. INNER JOIN 부서테이블 ON 부서테이블과 사원테이블의 부서번호가 같은가
→ 부서테이블의 부서번호와 사원테이블의 부서번호를 교집합 조인하여 겹치는 것을 호출
2. WHERE 부서테이블의 부서생성일자 >= 2023년 1월 1일
→ 부서 생성 일자가 2023년 1월 1일 이상인 부서만 호출
3. AVG(부서연봉) AS 평균연봉
GROUP BY 부서이름, 부서생성일자
→ 부서이름을 기준으로 부서의 평균연봉을 구합니다.
4. 마무리로 잘 출력합니다.
4️⃣ 대소문자를 변환해보자! LOWER, UPPER
SELECT 'Hello' as [text],
LOWER('Hello') as lower_text,
UPPER('Hello') as upper_text
아마 값이 Hello / hello / HELLO 이렇게 3개가 뜰 것입니다.
직관적인 만큼 이해하기 쉬운 함수인데, 이것은 보통 어디에 쓸까요?
✅ 실전 활용 예시: 이메일 주소 비교 (대소문자 무시)
SELECT *
FROM users
WHERE LOWER(email) = LOWER('User@Example.Com');
사용자가 입력한 이메일과 데이터베이스의 이메일 값을 소문자로 통일해서 비교할 때
✅ 실전 활용 예시: 대소문자 구분 없이 검색
SELECT name
FROM products
WHERE UPPER(name) LIKE UPPER('%Laptop%');
이렇게 하면 'laptop', 'LAPTOP', 'LapTop' 등 상관없이 잘 검색되는 것을 확인할 수 있다.
※ 대신 한글이나 숫자, 공백, 기호는 어떠한 변화도 나타나지 않습니다.