Chapter 3. Introduction to SQL
한 줄 핵심: SQL은 관계형 DB의 표준 언어로, 테이블을 정의하는 DDL과 데이터를 조회·수정하는 DML로 구성되며, 모든 질의의 뼈대는
select-from-where다.
이 챕터가 답하는 핵심 질문
- Q1. SQL은 무엇으로 구성되어 있나? (DDL/DML/그 외)
- Q2. DDL로 테이블을 어떻게 정의하고 변경하나?
- Q3. SQL 질의의 기본 구조
select-from-where는 관계대수의 무엇에 대응하나?- Q4. 같은 테이블을 두 번 써야 할 때(자기 조인)는 어떻게 하나?
- Q5. 문자열은 어떻게 매칭/조작하나? (like, 함수)
- Q6. 결과를 정렬하고 범위로 거르는 법은?
- Q7. 집합 연산(union/intersect/except)은 왜 필요한가?
- Q8. null은 연산·비교·조건에서 어떻게 동작하나?
- Q9. 집계 함수와 group by / having는 어떻게 쓰나?
- Q10. 중첩 서브쿼리(in, some, all, exists, unique)는 어떻게 쓰나?
- Q11. from 절·with 절·스칼라 서브쿼리는 각각 언제 쓰나?
- Q12. 데이터를 삽입·삭제·수정하는 법은? (case 활용 포함)
Q1. SQL은 무엇으로 구성되어 있나?
A. SQL은 단순 조회 언어가 아니라, 데이터 정의(DDL)·조작(DML)·무결성·뷰·트랜잭션·내장 SQL·권한을 모두 포괄하는 통합 언어다.
SQL(Structured Query Language)은 1970년대 IBM San Jose 연구소의 System R 프로젝트에서 Sequel이라는 이름으로 개발된 뒤 SQL로 개명되었다. ANSI/ISO 표준이 SQL-86 → SQL-89 → SQL-92 → SQL:1999 → SQL:2003 순으로 발전했다. 상용 시스템은 대부분 SQL-92를 지원하되 독자 기능이 섞여 있어, 모든 예제가 특정 시스템에서 동작하지는 않는다.
| 구성요소 | 역할 |
|---|---|
| DML (Data Manipulation Language) | 데이터 조회·삽입·삭제·수정 (CRUD) |
| 무결성 (Integrity) | DDL에 포함, 무결성 제약 명시 |
| 뷰 정의 (View definition) | DDL에 포함, 가상 릴레이션 정의 |
| 트랜잭션 제어 (Transaction control) | 트랜잭션의 시작·종료 명시 |
| Embedded / Dynamic SQL | C·Java 등 범용 언어에 SQL 내장 |
| 권한 (Authorization) | 릴레이션·뷰에 대한 접근 권한 명시 |
Q2. DDL로 테이블을 어떻게 정의하고 변경하나?
A. create table로 스키마(속성+타입)와 무결성 제약을 함께 선언하며, 이후 alter/drop/delete로 구조나 내용을 바꾼다.
DDL(Data Definition Language)은 각 릴레이션에 대해 다음을 명시한다.
- 스키마 (속성 이름과 순서)
- 각 속성의 값 타입 (도메인)
- 무결성 제약 (primary key, foreign key, not null 등)
- 유지할 인덱스 집합
- 보안·권한 정보
- 디스크상의 물리적 저장 구조
도메인 타입
| 타입 | 설명 |
|---|---|
char(n) | 고정 길이 문자열 (길이 n) |
varchar(n) | 가변 길이 문자열 (최대 n) |
int / smallint | 정수 / 작은 정수 (기계 의존) |
numeric(p, d) | 고정 소수점 (전체 p자리, 소수 d자리). 예: numeric(3,1)은 44.5는 OK, 444.5·0.32는 불가 |
real, double precision | 부동 소수점 (기계 의존 정밀도) |
float(n) | 최소 n자리 정밀도 부동 소수점 |
테이블 생성
create table r (A₁ D₁, ..., Aₙ Dₙ, 제약₁, ..., 제약ₖ) 형태. 각 Aᵢ는 속성명, Dᵢ는 도메인(타입)이다.
create table department (
dept_name varchar(20),
building varchar(15),
budget numeric(12, 2),
primary key (dept_name)
);
create table instructor (
ID varchar(5),
name varchar(20) not null,
dept_name varchar(20),
salary numeric(8, 2),
primary key (ID),
foreign key (dept_name) references department
);
create table takes (
ID varchar(5),
course_id varchar(8),
sec_id varchar(8),
semester varchar(6),
year numeric(4, 0),
grade varchar(2),
primary key (ID, course_id, sec_id, semester, year),
foreign key (ID) references student,
foreign key (course_id, sec_id, semester, year) references section
);primary key: 기본키 (자동으로 not null + unique)foreign key ... references ...: 외래키 (참조 무결성)not null: null 금지- SQL은 무결성 제약을 위반하는 어떤 업데이트도 막는다 (예: 없는 학과를 참조하는 교수 삽입 거부).
takes는 **복합 기본키(5속성)**와 복합 외래키를 가진다.
테이블 변경 명령
| 명령 | 의미 |
|---|---|
drop table r | 릴레이션 r과 스키마를 모두 삭제 |
delete from r | 튜플만 모두 삭제 (스키마 유지) |
alter table r add A D | 속성 A(도메인 D) 추가. 기존 튜플의 새 속성 값은 null |
alter table r drop A | 속성 A 제거. 많은 DBMS가 미지원 |
insert into r values (...) | 튜플 삽입 |
Q3. SQL 질의의 기본 구조는 관계대수의 무엇에 대응하나?
A. select-from-where는 각각 관계대수의 투영(Π) · 카티션 곱(×) · 선택(σ)에 대응하며, 질의 결과는 항상 하나의 릴레이션이다.
SQL 이름은 대소문자를 구분하지 않는다 (case insensitive).
Name≡NAME≡name
select A₁, A₂, ..., Aₙ -- 투영 Π
from r₁, r₂, ..., rₘ -- 카티션 곱 ×
where P; -- 선택 σdistinct / all, 그리고 multiset
select distinct dept_name from instructor; -- 중복 제거
select all dept_name from instructor; -- 중복 허용 (기본값)SQL의 릴레이션은 기본적으로 다중집합(multiset) → 중복 튜플 허용.
관계대수는 기본적으로 집합(set) → 중복 없음.
리터럴 select와 산술·별명
select '437'; -- from 없이: 1행 1열 '437'
select '437' as FOO; -- 컬럼명 FOO
select 'A' from instructor; -- instructor 튜플 수 N만큼 'A' 반복
select ID, name, salary/12 as monthly_salary
from instructor; -- 산술 연산 + as 별명- select 절에는 상수·속성에
+ - * /를 적용할 수 있다. select *는 모든 속성을 선택한다.
where 절과 from 절
select name from instructor
where dept_name = 'Comp. Sci.' and salary > 70000;- 비교 연산자
< <= > >= = <>, 논리 연산자and or not사용.
-- from에 여러 릴레이션 → 카티션 곱 + where 조인 조건
select name, course_id
from instructor, teaches
where instructor.ID = teaches.ID;from의 카티션 곱 자체는 무의미한 조합이 대부분이지만, where 조건과 결합하면 의미 있는 조인이 된다. 공통 속성(예: ID)은 결과에서
instructor.ID처럼 릴레이션명으로 구분된다.
카티션 곱이 실제로 어떻게 펼쳐지나 (눈으로 보기)
작은 샘플로 직접 돌려보자.
from instructor, teaches는 두 테이블의 모든 행 쌍을 만든 뒤, where로 거른다.instructor (2행)
ID name 101 Kim 222 Lee teaches (2행)
ID course_id 101 CS-101 999 CS-315 1단계 — 카티션 곱(2×2 = 4행 전부 조합):
instructor.ID name teaches.ID course_id 101 Kim 101 CS-101 101 Kim 999 CS-315 222 Lee 101 CS-101 222 Lee 999 CS-315 2단계 —
where instructor.ID = teaches.ID로 ID가 같은 행만 남김:
name course_id Kim CS-101 4행 → 1행. teaches에만 있는 999, instructor에만 있는 222는 짝이 없어 사라진다. 이것이 곧 inner join이며, 이 “짝 없는 행이 사라지는” 동작을 Ch4의 outer join이 보완한다.
Q4. 같은 테이블을 두 번 써야 할 때는 어떻게 하나?
A. as로 별명(alias)을 둘 부여하여 같은 테이블의 두 복사본처럼 다루는 **자기 조인(self-join)**을 쓴다.
as 절은 릴레이션·속성 이름을 바꾼다: old-name as new-name.
-- Comp. Sci. 학과의 어떤 교수보다 급여가 높은 모든 교수
select distinct T.name
from instructor as T, instructor as S
where T.salary > S.salary and S.dept_name = 'Comp. Sci.';instructor as T와instructor as S는 같은 테이블의 두 복사본.- T의 각 행을 S의 각 행과 비교한다.
- 키워드
as는 선택적:instructor as T≡instructor T⭐
Q5. 문자열은 어떻게 매칭하고 조작하나?
A. like 연산자로 %(임의 문자열)·_(한 문자) 패턴 매칭을 하고, ||·upper·substring 등 함수로 가공한다.
- 문자열은 작은따옴표로 감싼다:
'Hello'. 작은따옴표 자체는''로 이스케이프.
select name from instructor where name like '%dar%'; -- 'dar' 포함
where dept_name like '100\%' escape '\'; -- 리터럴 % 매칭| 패턴 | 매칭 대상 |
|---|---|
'Intro%' | ”Intro”로 시작 |
'%Comp%' | ”Comp” 포함 |
'___' | 정확히 3글자 |
'___%' | 최소 3글자 이상 |
패턴은 대소문자를 구분한다 (case sensitive). (이름 매칭과 반대)
| 함수 | 설명 |
|---|---|
|| | 문자열 연결 |
upper(s) / lower(s) | 대/소문자 변환 |
length(s) | 길이 |
substring(s, start, len) | 부분 문자열 추출 |
Q6. 결과를 정렬하고 범위로 거르는 법은?
A. order by로 정렬(기본 오름차순, desc/asc), between으로 범위 필터, 튜플 비교로 여러 속성을 한 번에 비교한다.
select * from instructor
order by salary desc, name asc; -- 급여 내림차순, 동률은 이름 오름차순
select name from instructor
where salary between 90000 and 100000;
-- 동치: salary >= 90000 and salary <= 100000튜플 비교 (Tuple Comparison)
select name, course_id
from instructor, teaches
where (instructor.ID, dept_name) = (teaches.ID, 'Biology');
-- 동치: instructor.ID = teaches.ID and dept_name = 'Biology'Q7. 집합 연산은 왜 필요한가?
A. union·intersect·except는 서로 다른 질의 결과를 튜플 단위로 합치는 연산이며, 기본적으로 중복을 자동 제거한다 (all을 붙이면 유지).
-- 2017 Fall 또는 2018 Spring에 개설된 과목 (중복 제거)
(select course_id from section where semester='Fall' and year=2017)
union
(select course_id from section where semester='Spring' and year=2018);
-- ... union all ... 이면 중복 유지| 연산 | 중복 제거 | 중복 유지 |
|---|---|---|
| 합집합 | union | union all |
| 교집합 | intersect | intersect all |
| 차집합 | except | except all |
세 연산을 같은 데이터로 비교
A = Fall 2017 개설 과목, B = Spring 2018 개설 과목이라 하자.
A (Fall) B (Spring) CS-101 CS-101 CS-315 CS-319 CS-319 CS-347
연산 의미 (벤다이어그램) 결과 A union BA 또는 B (합집합) CS-101, CS-315, CS-319, CS-347 A intersect BA 이면서 B (교집합) CS-101, CS-319 A except BA 이지만 B는 아님 (차집합) CS-315 핵심:
except는 방향이 있다.A except B(CS-315)와B except A(CS-347)는 결과가 다르다. 뺄셈처럼 “앞 - 뒤”라고 외우자.
all 버전의 중복 횟수 규칙 (r에서 m번, s에서 n번 등장 시):
union all→ m + nintersect all→ min(m, n)except all→ max(0, m − n)
집합 연산이 굳이 필요한가? 서브쿼리로 다 되지 않나?
대부분은 대체 가능하다(
INTERSECT≈IN,EXCEPT≈NOT IN). 그러나 ① UNION은 서로 다른 from의 결과 결합이라 단일 select로 불가, ② ALL 변형의 multiset 횟수 규칙은 서브쿼리로 정확히 재현하기 까다롭고, ③ 가독성·④ 옵티마이저 최적화 측면에서 집합 연산이 우월하다.
Q8. null은 연산·비교·조건에서 어떻게 동작하나?
A. null은 “알 수 없음/없음”을 뜻하며, 산술이 섞이면 결과는 null, 비교가 섞이면 결과는 unknown이 되어 3값 논리를 따른다.
- 산술:
5 + null = null - 비교:
5 < null = unknown - null 여부 확인:
is null,is not null
3값 논리 (Three-Valued Logic)
| 연산 | 결과 |
|---|---|
true and unknown | unknown |
false and unknown | false |
unknown and unknown | unknown |
true or unknown | true |
false or unknown | unknown |
not unknown | unknown |
select name from instructor where salary is null;where조건이 unknown으로 평가되면 그 튜플은 결과에서 제외된다 (false와 동일 취급).
초보자가 가장 많이 하는 실수:
= null은 절대 안 된다“급여가 비어있는(null) 교수”를 찾으려고
where salary = null이라 쓰면 한 행도 안 나온다. null은 “알 수 없는 값”이라서null = null조차 true가 아니라 unknown이기 때문이다. 반드시is null/is not null을 써야 한다.
조건 평가 결과 그 행이 결과에 나오나? salary = nullunknown ❌ (절대 안 나옴) salary is nulltrue ✅ salary <> 50000(단, salary가 null)unknown ❌ 마지막 줄이 함정이다. “5만이 아닌 교수”를 찾을 때 급여가 null인 교수는 ‘5만이 아닌 것도 아니고 5만인 것도 아닌’ 상태라 결과에서 빠진다.
Q9. 집계 함수와 group by / having는 어떻게 쓰나?
A. 집계 함수는 여러 튜플을 하나의 값으로 요약하고, group by로 그룹별 집계, having으로 그룹에 대한 조건을 건다.
| 함수 | 설명 |
|---|---|
avg / min / max / sum (col) | 평균/최소/최대/합 |
count(col) | 개수 (null 제외) |
count(*) | 튜플 수 (null 포함) |
select avg(salary) from instructor where dept_name = 'Comp. Sci.';
select count(distinct ID) from teaches where semester='Spring' and year=2018;select dept_name, avg(salary) as avg_salary
from instructor
group by dept_name
having avg(salary) > 42000;
select에 나오는 비집계 속성은 반드시group by에 포함되어야 한다.
실행 순서: from → where → group by → having → select → order by
where는 그룹 형성 전 개별 튜플에 적용.having은 그룹 형성 후 각 그룹에 적용 (집계 함수 조건은 having에).
group by가 실제로 "행을 뭉치는" 과정 (눈으로 보기)
instructor 샘플:
ID name dept_name salary 101 Kim Comp. Sci. 90000 102 Lee Comp. Sci. 75000 201 Park Biology 72000 202 Cho Biology 40000 301 Han Music 38000
group by dept_name→ 같은 학과끼리 한 덩어리로 묶고, 각 덩어리를 집계 함수가 한 값으로 압축:Comp. Sci. ┐ Kim 90000 ├─→ avg = 82500 Lee 75000 ┘ Biology ┐ Park 72000├─→ avg = 56000 Cho 40000 ┘ Music ┐ Han 38000 ┘─→ avg = 38000
select dept_name, avg(salary)결과 (학과 수만큼 = 3행):
dept_name avg_salary Comp. Sci. 82500 Biology 56000 Music 38000 여기에
having avg(salary) > 42000을 붙이면 그룹 단위로 필터링 → Music(38000) 탈락:
dept_name avg_salary Comp. Sci. 82500 Biology 56000
where vs having — "누구를 거를까"가 다르다
- where: 그룹으로 묶기 전, 개별 행을 거른다. (예:
where salary > 50000→ 묶기 전에 저급여 교수 제거)- having: 그룹으로 묶은 후, 그룹 전체를 거른다. (예:
having avg(salary) > 42000→ 평균이 낮은 학과 통째로 제거)그래서
avg,count같은 집계 함수 조건은 where에 못 쓰고 반드시 having에 써야 한다. where 단계에서는 아직 그룹이 없어 평균이라는 게 존재하지 않기 때문이다.
Q10. 중첩 서브쿼리는 어떻게 쓰나?
A. 서브쿼리는 select-from-where를 다른 질의 안에 넣은 것으로, from·where·select 세 위치에 올 수 있고 in/some/all/exists/unique로 집합 멤버십·비교·존재·중복을 검사한다.
| 위치 | 서브쿼리 형태 |
|---|---|
| from 절 | rᵢ를 임의 서브쿼리로 대체 |
| where 절 | B <op> (subquery) 형태 |
| select 절 | 단일 값을 내는 스칼라 서브쿼리 |
서브쿼리를 읽는 직관: "안쪽부터 한 번 계산해서 → 바깥 조건에 끼워넣기"
중첩 쿼리는 괄호 안(서브쿼리)을 먼저 실행해 값/집합을 구한 뒤, 그 결과를 바깥 where에 대입한다고 생각하면 쉽다.
예:salary > (select avg(salary) from instructor)는 먼저 평균(예: 65000)을 구한 뒤 →salary > 65000이 된다.
(단, 상관 서브쿼리는 예외 — 바깥 행마다 안쪽을 다시 계산한다. 10.3 참고.)
10.1 집합 멤버십: in / not in
-- 2017 Fall과 2018 Spring 모두 개설된 과목
select distinct course_id from section
where semester='Fall' and year=2017
and course_id in (select course_id from section
where semester='Spring' and year=2018);
-- not in: Mozart도 Einstein도 아닌 교수
select distinct name from instructor
where name not in ('Mozart', 'Einstein');
-- 튜플 수준 in: 교수 10101이 가르친 분반을 수강한 학생 수
select count(distinct ID) from takes
where (course_id, sec_id, semester, year) in
(select course_id, sec_id, semester, year
from teaches where teaches.ID = 10101);10.2 집합 비교: some / all
F <comp> some r ⟺ ∃ t ∈ r (F <comp> t), F <comp> all r ⟺ ∀ t ∈ r (F <comp> t).
-- some: Biology의 어떤 교수보다 급여 높은 교수
select name from instructor
where salary > some (select salary from instructor where dept_name='Biology');
-- all: Biology의 모든 교수보다 급여 높은 교수
select name from instructor
where salary > all (select salary from instructor where dept_name='Biology');| 표현 | 결과 | 표현 | 결과 | |
|---|---|---|---|---|
5 < some {0,5,6} | true | 5 < all {0,5,6} | false | |
5 = some {0,5} | true | 5 < all {6,10} | true | |
5 ≠ some {0,5} | true | 5 ≠ all {4,6} | true |
= some≡in/≠ some≢not in≠ all≡not in/= all≢in
10.3 존재 여부: exists / not exists
exists r ⟺ r ≠ ∅, not exists r ⟺ r = ∅.
-- exists: 2017 Fall과 2018 Spring 모두 개설된 과목 (상관 서브쿼리)
select course_id from section as S
where semester='Fall' and year=2017
and exists (select * from section as T
where semester='Spring' and year=2018 and S.course_id=T.course_id);- 상관 서브쿼리(Correlated Subquery): 내부 쿼리가 외부 쿼리 변수(S)를 참조. S를 **상관 이름(correlation name)**이라 한다.
상관 서브쿼리 = "바깥 행을 한 줄씩 들고 안쪽을 다시 검사"
보통 서브쿼리는 한 번만 계산하지만, 상관 서브쿼리는 바깥 테이블의 각 행마다 안쪽 쿼리를 새로 실행한다. 위 예는 “section의 Fall 2017 과목 하나를 집고(S) → 그 과목 id가 Spring 2018에도 있나(안쪽 exists)?를 묻기”를 모든 Fall 행에 대해 반복하는 것이다. 이중 for문(바깥 루프 = 바깥 테이블, 안쪽 루프 = 서브쿼리)을 떠올리면 정확하다.
-- not exists: Biology 학과의 모든 과목을 수강한 학생
select distinct S.ID, S.name
from student as S
where not exists (
(select course_id from course where dept_name='Biology')
except
(select T.course_id from takes as T where S.ID=T.ID)
);핵심 논리: X − Y = ∅ ⟺ X ⊆ Y. “모든 것을 만족”은 이 not exists ... except ... 패턴으로 표현한다.
"모든 X를 만족" 패턴, 직관으로 풀기
SQL에는 “모든(for all)“을 직접 쓰는 키워드가 없다. 그래서 발상을 뒤집는다:
“Biology의 모든 과목을 들었다” = “Biology 과목 중에서 내가 안 들은 게 하나도 없다”
이걸 집합으로 쓰면:
(Biology 과목 전체) − (내가 들은 과목) = ∅, 즉 남는 게 없으면 다 들은 것이다.학생 S의 입장에서 한 명씩 검사 (상관 서브쿼리):
Biology 과목 전체 학생이 들은 과목 except(차집합) 결과 {BIO-101, BIO-301} {BIO-101, BIO-301, CS-101} ∅ (빈 집합) not exists→ 선택됨 ✅{BIO-101, BIO-301} {BIO-101} {BIO-301} 비어있지 않음 → 탈락 ❌ 첫 학생은 Biology 과목을 빠짐없이 들었으니 차집합이 비고 →
not exists가 true → 결과에 포함. 둘째 학생은 BIO-301을 안 들어 차집합에 남으므로 탈락. “전부 만족 = 못 채운 게 하나도 없음”으로 외우면 이 패턴이 자연스럽다.
10.4 중복 검사: unique
-- 2017년에 최대 1번 개설된 과목 (중복 없으면 true)
select T.course_id from course as T
where unique (select R.course_id from section as R
where T.course_id=R.course_id and R.year=2017);Q11. from 절·with 절·스칼라 서브쿼리는 각각 언제 쓰나?
A. from 서브쿼리는 임시 릴레이션으로, with는 질의 안에서만 쓰는 이름 붙은 임시 릴레이션으로, 스칼라 서브쿼리는 단일 값이 필요한 곳에 쓴다.
from 절 서브쿼리 (having 없이 집계 조건)
select dept_name, avg_salary
from (select dept_name, avg(salary) as avg_salary
from instructor group by dept_name) as dept_avg
where avg_salary > 42000;
-- 릴레이션명+컬럼명 동시 부여
... from (select dept_name, avg(salary)
from instructor group by dept_name)
as dept_avg (dept_name, avg_salary) ...with 절 (임시 릴레이션 정의)
-- 최대 예산 학과
with max_budget(value) as (select max(budget) from department)
select department.name
from department, max_budget
where department.budget = max_budget.value;
-- 복수 임시 릴레이션: 평균 총급여 초과 학과
with dept_total(dept_name, value) as
(select dept_name, sum(salary) from instructor group by dept_name),
dept_total_avg(value) as
(select avg(value) from dept_total)
select dept_name from dept_total, dept_total_avg
where dept_total.value > dept_total_avg.value;스칼라 서브쿼리 (단일 값 반환)
-- 각 학과의 소속 교수 수
select dept_name,
(select count(*) from instructor
where department.dept_name = instructor.dept_name) as num_instructors
from department;스칼라 서브쿼리가 2개 이상의 튜플을 반환하면 런타임 에러.
Q12. 데이터를 삽입·삭제·수정하는 법은?
A. delete/insert/update로 수행하며, 서브쿼리를 동반할 때 SQL은 조건을 먼저 전부 평가한 뒤 변경을 적용하고, 조건부 갱신은 case로 순서 문제를 막는다.
Delete
delete from instructor; -- 전체 삭제
delete from instructor where dept_name='Finance'; -- 조건부
-- Watson 건물 학과의 교수 삭제
delete from instructor
where dept_name in (select dept_name from department where building='Watson');
-- 평균 이하 교수 삭제
delete from instructor where salary < (select avg(salary) from instructor);평균 삭제 시 SQL은 avg를 먼저 계산해 대상을 확정한 뒤 삭제한다 (삭제 도중 avg 재계산 없음).
Insert
insert into course values ('CS-437', 'Database Systems', 'Comp. Sci.', 4);
insert into course (course_id, title, dept_name, credits)
values ('CS-437', 'Database Systems', 'Comp. Sci.', 4);
insert into student values ('3003', 'Green', 'Finance', null); -- null 삽입
-- select 결과 삽입
insert into instructor
select ID, name, dept_name, 18000
from student where dept_name='Music' and total_cred > 144;
select는 삽입 전에 완전히 평가되므로insert into t select * from t도 안전하다.
Update와 case
update instructor set salary = salary * 1.05; -- 전체 5%
update instructor set salary = salary * 1.05 where salary < 70000; -- 조건부
update instructor set salary = salary * 1.05
where salary < (select avg(salary) from instructor); -- 평균 미만만-- 100,000 이하 5%, 초과 3% 인상 — case로 순서 문제 방지
update instructor
set salary = case
when salary <= 100000 then salary * 1.05
else salary * 1.03
end;두 개의 update로 나누면 순서에 따라 100,000인 교수가 먼저 5% 올라 105,000이 되어 3% 대상에서 빠지는 등 결과가 달라진다.
case가 이를 막는다.
스칼라 서브쿼리로 갱신
update student S
set tot_cred = (select sum(credits)
from takes, course
where takes.course_id=course.course_id and S.ID=takes.ID
and takes.grade <> 'F' and takes.grade is not null);수강 이력 없는 학생은 tot_cred가 null이 된다. 0으로 처리하려면 case when sum(credits) is not null then sum(credits) else 0 end 사용.
출처: Database System Concepts, 7th Edition (Silberschatz, Korth, Sudarshan)