[sqld 과목 2] sql 기본 및 활용
제1장 SQL 기본
제 1절 관계형 데이터베이스 개요
제 2절 DDL
제 3절 DML
제 4절 TCL
제 5절 WHERE 절
제 6절 함수
제 7절 Group By, Having 절
제 8절 Order By 절
제 9절 조인(JOIN)
SQL 문장들의 종류
명령어의 종류 | 명령어 | 설명 |
데이터 조작어 (DML: Data Manipulation Language) |
SELECT | 데이터베이스에 들어 있는 데이터를 조회하거나 검색하기 위한 명령어를 말하는 것으로 RETRIEVE 라고도 한다. |
INSERT UPDATE DELETE |
데이터베이스의 테이블에 들어 있는 데이터에 변형을 가하는 종류의 명령어들을 말하낟. 예를 들어 데이터를 테이블에 새로운 행을 집어넣거나, 원하지 않는 데이터를 삭제하거나 수정하는 것들의 명령어들을 DML 이라고 부른다. | |
데이터 정의어 (DDL: Data Definition Language) |
CREATE ALTER DROP RENAME |
테이블과 같은 데이터 구조를 정의하는데 사용되는 명령어들로 그러한 구조를 생성하거나 변경하거나 삭제하거나 이름을 바꾸는 데이터 구조롸 관련된 명령어들. |
데이터 제어어 (DCL; Data Control Language) |
GRANT REVOKE |
데이터베이스에 접근하고 객체들을 사용하도록 권한을 주고 회수하는 명령어. |
트랜잭션 제어어 (TCL; Transaction Control Language) |
COMMIT ROLLBACK |
논리적인 작업의 단위를 묶어서 DML에 의해 조작된 결과를 작업단위(트랜잭션)별로 제어하는 명령어. |
As-Is : 비절차적 데이터 조작어(DML)는 사용자가 무슨(What) 데이터를 원하는 지만을 명세함.
To-Be : 절차적 데이터 조작어는 어떻게(How) 데이터를 접근해야 한느지 명세 한다. 절차적 데이터 조작어로는 PL/SQL(오라클), T-SQL(오라클), T-SQL(SQL-Server) 등이 있다.
PRODUCT 테이블에 기본 키(primary key) 제약 조건을 추가하는 명령어.
ADD TABLE PRODUCT
ADD CONSTRAINT PRODUCT_PK
PRIMARY KEY(PROD_ID)
📌 구문 분석
- ALTER TABLE PRODUCT : 기존의 PRODUCT 테이블을 수정하는 명령어입니다.
- ADD CONSTRAINT PRODUCT_PK : PRODUCT_PK라는 이름의 제약 조건을 추가합니다.
- PRIMARY KEY(PROD_ID) : PROD_ID 컬럼을 기본 키로 설정합니다.
SQL SERVER vs ORACLE : 컬럼 제약조건 변경 방법 차이.
#SQL SERVER
ALTER TABLE 기관분류
ALTER COLUMN 분류명 VARCHAR(30) NOT NULL;
ALTER TABLE 기관분류
ALTER COLUMN 등록일자 DATE NOT NULL;
#ORACLE
ALTER TABLE 기관분류
MODIFY 분류명 VARCHAR(30) NOT NULL;
ALTER TABLE 기관분류
MODIFY 등록일자 DATE NOT NULL;
SQL SERVER 과 ORACLE 에서 SQL 작성 시 괄호 사용 사용 유무의 차이.
- 괄호를 쓰지 않는 경우: ALTER TABLE에서 MODIFY, ALTER COLUMN 사용 시
- 괄호를 쓰는 경우: PRIMARY KEY, FOREIGN KEY, INSERT INTO, SELECT 서브쿼리
즉, SQL Server와 Oracle 모두 괄호 사용 방식이 대부분 동일하며, 컬럼 변경(ALTER TABLE)에서만 괄호를 사용하지 않는 점이 특징입니다
📌 테이블 구조 분석
CREATE TABLE T (
C INTEGER PRIMARY KEY,
D INTEGER
);
CREATE TABLE S (
B INTEGER PRIMARY KEY,
C INTEGER REFERENCES T(C) ON DELETE CASCADE
);
CREATE TABLE B (
A INTEGER PRIMARY KEY,
B INTEGER REFERENCES S(B) ON DELETE SET NULL
);
🔹 테이블 간 관계
- T(C) → S(C):
- S 테이블의 C 컬럼은 T(C)를 참조 (ON DELETE CASCADE)
- T의 데이터 삭제 시 S의 관련 데이터도 자동 삭제됨
- S(B) → B(B):
- B 테이블의 B 컬럼은 S(B)를 참조 (ON DELETE SET NULL)
- S의 데이터 삭제 시 B 테이블에서 B 컬럼의 값이 NULL로 변경됨
🔎 DELETE FROM T; 실행 결과
1️⃣ T 테이블의 모든 데이터 삭제
DELETE FROM T;
- T 테이블의 모든 행이 삭제됨.
2️⃣ S 테이블의 데이터 삭제 (ON DELETE CASCADE)
- S 테이블의 C 컬럼이 T(C)를 참조하고 있으며, ON DELETE CASCADE가 설정되어 있음.
- 따라서, T 테이블의 데이터가 삭제되면 S 테이블의 관련 행도 자동 삭제됨.
3️⃣ B 테이블의 B 컬럼 값이 NULL로 변경 (ON DELETE SET NULL)
- S 테이블의 B 컬럼이 삭제되었으므로, 이를 참조하는 B(B)는 NULL로 변경됨.
- 즉, B 테이블의 B 컬럼 값이 NULL이 됨.
🔎 최종 테이블 상태
테이블변화 내용
T | 모든 행 삭제 |
S | T에서 참조하는 행 삭제 (연쇄 삭제) |
B | S(B) 참조 값이 NULL로 변경 |
VARCHAR 입력 시 특징.
- ✅ VARCHAR(10) 컬럼에는 '1' (문자) 도, 1 (숫자)도 입력 가능함.
- ✅ 숫자를 입력하면 자동으로 문자로 변환되어 저장됨.
- ⚠ 숫자 비교, 정렬 시 오류 발생 가능 → TO_NUMBER() 변환 필요.
- ⚠ 숫자 연산이 많다면 NUMBER 타입을 사용하는 것이 더 적절함.
제약조건의 종류
- PRIMARY KEY(기본키)
- UNIQUE KEY(고유키)
- NOT NULL
- CHECK
- FOREIGN KEY(외래키)
PK = UNIQUE & NOT NULL 의 특성을 가짐. 주키로 테이블당 1개만 생성이 가능하다.
UNIQUE는 테이블 내에서 중복되는 값이 없지만 NULL 입력은 가능하다.
NULL을 반드시 입력해야하는지의 여부.
- NULL을 생략하면 기본적으로 NULL을 허용하는 것과 동일합니다.
- NOT NULL만 필수적으로 명시해야 하며, NULL은 선택 사항입니다.
- 가독성을 위해 NULL을 명시적으로 적는 경우도 있지만, 필수는 아닙니다.
외래키(FK)의 특징
- 테이블 생성시 설정할 수 있다.
- 외래키 값은 NULL값을 가질 수 있다.
- 한 테이블에 여러 개 존재할 수 있다.
- 외래키 값은 참조 무결성 제약을 받을 수 있다.
테이블에서 불필요한 컬럼 삭제 시 활용되는 문장.
:ALER TABLE 테이블명 DROP COLUMN 컬럼명
해설 : TABLE 스키마 변경 시 사용하는 SQL 문은 DDL이다 (CREATE, DROP, ALTER, RENAME)
테이블 이름 변경 : RENAME
:RENAME 기존테이블명 TO 새로운테이블명
테이블에 데이터 입력하기 : INSERT
1번째 방법) INSERT INTO 테이블명 (COLUMN LIST) VALUES (COLUMN LIST에 넣을 VALUE LIST);
2번째 방법) INSERT INTO 테이블명 VALUES (전체 COLUMN 에 넣을 VALUE LIST);
└INSERT 시 주의사항 : 삽입 컬럼을 명시하지 않았을 경우 모든 컬럼에 데이터를 삽입해야 한다.
입력된 데이터의 수정 : UPDATE
UPDATE 테이블명 SET 수정되어야 할 칼럼명 = 수정되기를 원하는 새로운 값
TRUNCATE, DROP, DELETE 차이
- 🔥 빠르게 전체 삭제하려면 TRUNCATE (로그 존재 BUT 복구 불가)
- ❌ 테이블을 완전히 제거하려면 DROP
- ✅ 특정 행만 삭제하거나 복구 가능해야 하면 DELETE (로그 존재 AND 복구 가능) : DELETE FROM 테이블명;
TRUNCATE : 특정 테이블의 모든 데이터를 삭제하고, 디스크 사용량을 초기화하기 위해 사용. 테이블의 스키마 정의 유지.
DELETE TABLE : 테이블의 데이터를 모두 삭제하지만, 디스크 사용량을 초기화 하지는 않는다.
DROP TABLE: 테이블의 데이터를 모두 삭제하고 디스크 사용량도 없앨(초기화)할 수 있지만 테이블의 스키마 정의도 함께 삭제된다.
DROP(테이블 정의 완전 삭제) | TRUNCATE (최초 생성된 초기 상태로) | DELETE(데이터만 삭제) | |
삭제 방식 | DDL (테이블 자체 삭제 ) | DDL(일부 DML 성격 가짐; 모든 행 삭제, 공간 재사용) | DML(특정 행 삭제 가능) |
ROLLBACK(복구) 가능 여부 | ❌ | ❌ | ✅ COMMIT 이전 ROLLBACK 가능 |
COMMIT 종류 | AUTO COMMIT | AUTO COMMIT | 사용자 COMMIT |
STORAGE RELEASE 여부 | 테이블이 사용했던 STORAGE 모두 RELEASE | 테이블이 사용했던 STORAGE 중 최초 테이블 생성시 할당된 STORAGE만 남기고 RELEASE | 데이터를 모두 DELETE해도 사용했던 STORAGE는 RELEASE되지 않음 |
스키마 유지 여부 | ❌ 스키마 삭제됨 (완전 삭제) | ✅ 스키마 유지(테이블 구조 보존) | ✅ 스키마 유지(테이블 구조 보존) |
삭제 범위 | 테이블 + 데이터 + 제약 조건까지 삭제 | 데이터만 삭제(구조 유지, 자동 증가값 초기화) | 데이터만 삭제(구조 유지, 자동 증가값 유지) |
영향 범위 | 테이블 전체 제거 | 데이터만 초기화(구조 유지) | 특정 행만 삭제 가능 |
실행 속도 | 가장 빠름(구조까지 삭제) | 빠름(데이터만 삭제, 블록 정리) | 느림(각 행별로 로그 기록) |
AUTO)INCREMENT 초기화 여부 | ❌테이블 삭제되므로 의미 없음 | ✅ 초기화됨 | ❌초기화되지 않음 |
WHERE 절 사용 가능 여부 | ❌ 불가능 (전체 삭제만 가능) | ❌ 불가능 (전체 삭제만 가능) | ✅ 가능 (특정 데이터 삭제 가능) |
데이터 중복을 제거하는 명령법
1번째 방법) SELECT DISTINCT 컬럼명1,컬럼명2... FROM 해당 컬럼들이 있는 테이블명;
2번째 방법) SELECT 거주지, 근무지 FROM 고객지역 GROUP BY 거주지, 근무지;
참조동작 종류와 상황별 역할.
Dlete(/Modify) Action: Cascade, Set Null, Set Default, Restrict(부서-사원)
1)Cascade : Master 삭제 시 Child 같이 삭제
2)Set Null : Master 삭제 시 Child 해당 필드 Null
3)Set Default : Master 삭제 시 Child 해당 필드 Default 값으로 설정
4)Restrict : Child 테이블에 PK 값이 없는 경우만 Master 삭제 허용
5)No Action : 참조무결성을 위반하는 삭제/수정 액션을 취하지 않음
Insert Action : Automatic, Set Null, Set Default, Dependent(부서-사원)
1)Automatic : Master 테이블에 PK가 없는 경우 Master PK를 생성 후 Child 입력
2)Set Null : Master 테이블에 PK가 없는 경우 Child 외부키를 Null 값으로 처리
3)Set Default : Master 테이블에 PK가 없는 경우 Child 외부키를 지정된 기본값으로 입력
4)Dependent : Master 테이블에 PK가 존재할 때만 Child 입력 허용
5)No Action : 참조무결성을 위반하는 입력 액션을 취하지 않음
데이터베이스 트랜잭션의 4가지 특성
특성 | 설명 | 의미 |
원자성 (Atomicity) | 트랜잭션은 모두 실행되거나 전혀 실행되지 않아야 함 | 하나라도 실패하면 전체 작업이 취소됨 (Rollback 가능) |
일관성 (Consistency) | 트랜잭션 실행 전후로 데이터의 무결성이 유지되어야 함. 트랜잭션이 실행 되기 전의 데이터베이스 내용이 잘못 되어 있지 않다면 트랜잭션이 실행된 이후에도 데이터베이스의 내용이 잘못이 있으면 안된다. |
외래 키(FK), 제약 조건이 유지되어야 함 |
격리성 (Isolation) | 트랜잭션 간의 독립성이 보장되어야 함 | 동시에 여러 트랜잭션이 실행될 때 서로 영향을 미치지 않음 |
지속성 (Durability) | 트랜잭션이 성공적으로 완료되면 결과가 영구적으로 저장되어야 함 | 시스템 장애가 발생해도 데이터가 보존됨 |
데이터베이스의 트랜잭션에 대한 격리성이 낮은 경우 발생할 수 있는 문제.
문제 | 설명 | 발생 원인 |
Dirty Read (더티 리드) | 다른 트랜잭션이 COMMIT하지 않은 변경된 데이터를 읽는 현상 | 트랜잭션이 ROLLBACK하면 읽은 데이터가 사라짐 |
Non-Repeatable Read (비반복 읽기) | 같은 SELECT 쿼리를 여러 번 실행할 때 결과가 다르게 나오는 현상 | 쿼리 여러번 수행 중간에 다른 트랜잭션이 데이터를 수정 또는 삭제했기 때문 |
Phantom Read (팬텀 리드) | 같은 조건으로 조회했을 때 새로운 데이터가 추가되어 결과가 달라지는 현상 | 다른 트랜잭션이 새로운 행을 INSERT |
Lost Update (갱신 손실) | 두 개의 트랜잭션이 같은 데이터를 변경할 때 한 트랜잭션의 변경 내용이 덮어씌워지는 현상 | 마지막 업데이트만 남고 이전 변경 내용이 사라짐 |
Isolation | 트랜잭션이 실행되는 도중에 다른 트랜잭션의 영향을 받아 잘못된 결과를 만들어선 안된다. |
✅ SQL Server와 Oracle의 실행 차이점 정리
- SQL Server와 Oracle은 기본 SQL 문법이 유사하지만, 트랜잭션 처리, AUTO_INCREMENT, 문자열 처리, LIMIT, 조인 방식 등에서 차이가 있음.
- DDL 실행 후 자동 커밋 여부: SQL Server는 ROLLBACK 가능, Oracle은 ROLLBACK 불가능.
- VARCHAR 처리 방식 차이: SQL Server는 공백 비교, Oracle은 자동 TRIM.
- 데이터 삭제 방식 차이: Oracle은 UNDO를 활용해 DELETE 후 복구 가능.
https://belle-sooir.tistory.com/176
SQL Server 과 Oracle 차이점 정리
✅ SQL Server와 Oracle의 실행 차이점 정리 비교 항목 SQL Server Oracle 트랜잭션 자동 커밋DELETE, UPDATE, INSERT 실행 시 자동 커밋 안 됨 (명시적 COMMIT 필요)DELETE, UPDATE, INSERT 실행 시 자동 커밋 안 됨 (명시
belle-sooir.tistory.com
트랜잭션
: 데이터베이스의 논리적 연산단위로서 밀접히 관련되어 분리될 수 는 한 개 이상의 데이터베이스 조작.
BEGIN TRANSACTION으로 트랜잭션을 시작하고,
COMMIT TRANSACTION 또는 ROLLBACK TRANSACTION으로 트랜잭션을 종료한다.
ROLLBACK 구문을 만나면 COMMIT 되지 않은 최초의 BEGIN TRANSACTION 시점까지 모두 ROLLBACK이 수행된다.
SAVEPOINT를 정의하면 롤백할 때 트랜잭션에 포함된 전체 작업을 롤백하는 것이 아니라 현 시점에서 SAVEPOINT까지 트랜잭션의 일부만 롤백할 수 있다.
[Oracle]
SAVEPOINT SVP1;
...
ROLLBACK TO SVP1;
[SQL Server]
SAVE TRANSACTION
SVTR1;
...
ROLLBACK
TRANSACTION
SVTR1;
NULL의 연산
-NULL 값과의 연산(+,-,*,/)은 NULL값을 리턴.
-NULL 값과의 비교연산(=,>,>=,<,<=)은 거짓(FALSE)를 리턴
-특정 값보다 크다, 적다라고 표현할 수 없음
NULL 값을 WHERE 조건절에서 사용하는 경우 IS NULL, IS NOT NULL이란 키워드를 사용해야함.
Oracle vs. SQL Server에서 ''(빈 문자열) 처리 방식 차이
[ORACLE]
''(공백) : NULL로 입력
'' 으로 조건절에 입력하였을 때 해당 데이터를 조회하려면 -> WHERE 서비스명 IS NULL 로 조회해야함.
[SQL SERVER]
'' 으로 조건절에 입력하였을 때 해당 데이터를 조회하려면 -> WHERE 서비스명 = 로 조회해야한다.
📌 빈 문자열('') 비교 (Oracle vs. SQL Server)
구분 | ORACLE | SQL Server |
''(빈 문자열) 저장 시 | NULL로 변환됨 | 빈 문자열('') 그대로 저장됨 |
''(빈 문자열) 검색 시 | IS NULL 사용해야 함 | = '' 사용해야 함 |
문자열 비교 시 | NULL = NULL → FALSE | '' = '' → TRUE |
INSERT 시 동작 차이 | INSERT INTO 서비스 (이름) VALUES (''); → NULL 저장됨 | INSERT INTO 서비스 (이름) VALUES (''); → 빈 문자열 저장됨 |
함수의 입력 행수에 따라 단일행 함수와 다중행 함수로 구분할 수 있다.
단일행 함수는 SELECT, WHERE, ORDER BY, UPDATE의 SET절에 사용이 가능하다.
함수는 벤더에서 제공하는 함수인 내장함수와 사용자가 정의할 수 있는 함수로 나눌 수 있다.
내장함수는 단일행 함수와 다중행 함수로 나눌 수 있으며, 다중행 함수는 집계함수, 그룹함수, 윈도우 함수로 구분된다.
단일행 문자형 함수의 종류(60PG 쓰기)
DUAL 테이블의 특성
-사용자 sys가 소유하며 모든 사용자가 액세스 가능한 테이블.
-SELECT ~ FROM ~ 의 형식을 갖추기 위한 일종의 DUMMY 테이블이다.
-DUMMY라는 문자열 유형의 칼럼에 'X'라는 값이 들어 있는 행을 1건 포함하고 있다.
단일행 함수의 종류(61PG 표로 정리)
종류 | 내용 | 함수의 예 |
문자형 함수 | 문자를 입력하면 문자나 숫자 값을 반환한다. | LOWER,UPPER,SUBSTR/SUBSTRING, LENGTH/LEN. LTRIM, RTRIM,ASCII |
숫자형 함수 | 숫자를 입력하면 숫자 값을 반환한다. | ABS, MOD, BOUND, TRUNC, SIGN, CHR/CHAR, CEIL/CEILING, FLOOR, EXP, LOG, LN, POWER, SIN, COS, TAN |
날짜형 함수 | DATE 타입의 값을 연산한다. | SYSDATE/GETDATE, EXTRACT/DATEPART, TO_NUMBER(TO_CHAR(d, 'YYYY'|'MM'|'DD'))/YEAR|MONTH|DAY |
변환형 함수 | 문자, 숫자, 날짜형 값의 데이터 타입을 변환한다. | TO_NUMBER, TO_CHAR, TO_DATE/ CAST, CONVERT |
NULL 관련 함수 | NULL을 처리하기 위한 함수 | NVL/ISNULL, NULLIF, COALESCE |
COALESCE 함수?
첫번째 NULL이 아닌 값을 반환한다.
NULL 관련 함수
-NVL(표현식1, 표현식2) ORACLE 함수 ISNULL(표현식1, 표현식2)
-SQL SERVER 함수 NULLIF(표현식1, 표현식2)
COALESCE(표현식1, 표현식2)
NVL(표현식1, 표현식2) : 표현식1의 값이 NULL이면 표현식2의 값을 반환한다. = ISNULLL(<-SQL SERVER)
NULLIF(표현식1, 표현식2) : 표현식1이 표현식2와 같으면 NULL을, 같지 않으면 표현식1을 리턴한다.
COALESCE(표현식1, 표현식2) : 임의의 개수 표현식에서 NULL이 아닌 최초의 표현식을 나타낸다.
SEARCHED_CASE-EXPRESSION →(변환) → SIMPLE_CASE_EXPRESSION
: CASE WHEN LOC = 'NEW YORK' THEN 'EAST' → (변환)→ CASE LOC 'NEW YORK' THEN 'EAST'
단일행 NULL 관련 함수의 종류.(46PG)
NULL 의 특성
● NULL 값은 아직 정의되지 않은 값으로 0또는 공백과 다름. 0은 숫자이고, 공백은 하나의 문자이다.
● 테이블을 생성할 때 NOT NULL 또는 PRIMARY KEY로 정의되지 않은 모든 데이터 유형은 널 값을 포함할 수 있다.
● 널 값을 포함하는 연산의 경우 결과 값도 널 값이다. 모르는 데이터인 것과 같다.
● 결과값을 NULL이 아닌 다른 값을 얻고자 할 때 NVL/ISNULL 함수를 사용한다. NULL 값의 대상이 숫자 유형 데이터인 경우는 주로 0(ZERO)으로 문자 유형 데이터인 경우는 블랭크보다는 'x'같이 해당 시스템에서 의미 없는 문자로 바꾸는 경우가 많다.
📌 NULL vs. 실행 결과 없음(공집합) 차이
구분NULL
구분 | NULL | 실행 결과 없음 (공집합) |
의미 | 값이 "없음"을 나타내는 특수한 값 | 조건을 만족하는 데이터가 아예 없음 |
SQL 실행 결과 | 조회된 데이터 중 NULL 값이 존재 | 조회된 행 자체가 없음 |
SELECT 시 결과 | NULL이 포함된 결과 반환 | 아무 행도 반환되지 않음 |
예제 | SELECT NULL FROM DUAL; → NULL 반환 | SELECT * FROM EMP WHERE 1 = 2; → 결과 없음 (공집합) |
집계함수종류
집계함수 | 사용목적 |
COUNT(*) | NULL 값을 포함한 행의 수를 출력 |
COUNT(표현식) | 표현식의 값이 NULL 값인 것을 제외한 행의 수를 출력한다. |
SUM([DISTINCT |ALL] 표현식) | 표현식의 NULL 값을 제외한 합계를 출력한다. |
AVG([DISTINCT |ALL] 표현식) | 표현식의 NULL 값을 제외한 평균을 출력한다. |
MAX([DISTINCT |ALL] 표현식) | 표현식의 최대값을 출력한다. (문자, 날짜 데이터 타입도 사용가능) |
MIN([DISTINCT |ALL] 표현식) | 표현식의 최소값을 출력한다. (문자, 날짜 데이터 타입도 사용가능) |
STDDEV([DISTINCT |ALL] 표현식) | 표현식의 표준 편차를 출력한다. |
VARIAN([DISTINCT |ALL] 표현식) | 표현식의 분산을 출력한다. |
기타 통계 함수 | 벤더별로 다양한 통계식을 제공한다. |
SELECT
FROM
WHERE
GROUP BY
HAVING
GROUP BY 절과 HAVING 절의 특성?
-GROUP BY 절을 통해 소그룹별 기준을 정한 후, SELECT 절에 집계함수를 사용한다.
-집계 함수의 통계 정보는 NULL 값을 가진행을 제외하고 수행한다.
-GROUP BY 절에서는 SELECT 절과는 달리 ALIAS 명을 사용할 수 없다.
-집계 함수는 WHERE 절에는 올 수 없다.(집계 함수를 사용할 수 있는 GROUP BY 절보다 WHERE 절이 먼저 수행된다)
-WHERE 절은 전체 데이터를 GROUP으로 나누기 전에 행들을 미리 제거시킨다.
-HAVING 절은 GROUP BY 절의 기준 항목이나 소그룹 집계 함수를 이용한 조건을 표시할 수 있다.
-GROUP BY 절에 의한 소그룹별로 만들어진 집계 데이터 중, HAVING 절에서 제한 조건을 두어 조건을 만족하는 내용만 출력한다.
-HAVING 절은 일반적으로 GROUP BY 절 뒤에 위치한다.
SQL SERVER와 ORACLE 차이
SQL 실행 순서에 의하면 SELECT절 이후에 ORDER BY 절이 수행되기 때문에 SELECT 절에 기술되지 않는 '년' 칼럼으로 정렬하는 것은 논리적으로 맞지 않다. 하지만 오라클은 행기반 DATABASE 이기에 데이터를 액세스할 때 행 전체 칼럼을 메모리에 로드한다.
그래서 아래와 같은 코드도 정렬이 가능하다. 이와 같은 특성으로 인해 SELECT 절에 기술되지 않은 칼럼으로도 정렬을 할 수 있다.
//가능한 쿼리
//오라클은 SELECT 문에 없는 속성으로 정렬이 가능하다
// 지역별매출 테이블에서 지역과 매출금액을 선택한 후, 년을 기준으로 오름차순 정렬하는 단순한 쿼리
SELECT 지역, 매출금액
FROM 지역별매출
ORDER BY 년 ASC;
단 아래와 같은 SQL은 정렬을 할 수 없다.
//불가능한 정렬
//두 번째 쿼리에서는 서브쿼리가 사용되는데, 서브쿼리에서 ORDER BY 절을 사용할 수 없습니다.
//서브쿼리는 기본적으로 데이터를 반환하는 역할만 하며, 정렬은 바깥 쿼리에서 처리해야 합니다.
SELECT 지역, 매출금액
FROM (
SELECT 지역,매출금액
FROM 지역별매출
)
ORDER BY 년 ASC;
//가능한 쿼리.수정된 쿼리.
SELECT 지역, 매출금액
FROM (
SELECT 지역, 매출금액
FROM 지역별매출
) AS 서브쿼리
ORDER BY 년 ASC;
//실행 불가능한 쿼리
//GROUP BY를 사용하면 SELECT 절에 있는 컬럼은 반드시 그룹화(GROUP BY)되거나,
//집계 함수(예: SUM, AVG)로 감싸져야 함
//년(Year) 컬럼이 SELECT에도 없고 GROUP BY에도 포함되지 않았으므로 오류 발생
SELECT 지역, SUM(매출금액) AS 매출금액
FROM 지역별매출
GROUP BY 지역
ORDER BY 년 DESC;
ORDER BY 실행 시 NULL의 처리 (DBMS 별 차이: ORACLE vs SQL SERVER)
ORDER BY | SQL SERVER |
NULL을 가장 큰 값으로 간주. | NULL을 가장 작은 값으로 간주 |
ORDER BY 절에 대한 설명.
-SQL 문장으로 조회된 데이터들을 다양한 목적에 맞게 특정 칼럼을 기준으로 정렬하는데 사용한다.
-DBMS마다 NULL 값에 대한 정렬 순서가 다를 수 있으므로 주의하여야 한다.
-ORDER BY 절에서 컬럼명 대신 ALIAS 명이나 컬럼 순서를 나타내는 정수(1,2,3)도 사용이 가능하고, 이들을 혼용하여 사용할 수 있다. EX) SELECT 지역, 매출금액 AS 매출액 FROM 지역별매출 ORDER BY 2, 매출액 DESC; --2는 매출금액이 위치한 컬럼 번호.
-GROUP BY 절을 사용하는 경우 ORDER BY 절에 집계 함수를 사용할 수도 있다.
WITH TIES : ORDER BY절과 함께 사용되며 SQL SERVER TOP N 질의문에서
N에 해당하는 값이 동일한 경우 함께 출력되도록 하는 옵션.
//팀별성적 테이블에서 승리건수가 높은 순으로 3위까지 출력하되
//3위의 승리건수가 동일한 팀이 있다면 함께 출력하는 SQL 쿼리.
SELECT TOP(3) WITH TIES 팀명, 승리건수
FROM 팀별성적
ORDER BY 승리건수 DESC;
LEFT OUTER JOIN
조인 수행시 먼저 표기된 좌측 테이블에 해당하는 데이터를 먼저 읽은 후, 나중 표기된 우측 테이블에서 JOIN 대상 데이터를 읽어 온다.
Table A와 B가 있을 때 A와 B를 비교해서 B의 JOIN 컬럼에서 같은 값이 있을 때 그 해당 데이터를 가져오고, B의 JOIN 칼럼에서 같은 값이 없는 경우에는 B테이블에서 가져오는 칼럼들은 NULL 값으로 채운다.
[테이블 : OS]
OSID(PK) | OS명 |
100 | Android |
200 | iOS |
300 | Bada |
[테이블 : 단말기]
단말기ID(PK) | 단말기명 | OSID(FK) |
1000 | A1000 | 100 |
2000 | B2000 | 100 |
3000 | C3000 | 200 |
4000 | D3000 | 300 |
[테이블 : 고객]
고객번호(PK) | 고객명 | 단말기ID(FK) |
11000 | 홍길동 | 1000 |
12000 | 강감찬 | NULL |
13000 | 이순신 | NULL |
14000 | 안중근 | 3000 |
15000 | 고길동 | 4000 |
16000 | 이대로 | 4000 |
SELECT A.고객번호, A.고객명, B.단말기ID, B.단말기명, C.OSID, C.OS명
FROM 고객 A LEFT OUTER JOIN 단말기 B
ON (A.고객번호 IN(11000, 12000) AND A.단말기ID = B.단말기ID) LEFT OUTER JOIN OS C
ON (B.OSID = C.OSID)
ORDER BY A.고객번호;
▶SQL 수행 결과 :
고객번호 | 고객명 | 단말기ID | 단말기명 | OSID | OS명 |
11000 | 홍길동 | 1000 | A1000 | 100 | Android |
12000 | 강감찬 | NULL | NULL | NULL | NULL |
13000 | 이순신 | NULL | NULL | NULL | NULL |
14000 | 안중근 | NULL | NULL | NULL | NULL |
15000 | 고길동 | NULL | NULL | NULL | NULL |
16000 | 이대로 | NULL | NULL | NULL | NULL |
FULL OUTER JOIN
SELECT A.ID, B.ID
FROM TBL1 A, TBL2 B
WHERE A.ID = B.ID
UNION ALL
SELECT A.ID, NULL
FROM TBL1 A
WHERE NOT EXISTS (SELECT 1 FROM TBL2 B WHERE A.ID=B.ID)
UNION ALL
SELECT NULL, B.ID
FROM TBL2 B
WHERE NOT EXISTS (SELECT 1 FROM TBL1 A WHERE B.ID = A.ID)
이 SQL 쿼리는 두 테이블 TBL1과 TBL2를 조인하고, 각 테이블에만 존재하는 값도 포함하여 전체 결과를 출력하는 역할을 합니다.
결과를 이해하기 쉽게 예제 데이터를 사용해 설명하겠습니다.
🎯 테이블 예제 데이터
TBL1
ID1 |
2 |
3 |
TBL2
ID2 |
3 |
4 |
🛠 쿼리 분석
쿼리는 3가지 부분으로 나뉩니다.
1️⃣ INNER JOIN 부분
SELECT A.ID, B.ID
FROM TBL1 A, TBL2 B
WHERE A.ID = B.ID
- A.ID와 B.ID가 같은 경우를 선택 → 교집합
- 결과:
(2, 2) (3, 3)
2️⃣ TBL1에만 있는 값 (LEFT OUTER JOIN의 NULL 처리)
SELECT A.ID, NULL
FROM TBL1 A
WHERE NOT EXISTS (SELECT 1 FROM TBL2 B WHERE A.ID = B.ID)
- TBL1에는 있지만 TBL2에 없는 ID 찾기
- TBL1 = {1, 2, 3}, TBL2 = {2, 3, 4}
- 1은 TBL2에 없으므로 포함됨
- 결과:
(1, NULL)
3️⃣ TBL2에만 있는 값 (RIGHT OUTER JOIN의 NULL 처리)
SELECT NULL, B.ID
FROM TBL2 B
WHERE NOT EXISTS (SELECT 1 FROM TBL1 A WHERE B.ID = A.ID)
- TBL2에는 있지만 TBL1에 없는 ID 찾기
- TBL2 = {2, 3, 4}, TBL1 = {1, 2, 3}
- 4는 TBL1에 없으므로 포함됨
- 결과:
(NULL, 4)
✅ 최종 결과
A.IDB.ID2 | 2 |
3 | 3 |
1 | NULL |
NULL | 4 |
🔥 결론
이 SQL 쿼리는 두 테이블 간의 FULL OUTER JOIN을 수행하는 것과 같은 역할을 합니다.
- INNER JOIN → A.ID = B.ID인 값들 포함
- LEFT JOIN → TBL1에만 있는 값(B.ID = NULL) 포함
- RIGHT JOIN → TBL2에만 있는 값(A.ID = NULL) 포함
즉, TBL1과 TBL2의 모든 ID를 포함한 전체 결과를 얻을 수 있습니다.
아래 두 테이블의 LEFT, FULL, RIGHT 외부조인(OUTER JOIN)의 결과(밑줄 친 속성은 주키, EMP.C는 DEPT와 연결된 외래키)
EMP 테이블
A | B | C |
1 | b | w |
3 | d | w |
5 | y | y |
DEPT 테이블
C | D | E |
w | 1 | 10 |
z | 4 | 11 |
v | 2 | 22 |
LEFT OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
5 | y | y | NULL | NULL |
FULL OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
5 | y | y | NULL | NULL |
NULL | NULL | z | 4 | 11 |
NULL | NULL | v | 2 | 22 |
RIGHT OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
NULL | NULL | z | 4 | 11 |
NULL | NULL | v | 2 | 22 |
쿼리 실행 결과 예측하기
사용여부, 삭제여부
예제 테이블 데이터
📌 게시판 (A)
게시판ID | 게시판명 | 사용여부 |
1 | 공지사항 | Y |
2 | 자유게시판 | Y |
3 | 질문게시판 | N |
📌 게시글 (B)
게시글ID게시판ID삭제여부게시글ID | 게시판ID | 삭제여부 |
101 | 1 | N |
102 | 1 | Y |
103 | 2 | N |
104 | 3 | N |
첫번째 쿼리
SELECT A.게시판ID, A.게시판명, COUNT(B.게시글ID) AS CNT
FROM 게시판 A
LEFT OUTER JOIN 게시글 B
ON (A.게시판ID = B.게시판ID AND B.삭제여부 = 'N') -- 🔹 삭제되지 않은 게시글만 조인됨
WHERE A.사용여부 = 'Y' -- 🔹 사용여부가 'Y'인 게시판만 유지
GROUP BY A.게시판ID, A.게시판명
ORDER BY A.게시판ID;
게시판ID | 게시판명 | CNT |
1 | 공지사항 | 1 |
2 | 자유게시판 | 1 |
두번째 쿼리
SELECT A.게시판ID, A.게시판명, COUNT(B.게시글ID) AS CNT
FROM 게시판 A
LEFT OUTER JOIN 게시글 B
ON (A.게시판ID = B.게시판ID AND A.사용여부 = 'Y') -- 🔹 사용여부가 'Y'인 게시판만 조인됨
WHERE B.삭제여부 = 'N' -- 🔹 삭제되지 않은 게시글만 최종 필터링
GROUP BY A.게시판ID, A.게시판명
ORDER BY A.게시판ID;
게시판ID | 게시판명 | CNT |
1 | 공지사항 | 1 |
2 | 자유게시판 | 1 |
📢 핵심 정리
- 첫 번째 쿼리는 조인할 때부터 삭제여부 = 'N'을 필터링하므로, 애초에 삭제된 게시글은 조인되지 않음.
- 두 번째 쿼리는 조인 후 WHERE B.삭제여부 = 'N'을 적용하므로, 삭제된 게시글이 포함되었다가 WHERE에서 필터링됨.
- 두 번째 쿼리는 게시글이 하나도 없는 게시판이 결과에서 아예 사라질 가능성이 있음.
➡️ 결론:
- **"게시판이 존재하면 게시글이 없어도 보여줘야 한다"**면 첫 번째 쿼리를 사용.
- **"게시글이 있는 게시판만 보고 싶다"**면 두 번째 쿼리를 사용.
집합연산자의 종류
집합 연산자 | 연산자의 의미 |
UNION | 합집합. 중복행 제거 |
UNION ALL | 합집합. 중복행 포함. |
INTERSECT | 교집합. 중복행 제거 |
EXCEPT | 앞의 SQL문의 결과에서 뒤의 SQL문의 결과에 대한 차집합. 중복행 제거 (일부 데이터베이스는 MINUS를 사용함.) |
암시적 JOIN의 사용
※내부적으로 INNER JOIN 사용한 것임.
SELECT A.서비스ID, B.서비스명, B.서비스URL
FROM 서비스이용 A, 서비스 B
WHERE A.서비스ID = B.서비스ID;
명시적 JOIN의 사용
SELECT A.서비스ID, B.서비스명, B.서비스URL
FROM 서비스이용 A
INNER JOIN 서비스 B
ON A.서비스ID = B.서비스ID;
위를 토대로
아래 접은글의 두 쿼리의 차이 확인하기.
INTERSECT와 INNER JOIN의 차이 :
INTERSECT와 INNER JOIN의 차이를 단순히 **"중복 제거 여부"**로만 설명하면 부족해요!
더 정확한 차이점은 다음과 같아요.
✔ INTERSECT → 집합 연산 (두 테이블에서 동일한 SELECT 결과를 가진 행만 남기고, 중복 제거)
✔ INNER JOIN → 조인 연산 (조인 조건에 맞는 행을 매칭하고, 중복 제거 없이 결과 반환)
즉, INTERSECT는 SELECT된 전체 값이 동일한 경우에만 중복을 제거하고,
INNER JOIN은 조인 조건을 만족하는 행들을 조합하여 출력하는 방식이에요!
🔍 첫 번째 쿼리 분석 (INTERSECT 사용)
SELECT A.서비스ID, B.서비스명, B.서비스URL
FROM ( SELECT 서비스ID
FROM 서비스
INTERSECT
SELECT 서비스ID
FROM 서비스이용 A, 서비스 B
WHERE A.서비스ID = B.서비스ID;
🚀 어떻게 동작하는가?
- 서브쿼리 실행 (INTERSECT 연산)
- 서비스 테이블에 있는 서비스ID와
- 서비스이용 테이블과 서비스 테이블을 조인하여 나온 서비스ID를
- INTERSECT를 사용해 공통으로 존재하는 서비스ID만 추출 (중복 제거됨).
-
복사편집SELECT 서비스ID FROM 서비스 INTERSECT SELECT 서비스ID FROM 서비스이용 A, 서비스 B WHERE A.서비스ID = B.서비스ID;
- sql
- 메인 쿼리 실행
- 위에서 나온 서비스ID만 서비스 테이블과 다시 조인하여 서비스명과 서비스URL을 가져옴.
📢 핵심
✅ INTERSECT를 사용하여 서비스 테이블과 서비스이용 테이블에 공통으로 존재하는 서비스ID만 선택됨.
✅ 또한, INTERSECT는 중복을 자동으로 제거하기 때문에 동일한 서비스ID가 여러 번 나와도 한 번만 조회됨.
🔍 두 번째 쿼리 분석 (암시적 INNER JOIN 사용)
SELECT A.서비스ID, B.서비스명, B.서비스URL
FROM 서비스이용 A, 서비스 B
WHERE A.서비스ID = B.서비스ID;
🚀 어떻게 동작하는가?
- 단순히 서비스이용 테이블과 서비스 테이블을 INNER JOIN 해서 일치하는 서비스ID가 있는 모든 행을 가져옴.
- 서비스ID가 중복될 경우 그대로 포함됨.
📢 핵심
✅ INTERSECT를 사용한 첫 번째 쿼리와 달리, 서비스 테이블에 존재하지 않는 서비스ID라도 서비스이용 테이블에 있으면 조회 가능.
✅ INTERSECT는 중복을 제거하지만, 이 쿼리는 중복된 데이터가 그대로 조회됨.
📌 결론: 왜 결과가 다른가?
- 첫 번째 쿼리는 INTERSECT를 사용하여 두 테이블에서 공통으로 존재하는 서비스ID만 조회
→ 즉, 서비스 테이블에도 있고, 서비스이용 테이블에도 있는 서비스ID만 남김.
→ 게다가 INTERSECT는 중복을 제거하므로, 동일한 서비스ID가 여러 번 나타나도 한 번만 조회됨. - 두 번째 쿼리는 단순 JOIN을 사용하여 서비스이용 테이블에 있는 모든 서비스ID를 조회
→ 서비스이용 테이블에만 있는 서비스ID도 포함될 수 있음.
→ 또한, 서비스ID가 여러 번 사용되었다면 중복된 행이 포함될 수 있음.
📢 즉, INTERSECT는 서비스 테이블에도 존재하는 서비스ID만 남기고 중복을 제거하는 반면, 단순 JOIN은 서비스이용 테이블에 있는 모든 데이터를 포함하고 중복도 유지하기 때문에 결과가 다르게 나올 수 있습니다. 🚀
<98pg>
셀프조인 :
같은 테이블을 두 번 이상 조인하여 데이터를 비교하거나 연관된 값을 가져오는 기법입니다. 주로 자기 자신과의 관계를 연결해야 할 때 사용
셀프조인 기본 구조 :
SELECT
ALIAS명1.컬럼명,
ALIAS명2.컬럼명,
FROM
테이블 ALIAS명1,
테이블 ALIAS명2
WHERE
ALIAS명1.컬럼명2 = ALIAS명2.컬럼명1;
셀프조인을 사용하는 경우 :
1.계층적 관계를 표현할 때
- 부모-자식 관계를 표현하는 데 유용합니다. 예를 들어, 부서 테이블에서 상위 부서와 하위 부서 관계를 표현할 때 사용합니다.
예시: 부서 간 상하 관계 (상위 부서와 하위 부서)
SELECT A.부서명 AS 하위부서, B.부서명 AS 상위부서
FROM 부서 A
LEFT JOIN 부서 B ON A.상위부서코드 = B.부서코드;
부서 A와 부서 B는 동일한 부서 테이블을 두 번 참조하며, 상위부서코드와 부서코드를 연결하여 상위/하위 부서를 찾습니다.
2.같은 테이블 내에서 데이터 비교
- 같은 테이블 내에서 두 데이터 간 비교가 필요할 때 사용합니다. 예를 들어, 급여가 더 높은 직원이나 상위 직급을 찾을 때 유용합니다.
예시: 급여가 더 높은 직원 찾기
SELECT E1.직원명, E1.급여
FROM 직원 E1, 직원 E2
WHERE E1.급여 > E2.급여;
3.이전/다음 레코드를 찾을 때
- 시간적 순서나 순차적 관계가 필요할 때 사용합니다. 예를 들어, 이전 판매 기록을 찾거나 다음 게시글을 찾는 경우에 사용합니다.
예시: 같은 테이블에서 이전 게시글 찾기
SELECT A.게시글번호, A.제목, B.제목 AS 이전게시글
FROM 게시글 A
LEFT JOIN 게시글 B ON A.게시글번호 = B.게시글번호 + 1;
4.중복된 데이터 처리
- 중복된 데이터를 제거하거나 특정 조건을 기준으로 유일한 값을 추출해야 할 때 사용합니다.
5.단일 테이블 내에서 순서대로 나열할 때
- 테이블 내의 레코드들을 자기 자신과 비교하면서 원하는 순서대로 나열하거나 관계를 파악할 때 사용합니다.
cf) COUNT(DISTINCT A || B)의 의미
COUNT(DISTINCT A || B)의 의미
- A || B: 컬럼 A와 컬럼 B의 값을 문자열로 결합한 결과.
- DISTINCT: 결합된 결과에서 중복된 값을 제외하고 고유한 값만을 선택.
- COUNT: 고유한 결합된 값의 개수를 셈.
즉, COUNT(DISTINCT A || B)는 컬럼 A와 B의 값이 결합된 후 중복되지 않는 고유한 결합 값을 세는 쿼리입니다.
📚 예시
- 컬럼 A: ['홍길동', '김철수', '홍길동']
- 컬럼 B: ['서울', '부산', '서울']
A || B로 결합된 값은:
- '홍길동서울'
- '김철수부산'
- '홍길동서울'
서브쿼리 종류와 설명.
- 단일 행 서브쿼리 (Single-Row): 하나의 값을 반환하는 서브쿼리.
- 단일 행 비교연산자 : =, <, >
- 예시
설명: 이 쿼리는 직원 테이블에서 급여가 가장 높은 직원의 이름을 찾습니다.
내부 서브쿼리는 가장 높은 급여 값을 반환합니다.
SELECT 이름
FROM 직원
WHERE 급여 = (SELECT MAX(급여) FROM 직원);
- 다중 행 서브쿼리 (Multiple-Row): 여러 행(여러 값)을 반환하는 서브쿼리. IN 연산자와 함께 자주 사용됨.
- 다중 행 연산자 : IN, ALL, ANY, SOME, EXISTS
- 예시
설명: 이 쿼리는 IT 부서에 속한 모든 직원을 찾습니다. 내부 서브쿼리는 IT 부서의 부서ID를 반환합니다.
SELECT 이름
FROM 직원
WHERE 부서ID IN (SELECT 부서ID FROM 부서 WHERE 부서명 = 'IT');
- 다중 열 서브쿼리 (Multiple-Column): 여러 열을 반환하는 서브쿼리.
- 주로 **IN**이나 = 연산자와 함께 사용되며, 여러 값을 비교하는 데 사용됨.
- 예시
설명: 이 쿼리는 매니저와 같은 부서에서 동일한 급여를 받는 직원을 찾습니다. 내부 서브쿼리는 부서ID와 급여 값을 반환합니다.
SELECT 이름
FROM 직원
WHERE (부서ID, 급여) IN (SELECT 부서ID, 급여 FROM 직원 WHERE 직위 = 'Manager');
서브쿼리 사용시 주의사항
①서브쿼리를 괄호로 감싸서 사용한다.
②서브쿼리는 단일 행 또는 복수 행 비교 연산자와 함께 사용 가능하다.
③단일 행 비교 연산자는 서브쿼리의 결과가 반드시 1건 이하여야 한다.
④복수 행 비교 연산자는 서브쿼리의 결과 건수와 상관 없다.
⑤다중 컬럼 서브쿼리는 서브쿼리의 결과로 여러 개의 컬럼이 반환되어 메인 쿼리의 조건과 비교되는데, SQL Server에서는 현재 지원하지 않는 기능이다.
⑥연관 서브쿼리는 서브쿼리가 메인쿼리 컬럼을 포함하고 있는 형태의 서브쿼리이다.-서브쿼리의 결과가 복수 행 결과를 반환하는 경우에는 IN,ALL,ANY 등의 복수 행 비교 연산자와 사용하여야 한다.
▼ CF) IN,ALL,ANY 연산자 예시와 설명
1. IN 연산자
IN 연산자는 서브쿼리가 반환하는 여러 값을 리스트처럼 다루고, 외부 쿼리의 값을 그 리스트 중 하나와 비교합니다. 즉, 외부 쿼리의 값이 서브쿼리에서 반환한 값 중 하나에 포함되는지 여부를 체크합니다.
예시:
SELECT 이름
FROM 직원
WHERE 부서ID IN (SELECT 부서ID FROM 부서 WHERE 부서명 = 'IT');
- 설명: 서브쿼리는 부서명이 'IT'인 부서의 ID들을 반환합니다. 그 반환된 부서ID들 중 하나에 부서ID가 포함되는 직원들을 찾는 쿼리입니다.
2. ALL 연산자
ALL 연산자는 서브쿼리가 반환한 모든 값에 대해 비교 연산을 수행합니다. 외부 쿼리의 값이 서브쿼리에서 반환한 모든 값과 비교되어야 하며, 외부 값이 서브쿼리의 결과값에 대해 참인 조건이 맞아야만 결과를 반환합니다.
예시:
SELECT 이름
FROM 직원
WHERE 급여 > ALL (SELECT 급여 FROM 직원 WHERE 부서ID = 3);
- 설명: 서브쿼리는 부서ID가 3인 직원들의 급여를 반환합니다. 외부 쿼리는 급여가 부서ID가 3인 모든 직원들의 급여보다 높은 직원들을 찾습니다. 즉, 외부 쿼리의 급여가 모든 부서ID가 3인 직원들의 급여보다 크다면 해당 직원이 출력됩니다.
3. ANY 연산자
ANY 연산자는 서브쿼리가 반환하는 하나 이상의 값과 비교하여, 외부 쿼리 값이 그 중 하나와 일치하면 결과를 반환합니다. ANY는 하나라도 참이면 외부 쿼리의 조건이 만족되도록 합니다.
예시:
SELECT 이름
FROM 직원
WHERE 급여 > ANY (SELECT 급여 FROM 직원 WHERE 부서ID = 3);
- 설명: 서브쿼리는 부서ID가 3인 직원들의 급여를 반환합니다. 외부 쿼리는 급여가 부서ID가 3인 직원들 중 하나보다 큰 직원을 찾습니다. 즉, 외부 쿼리의 급여가 부서ID가 3인 직원들 중 최소한 하나의 급여보다 크면 해당 직원이 결과에 포함됩니다.
📝 요약
- IN: 서브쿼리에서 반환된 값들 중 하나와 일치하는 값을 찾을 때 사용합니다.
- ALL: 서브쿼리에서 반환된 모든 값과 비교하여 외부 쿼리 값이 모두 더 크거나 작은지 확인할 때 사용합니다.
- ANY: 서브쿼리에서 반환된 하나 이상의 값과 외부 값이 비교되며, 하나라도 조건을 만족하면 결과에 포함됩니다.
따라서, 서브쿼리가 복수 행을 반환할 경우, IN, ALL, ANY 같은 연산자를 사용하여 외부 쿼리와 비교해야 올바른 결과를 얻을 수 있습니다.
⑦서브쿼리에서는 ORDER BY를 사용하지 못한다.ORDR BY 절은 SELECT 절에서 오직 한 개만 올 수 있기 때문에 ORDER BY 절은 메인쿼리의 마지막 문장에 위치해야 한다.
⑧서브쿼리는 SELECT, FROM, HAVING, ORDER BY 절 등에서 사용이 가능하다.
▼CF)서브쿼리에서 ORDER BY 사용
1. 서브쿼리에서 ORDER BY 사용
- 서브쿼리에서는 ORDER BY를 사용할 수 없다고 제한하는 경우가 있습니다.
- 기본적으로 서브쿼리 내에서 ORDER BY 절은 필요하지 않습니다. 왜냐하면 서브쿼리는 데이터를 가공하는 용도일 뿐, 그 결과의 순서를 보장할 필요가 없기 때문입니다. ORDER BY는 보통 결과를 정렬하려는 의도에서 사용되는데, 서브쿼리에서 결과의 순서는 외부 쿼리에서 정렬하기 때문입니다.
서브쿼리에서 ORDER BY 사용 예시:
서브쿼리에서 ORDER BY를 사용하면 에러가 발생하거나 불필요할 수 있습니다.
SELECT *
FROM (SELECT 부서ID, 이름 FROM 직원 ORDER BY 급여);
- 이 쿼리는 에러가 발생할 수 있습니다. 서브쿼리 내부에서 ORDER BY를 사용할 때는 LIMIT이나 FETCH 절과 함께 사용해야 결과가 제한되는 경우에만 의미가 있습니다.
서브쿼리 내에서 ORDER BY가 가능한 경우:
서브쿼리 내에서 ORDER BY는 결과를 제한할 때만 사용할 수 있습니다. 예를 들어, LIMIT이나 FETCH FIRST와 함께 사용됩니다.
SELECT *
FROM (SELECT 부서ID, 이름 FROM 직원 ORDER BY 급여 DESC FETCH FIRST 5 ROWS ONLY);
- 이런 경우 서브쿼리 내에서 ORDER BY가 유효하고, 5명의 상위 급여를 가진 직원들만 반환됩니다.
2. 서브쿼리가 사용될 수 있는 위치
서브쿼리는 SELECT, FROM, WHERE, HAVING, ORDER BY 등 다양한 곳에서 사용할 수 있습니다. 이 말은 서브쿼리 자체가 여러 구문에서 유효하다는 것입니다.
- SELECT 절에서 서브쿼리를 사용해 계산된 값을 선택할 수 있습니다.
- FROM 절에서 서브쿼리를 사용해 테이블처럼 취급할 수 있습니다.
- WHERE 절에서 서브쿼리를 사용해 조건을 설정할 수 있습니다.
- HAVING 절에서 서브쿼리를 사용할 수 있습니다.
- ORDER BY 절에서도 서브쿼리를 사용할 수 있지만, 여기서는 서브쿼리 자체의 순서를 정렬하는 것이 아니라 외부 쿼리 결과의 정렬이 목적입니다.
결론
- 서브쿼리 내에서 ORDER BY는 보통 사용되지 않지만, 특정 조건에서 LIMIT 또는 FETCH 절과 함께 사용할 수 있습니다.
- 서브쿼리는 SELECT, FROM, WHERE, HAVING, ORDER BY 절 등에서 모두 사용이 가능합니다. 다만, ORDER BY는 외부 쿼리의 결과를 정렬할 때 주로 사용되고, 서브쿼리 내에서는 순서가 필요하지 않기 때문에 일반적으로 사용하지 않는다고 이해하면 됩니다.
두 개의 설명이 상충되지 않는 이유는 서브쿼리 내에서 ORDER BY를 사용할 수 있는 조건이 특정 상황에 한정되기 때문입니다.
⑨다중행 서브쿼리 비교 연산자는 단일 행 서브쿼리의 비교 연산자로도 사용할 수 있다.
⑩비 연관 서브쿼리가 주로 메인쿼리에 값을 제공하기 위한 목적으로 사용됨
ⓐ메인 쿼리의 결과가 서브쿼리로 제공될 수도 있고, 서브쿼리의 결과가 메인쿼리로 제공될 수도 있다.
#97 문제풀이 답 오류 : 4번이 답.
SELECT A.회원번호, A.회원명
FROM 회원 A, 동의항목 B
WHERE A.회원번호 = B.회원번호
GROUP BY A.회원번호, A.회원명
HAVING COUNT(CASE WHEN B.동의여부 = 'N' THEN 0 ELSE NULL END)>=1
ORDER BY A.회원번호;
4. HAVING 절:
HAVING COUNT(CASE WHEN B.동의여부 = 'N' THEN 0 ELSE NULL END) >= 1
- HAVING 절은 그룹화된 데이터에 대해 조건을 걸기 위한 절입니다.
- COUNT(CASE WHEN B.동의여부 = 'N' THEN 0 ELSE NULL END):
- CASE문을 사용하여 B.동의여부가 'N'인 경우에만 0을 반환하고, 그렇지 않으면 NULL을 반환합니다.
- 이 COUNT 함수는 'N'인 동의여부가 1개 이상인 회원을 찾습니다. 즉, 동의여부가 'N'인 항목이 하나 이상 있는 회원만 결과에 포함됩니다.
- >= 1로 설정되었으므로, 하나라도 'N'인 동의항목이 있으면 그 회원이 선택됩니다.
#98 메인쿼리 서브쿼리에 대한 이해, HAVING COUNT 절의 이해, WHERE EXIST 절의 이해.
SELECT A.회원ID, A.회원명, A.이메일
FROM 회원 A
WHERE EXISTS (
SELECT 'X'
FROM 이벤트 B, 메일발송 C
WHERE B.시작일자 >= '2014.10.01'
AND B.이벤트ID = C.이벤트ID
AND A.회원ID = C.회원ID
AND A.회원ID = C.회원ID
HAVING COUNT(*) < (
SELECT COUNT(*)
FROM 이벤트
WHERE 시작일자 >= '2014.10.01'
)
);
WHERE B.시작일자 >= '2014.10.01'
AND B.이벤트ID = C.이벤트ID
AND A.회원ID = C.회원ID
이 조건들은 이벤트 테이블과 메일발송 테이블을 결합한 후 어떤 회원이 어떤 이벤트를 받았는지를 특정하기 위한 필터입니다. 그 결과로 나온 행들에 대해 COUNT(*)가 적용됩니다.
▽이렇게 이해하시면 됩니다:
- B.시작일자 >= '2014.10.01': 2014년 10월 1일 이후에 시작한 이벤트만 포함합니다.
- B.이벤트ID = C.이벤트ID: 이벤트와 메일발송 테이블을 이벤트ID 기준으로 결합합니다.
- A.회원ID = C.회원ID: 회원과 메일 발송 기록을 매칭합니다. 즉, 특정 회원이 어떤 이벤트에 참여했는지 찾습니다.
이 세 조건을 모두 만족하는 데이터들만 남게 됩니다.
쿼리 해석:
- 메인 쿼리:
SELECT A.회원ID, A.회원명, A.이메일
FROM 회원 A
WHERE EXISTS ( ... )- 여기서는 회원 테이블에서 회원의 회원ID, 회원명, 이메일 컬럼을 가져옵니다. WHERE EXISTS는 서브쿼리가 참인 경우에만 데이터를 선택하게 합니다.
- 즉, 회원이 특정 조건을 만족하는지 여부를 판단하고 있습니다.
- 서브쿼리:
SELECT 'X'
FROM 이벤트 B, 메일발송 C
WHERE B.시작일자 >= '2014.10.01'
AND B.이벤트ID = C.이벤트ID
AND A.회원ID = C.회원ID
HAVING COUNT(*) < ( SELECT COUNT(*) FROM 이벤트 WHERE 시작일자 >= '2014.10.01' )
- 이 서브쿼리는 이벤트와 메일발송 테이블을 INNER JOIN처럼 조인하고 있습니다.
- B.시작일자 >= '2014.10.01': 이벤트 시작일자가 2014.10.01 이후인 것만 필터링.
- B.이벤트ID = C.이벤트ID: 이벤트와 메일발송이 일치해야 하므로 이벤트 ID를 기준으로 매칭.
- A.회원ID = C.회원ID: 이벤트에 해당 회원이 참여했는지 확인.
- 이 서브쿼리는 이벤트와 메일발송 테이블을 INNER JOIN처럼 조인하고 있습니다.
- HAVING COUNT(*) < ...
- 여기에서 중요한 부분은 HAVING COUNT(*)입니다.
- COUNT(*)는 조인된 데이터에서 행의 개수를 셉니다. 즉, 해당 회원이 참여한 이벤트와 메일발송의 수를 셉니다.
- 그 수가, 서브쿼리 내에서 구한 전체 이벤트 수보다 적어야 합니다.
- 서브쿼리에서 COUNT(*)는 2014.10.01 이후 시작된 이벤트의 개수를 세고 있습니다.
이 부분의 의미는?
HAVING COUNT(*) < (SELECT COUNT(*) FROM 이벤트 WHERE 시작일자 >= '2014.10.01')
- 이벤트 테이블에서 시작일자가 2014.10.01 이상인 이벤트의 총 개수를 구하고, 이 값과 각 회원이 참여한 이벤트 수를 비교합니다.
- 회원이 참여한 이벤트 수가 전체 이벤트 수보다 적으면 그 회원은 결과에서 제외됩니다.
- 즉, 2014.10.01 이후의 이벤트에 대해 메일을 발송하지 못한 회원만 찾고 있다는 거죠.
결론:
- 회원 테이블에서 2014.10.01 이후의 이벤트에 대해 메일을 발송한 회원을 찾는 것이 아니라, 메일을 발송하지 못한 회원을 찾는 것입니다.
- 메일을 발송한 이벤트 수가 전체 이벤트 수보다 적은 경우, 그 회원이 조건에 맞는 회원이 됩니다.
말하자면, 이 쿼리는 누락된 메일 발송이 있는 회원을 찾는 쿼리입니다.
좋은 질문이에요! HAVING COUNT(*)가 모든 행의 수가 아닌 각 회원을 세는 방식으로 동작하는 이유는 **그룹화 (GROUP BY)**와 관련이 있습니다.
이 쿼리에서 HAVING COUNT(*)는 사실 그룹화된 결과에서 각 회원별로 발송된 메일 수를 셈으로써 작동합니다. 이게 어떻게 이루어지는지 좀 더 자세히 설명할게요.
1. HAVING COUNT(*)의 기본 동작
COUNT(*)는 FROM 절에서 조인된 테이블에서 필터링된 행의 수를 셉니다. 그런데 이 경우, 서브쿼리 안에서 이벤트 B와 메일발송 C를 결합하면서 각 회원에 대해 그룹화가 일어나게 됩니다.
2. 그룹화된 상태에서 COUNT(*) 사용
여기서 그룹화란, A.회원ID = C.회원ID 조건으로 각 회원별로 그룹이 나누어지는 것입니다.
- A.회원ID = C.회원ID: 각 회원이 참여한 이벤트의 메일 발송 개수를 셀 수 있도록 회원별로 그룹화됩니다.
- 그 후, HAVING COUNT(*)가 **각 회원이 참여한 이벤트 수 (메일 발송 수)**를 세는 방식으로 동작합니다.
3. 예시로 풀어보자면
각 회원마다 이벤트와 메일발송이 일치할 때마다 하나의 행이 나옵니다. 예를 들어, 회원 A가 이벤트 3개에 참여하고 메일을 발송했다면, 메일발송 테이블에 3개의 행이 있을 것입니다. 이렇게 3개 행을 하나의 그룹으로 묶은 뒤, 그 그룹 내에서 COUNT(*)를 사용하면 그 회원이 참여한 이벤트 수를 셈할 수 있습니다.
4. 왜 각 회원을 세는가 되는가?
서브쿼리 내에서 조건을 걸어서 A.회원ID = C.회원ID로 매칭을 하고, 그 후 그 회원이 참여한 이벤트의 수를 행의 개수로 세는 것입니다.
그룹화 후에는 각 회원마다 하나의 그룹이 형성되기 때문에 COUNT(*)는 그 회원이 참여한 이벤트 수를 셉니다.
결론
HAVING COUNT(*)는 각 회원을 세는 것처럼 보이지만, 실제로는 각 회원별로 그룹화된 결과에서 메일 발송 개수를 세는 것입니다. HAVING 절은 그룹화된 각 회원에 대해 조건을 평가하기 때문에 전체 행 수가 아닌 각 회원별 메일 발송 개수가 계산됩니다.
참고자료 : sql 자격검정 실전문제 (노랭이 책)