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 b | a 이상 b 이하 | 양 끝 값을 포함한다. 작은 값을 앞에 |
| IN (a, b, c) | 목록 중 하나와 같다 | NOT IN 목록에 NULL이 있으면 결과가 하나도 안 나온다 |
| LIKE '김%' | 패턴 | %는 0자 이상 아무 문자, _는 딱 한 문자. % 자체를 찾으려면 ESCAPE |
| IS NULL / IS NOT NULL | NULL인가 | = NULL은 항상 알 수 없음이라 한 행도 안 나온다 |
조건 여러 개를 이을 때는 우선순위가 있습니다. Oracle 문서 기준으로 괄호 → 비교 연산자 → NOT → AND → OR 순서입니다. AND가 OR보다 먼저라서 A OR B AND C는 A OR (B AND C)로 계산됩니다. 헷갈리면 괄호를 치는 것이 안전합니다.
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 급여 >= 260GROUP BY 부서코드HAVING SUM(급여) >= 280ORDER BY 합계 DESC;
사원
| 사원번호 | 이름 | 부서코드 | 급여 |
|---|---|---|---|
| 101 | 김하늘 | D10 | 300 |
| 102 | 이도윤 | D10 | 250 |
| 103 | 박서준 | D20 | 400 |
| 104 | 최유나 | D20 | 350 |
| 105 | 정민재 | NULL | 280 |
| 106 | 한지우 | D30 | 260 |
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 어디에나 쓸 수 있고, 함수 안에 함수를 넣을 수도 있습니다.
| 종류 | Oracle | SQL Server | 예와 결과 |
|---|---|---|---|
| 문자 | LOWER, UPPER | LOWER, UPPER | UPPER('sql') → 'SQL' |
| SUBSTR | SUBSTRING | SUBSTR('DATABASE', 3, 4) → 'TABA' | |
| LENGTH | LEN | LENGTH('SQL') → 3 | |
| LTRIM, RTRIM, TRIM | LTRIM, RTRIM, TRIM | TRIM(' A ') → 'A' | |
| CONCAT, || | CONCAT, + | 'A' || 'B' → 'AB' | |
| 숫자 | ROUND, TRUNC | ROUND | ROUND(38.567, 1) → 38.6 / TRUNC(38.567, 1) → 38.5 |
| CEIL, FLOOR | CEILING, FLOOR | CEIL(3.1) → 4 / FLOOR(-3.1) → -4 | |
| MOD | % | MOD(7, 3) → 1 | |
| 날짜 | SYSDATE | GETDATE() | 지금 날짜와 시각 |
| EXTRACT | DATEPART | EXTRACT(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, 아니면 a | NVL(보너스, 0) |
| NVL2(a, b, c) | a가 NULL이 아니면 b, NULL이면 c (Oracle) | NVL2(보너스, '있음', '없음') |
| NULLIF(a, b) | a와 b가 같으면 NULL, 다르면 a | NULLIF(10, 10) → NULL |
| COALESCE(a, b, c, ...) | 처음으로 NULL이 아닌 값 | COALESCE(NULL, NULL, 3) → 3 |
집계 함수와 NULL
COUNT·SUM·AVG·MAX·MIN 같은 집계 함수는 여러 행을 묶어 값 하나를 만듭니다. 기억할 규칙은 하나입니다. COUNT(*)만 빼고 모두 NULL을 무시합니다. 아래 그림에서 함수를 바꿔 보세요.
사원
| 이름 | 급여 | 보너스 |
|---|---|---|
| 김하늘 | 300 | 50 |
| 이도윤 | 250 | NULL |
| 박서준 | 400 | NULL |
| 최유나 | 350 | 30 |
| 정민재 | 280 | NULL |
| 한지우 | 260 | 20 |
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 그림과 "문제에 이런 표현이 나오면" 정리가 이어지고, 마지막 장에서는 직접 풀어 보며 확인합니다.
- 03조인
- 04서브쿼리·뷰·집합 연산자
- 05그룹 함수·윈도우 함수·Top N
- 06셀프 조인·계층형 질의·PIVOT·정규 표현식
- 07관리 구문: DML·TCL·DDL·DCL
광고
PasslyExam
개념을 읽었다면, 이제 시험 문제로 확인할 차례
PasslyExam은 자격증 시험 범위에 맞춰 만든 오리지널 연습문제와 해설, 실전 모의고사를 제공합니다. 왜 그 보기가 정답이고 비슷한 보기는 왜 틀리는지까지 해설로 짚습니다. 가입하면 매일 무료로 연습문제를 풀 수 있습니다.