SQL 기본

SQL은 데이터베이스에 데이터를 묻고, 넣고, 고치는 언어입니다. 이 자료는 SELECT 문이 처리되는 순서에서 시작해 함수와 GROUP BY, 조인, 서브쿼리, 윈도우 함수, 계층형 질의를 거쳐 데이터를 바꾸고 확정하는 관리 구문까지 차례로 봅니다. 예제는 사원·부서 테이블을 계속 쓰고, 장마다 직접 조작해 볼 수 있는 그림을 두었습니다. 설명은 Oracle을 기준으로 하고, SQL Server가 다르게 동작하는 곳은 따로 적었습니다. 테이블을 어떻게 나누고 설계하는지는 데이터 모델링 자료에서 다룹니다.

01

관계형 데이터베이스와 SELECT 기본

관계형 데이터베이스의 말

관계형 데이터베이스는 데이터를 테이블(행과 열로 된 표)에 담고, 테이블끼리는 키 값으로 잇습니다. 이론에서는 같은 것을 다른 이름으로 부르므로 짝을 알아 두면 좋습니다.

이론(관계 모델)테이블에서설명
릴레이션테이블같은 구조의 행을 모은 것
튜플행인스턴스 하나
애트리뷰트열(컬럼)속성 하나
차수(Degree)컬럼 수애트리뷰트의 개수
카디널리티(Cardinality)행 수튜플의 개수
도메인컬럼의 값 범위애트리뷰트가 가질 수 있는 값의 집합

릴레이션에는 성질이 있습니다. 똑같은 튜플이 두 개 있을 수 없고, 튜플 사이·애트리뷰트 사이에 순서가 없으며, 모든 값은 더 나눌 수 없는 원자값입니다.

키도 종류가 나뉩니다.

키조건
슈퍼키유일성만 만족 — 행을 구별할 수 있는 컬럼 조합 전부
후보키유일성 + 최소성 — 기본키가 될 수 있는 후보
기본키(PK)후보키 중 대표로 고른 것. NULL과 중복 불가
대체키기본키로 뽑히지 않은 나머지 후보키
외래키(FK)다른 테이블의 기본키를 참조하는 컬럼

데이터가 늘 올바르도록 지키는 규칙이 무결성 제약입니다. 기본키는 NULL이거나 중복될 수 없다는 개체 무결성, 외래키 값은 참조하는 테이블에 있는 값이거나 NULL이어야 한다는 참조 무결성, 컬럼 값은 정해진 도메인 안에 있어야 한다는 도메인 무결성이 대표적입니다.

관계형 데이터베이스의 이론적 연산을 관계 대수라고 합니다. SQL의 각 부분이 어떤 연산인지 짝지어 보면 이렇습니다.

종류연산기호SQL에서
순수 관계 연산자SELECT(셀렉트)σWHERE — 조건에 맞는 행을 고른다
PROJECT(프로젝트)πSELECT 목록 — 원하는 열을 고른다
JOIN(조인)⋈JOIN — 공통 값으로 두 릴레이션을 붙인다
DIVISION(디비전)÷나누는 쪽 값을 모두 가진 행만 남긴다(직접 대응하는 문법 없음)
일반 집합 연산자합집합 / 교집합 / 차집합∪ / ∩ / −UNION / INTERSECT / MINUS(EXCEPT)
카티션 프로덕트×CROSS JOIN

SQL 문장의 네 가지 종류

종류명령하는 일
DML (조작)SELECT, INSERT, UPDATE, DELETE, MERGE데이터를 조회하고 넣고 고치고 지운다. SELECT만 따로 DQL로 부르기도 한다
DDL (정의)CREATE, ALTER, DROP, RENAME, TRUNCATE테이블 같은 구조를 만들고 바꾸고 없앤다
DCL (제어)GRANT, REVOKE권한을 주고 회수한다
TCL (트랜잭션 제어)COMMIT, ROLLBACK, SAVEPOINT변경을 확정하거나 되돌린다

SELECT와 WHERE

SELECT의 뼈대는 이렇습니다.

SELECT [DISTINCT] 컬럼 [AS 별칭], ...
FROM   테이블
WHERE  조건
ORDER BY 컬럼 [ASC | DESC];
  • DISTINCT는 결과에서 중복 행을 없앤다 (SELECT 목록 전체가 같아야 중복)
  • 별칭은 AS로 붙이고, 공백이나 특수 문자가 있으면 큰따옴표로 감싼다
  • 문자열을 이을 때 Oracle은 ||, SQL Server는 +를 쓴다 (CONCAT 함수는 둘 다 있다)

WHERE에서 쓰는 연산자는 이렇습니다.

연산자뜻주의
=, <>, !=, ^=, >, <, >=, <=비교<>, !=, ^=는 모두 "같지 않다"
BETWEEN a AND ba 이상 b 이하양 끝 값을 포함한다. 작은 값을 앞에
IN (a, b, c)목록 중 하나와 같다NOT IN 목록에 NULL이 있으면 결과가 하나도 안 나온다
LIKE '김%'패턴%는 0자 이상 아무 문자, _는 딱 한 문자. % 자체를 찾으려면 ESCAPE
IS NULL / IS NOT NULLNULL인가= NULL은 항상 알 수 없음이라 한 행도 안 나온다

조건 여러 개를 이을 때는 우선순위가 있습니다. Oracle 문서 기준으로 괄호 → 비교 연산자 → NOT → AND → OR 순서입니다. AND가 OR보다 먼저라서 A OR B AND C는 A OR (B AND C)로 계산됩니다. 헷갈리면 괄호를 치는 것이 안전합니다.

NOT IN (10, 20, NULL)이 왜 아무것도 돌려주지 않을까요? NOT IN은 “10이 아니고 AND 20이 아니고 AND NULL이 아니다”로 풀리는데, NULL과의 비교는 언제나 ‘알 수 없음’이라 전체 조건이 참이 될 수 없기 때문입니다. 서브쿼리 결과에 NULL이 섞일 수 있다면 NOT EXISTS를 쓰거나 NULL을 걸러 냅니다.

ORDER BY: 정렬과 NULL의 자리

ORDER BY는 오름차순(ASC)이 기본이고, 컬럼 이름·별칭·SELECT 목록의 위치 번호(1, 2…)로 정렬할 수 있습니다. 차이가 나는 곳은 NULL입니다. Oracle은 NULL을 가장 큰 값으로 보아 오름차순이면 맨 뒤에, SQL Server는 가장 작은 값으로 보아 맨 앞에 둡니다. Oracle에서는 NULLS FIRST·NULLS LAST로 자리를 직접 정할 수 있습니다.

SELECT 문이 처리되는 순서

SELECT 문은 적힌 순서대로 처리되지 않습니다. 이 순서를 알면 “왜 WHERE에서 별칭을 못 쓰지?”, “왜 WHERE에 SUM을 못 쓰지?” 같은 질문이 한 번에 풀립니다. 아래 그림에서 단계를 넘겨 보세요.

SELECT 부서코드, SUM(급여) AS 합계
▶FROM 사원
WHERE 급여 >= 260
GROUP BY 부서코드
HAVING SUM(급여) >= 280
ORDER BY 합계 DESC;

사원

사원번호이름부서코드급여
101김하늘D10300
102이도윤D10250
103박서준D20400
104최유나D20350
105정민재NULL280
106한지우D30260

1번째로 처리 — FROM

처리 순서
FROM

단계를 넘겨 보세요. 적힌 순서(SELECT가 맨 위)와 처리되는 순서가 다릅니다.

FROM이 가장 먼저입니다. 사원 테이블의 행 6개를 모두 가져옵니다. SELECT가 맨 위에 적혀 있지만 실제로는 거의 마지막에 처리됩니다.

정리하면 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY입니다. 별칭은 SELECT에서 생기므로 그 뒤의 ORDER BY에서만 쓸 수 있습니다.

문제에 이런 표현이 나오면 → 이렇게 고른다

  • “릴레이션의 컬럼 수 / 행 수”차수 / 카디널리티
  • “유일성과 최소성을 모두 만족”후보키
  • “외래키는 참조 테이블의 값이거나 NULL”참조 무결성
  • “원하는 열만 고르는 관계 연산”PROJECT(π)
  • “GRANT·REVOKE”DCL
  • “BETWEEN 100 AND 200”100과 200을 포함
  • “NOT IN 목록에 NULL”결과 0건
  • “Oracle 오름차순에서 NULL의 자리”맨 뒤 (SQL Server는 맨 앞)
  • “WHERE에서 SELECT 별칭을 못 쓰는 이유”WHERE가 SELECT보다 먼저 처리됨

02

함수와 GROUP BY·HAVING

SELECT로 컬럼을 꺼냈다면, 이제 값을 가공하고(함수) 묶어서 셉니다(GROUP BY).

단일행 함수: 행마다 값 하나를 돌려준다

함수는 두 종류입니다. 행마다 결과를 하나씩 돌려주는 단일행 함수와, 여러 행을 묶어 결과 하나를 돌려주는 다중행 함수(집계 함수)입니다. 단일행 함수는 SELECT·WHERE·ORDER BY 어디에나 쓸 수 있고, 함수 안에 함수를 넣을 수도 있습니다.

종류OracleSQL Server예와 결과
문자LOWER, UPPERLOWER, UPPERUPPER('sql') → 'SQL'
SUBSTRSUBSTRINGSUBSTR('DATABASE', 3, 4) → 'TABA'
LENGTHLENLENGTH('SQL') → 3
LTRIM, RTRIM, TRIMLTRIM, RTRIM, TRIMTRIM(' A ') → 'A'
CONCAT, ||CONCAT, +'A' || 'B' → 'AB'
숫자ROUND, TRUNCROUNDROUND(38.567, 1) → 38.6 / TRUNC(38.567, 1) → 38.5
CEIL, FLOORCEILING, FLOORCEIL(3.1) → 4 / FLOOR(-3.1) → -4
MOD%MOD(7, 3) → 1
날짜SYSDATEGETDATE()지금 날짜와 시각
EXTRACTDATEPARTEXTRACT(YEAR FROM 입사일)

Oracle에서 날짜에 숫자를 더하면 일 단위로 계산됩니다. 입사일 + 1은 다음 날, 입사일 + 1/24는 한 시간 뒤입니다. 또 하나, Oracle은 빈 문자열(‘’)을 NULL로 취급하지만 SQL Server는 빈 문자열과 NULL을 구분합니다.

형 변환과 CASE

데이터 타입을 바꾸는 방법은 둘입니다. 함수로 직접 바꾸는 명시적 변환(Oracle: TO_CHAR·TO_NUMBER·TO_DATE, SQL Server: CAST·CONVERT)과, 데이터베이스가 알아서 바꾸는 암시적 변환입니다. 암시적 변환은 편하지만 예상과 다르게 바뀌거나 인덱스를 못 쓰게 만들 수 있어, 명시적으로 쓰는 습관이 좋습니다.

조건에 따라 다른 값을 돌려줄 때는 CASE를 씁니다. 값 하나와 비교하는 단순 CASE와, 조건식을 쓰는 검색 CASE가 있습니다. Oracle에는 같은 일을 하는 DECODE 함수도 있습니다.

SELECT 이름,
       CASE WHEN 급여 >= 350 THEN '상'
            WHEN 급여 >= 280 THEN '중'
            ELSE '하' END AS 등급,      -- ELSE가 없으면 NULL
       DECODE(부서코드, 'D10', '영업', 'D20', '개발', '기타') AS 부서명
FROM   사원;

NULL을 다루는 함수

함수돌려주는 값예
NVL(a, b) / ISNULL(a, b)a가 NULL이면 b, 아니면 aNVL(보너스, 0)
NVL2(a, b, c)a가 NULL이 아니면 b, NULL이면 c (Oracle)NVL2(보너스, '있음', '없음')
NULLIF(a, b)a와 b가 같으면 NULL, 다르면 aNULLIF(10, 10) → NULL
COALESCE(a, b, c, ...)처음으로 NULL이 아닌 값COALESCE(NULL, NULL, 3) → 3

집계 함수와 NULL

COUNT·SUM·AVG·MAX·MIN 같은 집계 함수는 여러 행을 묶어 값 하나를 만듭니다. 기억할 규칙은 하나입니다. COUNT(*)만 빼고 모두 NULL을 무시합니다. 아래 그림에서 함수를 바꿔 보세요.

사원

이름급여보너스
김하늘30050
이도윤250NULL
박서준400NULL
최유나35030
정민재280NULL
한지우26020

COUNT(*) = 6

계산
계산에 들어간 행
6 / 6

함수를 바꿔 보세요. 연두 행이 계산에 들어간 행, 줄 그은 행이 빠진 행입니다.

COUNT(*)는 행 자체를 셉니다. 보너스가 NULL인 행도 행이므로 6입니다.

집계 함수 안에 DISTINCT를 넣으면 중복 값을 한 번만 셉니다(COUNT(DISTINCT 부서코드)). 조건에 맞는 행이 하나도 없으면 COUNT는 0을, SUM·AVG·MAX·MIN은 NULL을 돌려줍니다.

GROUP BY와 HAVING

GROUP BY는 같은 값을 가진 행을 묶어 그룹마다 집계합니다. 묶은 뒤에는 그룹마다 한 줄이 되므로 규칙이 생깁니다.

  • SELECT 목록에서 집계 함수 밖에 있는 컬럼은 반드시 GROUP BY에도 있어야 한다
  • GROUP BY에는 SELECT의 별칭을 쓰지 않는다 — 처리 순서상 별칭이 아직 없다 (최신 Oracle 23ai는 허용하지만 시험은 전통 규칙 기준)
  • NULL도 한 그룹이 된다
  • GROUP BY는 정렬을 보장하지 않는다 — 순서가 필요하면 ORDER BY를 쓴다

그룹에 조건을 걸 때는 HAVING을 씁니다. WHERE는 묶기 전에 행을 거르고, HAVING은 묶은 뒤 그룹을 거릅니다. 그래서 집계 함수 조건은 HAVING에만 쓸 수 있습니다. 집계 함수가 필요 없는 조건이면 WHERE에서 미리 거르는 편이 처리할 행이 줄어 유리합니다.

SELECT   부서코드, COUNT(*) AS 인원, AVG(급여) AS 평균급여
FROM     사원
WHERE    급여 >= 250          -- 행을 먼저 거른다
GROUP BY 부서코드
HAVING   COUNT(*) >= 2;       -- 묶은 뒤 그룹을 거른다

문제에 이런 표현이 나오면 → 이렇게 고른다

  • “SUBSTR('DATABASE', 3, 4)”'TABA'
  • “Oracle에서 빈 문자열”NULL로 취급
  • “a와 b가 같으면 NULL”NULLIF(a, b)
  • “처음으로 NULL이 아닌 값”COALESCE
  • “NULL을 세는 집계 함수”COUNT(*)뿐
  • “AVG(보너스) vs AVG(NVL(보너스, 0))”나누는 행 수가 다르다
  • “집계 함수 조건을 거는 절”HAVING
  • “GROUP BY 없는 컬럼을 SELECT에”집계 함수로 감싸지 않으면 오류

Pro 전용

여기까지 무료로 읽을 수 있어요

나머지 5개 장은 Pro 구독자에게 열려 있어요. 장마다 3D 그림과 "문제에 이런 표현이 나오면" 정리가 이어지고, 마지막 장에서는 직접 풀어 보며 확인합니다.

  1. 03조인
  2. 04서브쿼리·뷰·집합 연산자
  3. 05그룹 함수·윈도우 함수·Top N
  4. 06셀프 조인·계층형 질의·PIVOT·정규 표현식
  5. 07관리 구문: DML·TCL·DDL·DCL

광고

PasslyExam

개념을 읽었다면, 이제 시험 문제로 확인할 차례

PasslyExam은 자격증 시험 범위에 맞춰 만든 오리지널 연습문제와 해설, 실전 모의고사를 제공합니다. 왜 그 보기가 정답이고 비슷한 보기는 왜 틀리는지까지 해설로 짚습니다. 가입하면 매일 무료로 연습문제를 풀 수 있습니다.

같은 분야의 다른 자료

다른 분야의 개념 자료

개념 자료 전체 보기