본문 바로가기

(09.11) 데이터베이스 - JOIN 심화, 상관 서브쿼리, 집합 연산, 윈도우 함수, DDL과 DB설계

@starweb2025. 9. 11. 19:25

[ 5주차 - 0911 ]

    금일 커리큘럼
        ├ 09:00 ~ 12:00 관계형 데이터베이스 (JOIN 심화 : 범위 / 외부 / 자기자신, 서브쿼리)
        └ 13:00 ~ 18:00 관계형 데이터베이스 (상관 서브쿼리, 집합 연산, 윈도우 함수, DDL과 DB설계)

1. JOIN 심화 : 범위 / 외부 / 자기자신

현재 lionDB ERD

범위 - non-equi join

  • 정의: 조인 조건에 = 대신 BETWEEN, <, > 같은 비교 연산자를 사용하는 방식
  • 용도: 매칭이 필요한 경우 (예: 급여 등급 찾기, 성적학점 부여, 상품 가격-할인율 매칭)
/* non-equi join */
SELECT e.ename, e.sal, s.grade FROM emp e, salgrade s
    WHERE e.sal BETWEEN s.min_salary and s.max_salary;
    -- emp.sal 값이 salgrade 테이블의 min_salary ~ max_salary 범위에 속하면 매칭
    -- 조인을 통해 급여 등급 확인 가능

외부 - outer join

  • 정의: 한쪽 테이블에는 데이터가 없더라도 다른 쪽 테이블의 데이터는 반드시 포함시키는 조인.
  • 종류 : LEFT OUTER JOIN, RIGHT OUTER JOIN, FULL OUTER JOIN // or UNION
    • 오라클 = FULL OUTER JOIN
    • mysql = UNION
  • 용도
    • left : 부서 배정 안된 직원 찾기 (모든직원 <-> 매칭부서값)
    • right : 직원 없는 부서 찾기 (모든부서 <-> 매칭직원값)
    • left + right union : 모든 직원, 모든 부서 다 출력
/* outer join */

-- left (좌측 테이블 emp 정보 모두 포함)
SELECT e.ename, e.deptno, d.dname FROM emp e
    LEFT OUTER JOIN dept d USING(deptno);
    -- dept 에 해당부서없으면 dname(부서명) 은 null로 나타남

-- right (우측 테이블 dept 정보 모두 포함)
SELECT e.ename, e.deptno, d.dname FROM emp e
    RIGHT OUTER JOIN dept d USING(deptno);
    -- emp 에 해당 부서 직원이 없으면 ename(직원명) 은 null로 나타남

-- union (두 테이블 합집합)
SELECT e.ename, e.deptno, d.dname FROM emp e
    RIGHT OUTER JOIN dept d USING(deptno)
    UNION
    SELECT e.ename, e.deptno, d.dname FROM emp e
    LEFT OUTER JOIN dept d USING(deptno);
    -- emp 직원, dept 부서 없어도 둘다 합집합으로 null로 나옴
    -- 즉 모든 직원 + 모든 부서 정보 한번에 노출

자기자신 - self join

  • 정의 : 자기 자신과 자기 자신을 조인하는 방식
  • 하나의 테이블을 두 번 불러와서(별칭 사용) 마치 서로 다른 테이블처럼 연결
  • 주로 계층 구조(상위/하위 관계) 표현할 때 사용
SELECT 
    e.empno as 사원번호,
    e.ename as 사원명,
    m.empno as '담당 매니저번호',
    m.ename as '담당 매니저명'
    FROM emp e, emp m
    WHERE e.mgr = m.empno;
    -- 사원 담당 mgr 값 기준으로 사원의 매니저를 같은 테이블에서 찾아 매칭

2. 서브쿼리 - SubQuery

하나의 SQL 구절 속에 다른 SQL 구절이 포함되어 있는 형태

single row 서브쿼리

  • 서브쿼리 결과값 하나

-- 스미스 사원이 속한 부서의 평균 급여
SELECT avg(sal) FROM emp 
    WHERE deptno = (
        SELECT deptno FROM emp
        WHERE ename = 'smith'
    );

-- 전체 평균 급여보다 적은 급여를 받는 사원
SELECT ename, sal FROM emp
    WHERE sal <= (
        SELECT avg(sal) FROM emp
    );

-- 가장 먼저 입사한 사원
SELECT ename, hiredate FROM emp
    WHERE hiredate = (
        SELECT min(hiredate) FROM emp
    );

-- 부서이름이 SALES 인 사원 정보
SELECT * FROM emp
    WHERE deptno = (
        SELECT deptno FROM dept
        WHERE dname = 'SALES'
    );

multi row 서브쿼리

  • 서브쿼리 결과값 여러개
  • in, any, all 활용
/* in 연산자 : 무조건 '='이고, 내부 값 중 하나라도 만족 */
-- 부하직원이 있는(= 누군가의 mgr로 등록된) 사원만 조회
SELECT empno, ename FROM emp
    WHERE empno IN (
        SELECT mgr
        FROM emp
        WHERE mgr IS NOT NULL   
        -- 여러 행 반환 (매니저 사번들의 집합)
    );


/* any 연산자 */
-- 비교 연산시 any () 안의 값들 중 하나 만족할 경우 = 참
SELECT ename, deptno, sal FROM emp
    -- 30번 부서 사람들 급여보다 하나라도 작을 경우
    WHERE sal > any (
        SELECT sal FROM emp
        WHERE deptno = 30
    );


/* all 연산자 */
-- 비교 연산시 all () 안의 값들 중 전부 만족할 경우 = 참
SELECT ename, deptno, sal FROM emp
    -- 30번 부서 사람들 급여보다 모두 작을 경우
    WHERE sal > all (
        SELECT sal FROM emp
        WHERE deptno = 30
    );
  • any , all 비교 연산 : =, <, >=, <, <=, <> (-하나라도다른경우 참)

3. 상관 서브쿼리 - Correlated SubQuery

외부쿼리와 () 안에 있는 내부쿼리가 서로 연관되어 실행하는 서브쿼리

기본형

-- 사원의 급여가 자기 부서의 평균 급여보다 큰 경우
SELECT ename, sal, deptno FROM emp e
    WHERE e.sal > (
        SELECT avg(i.sal) FROM emp i
        WHERE i.deptno = e.deptno
        -- 내부부서번호 = 외부부서번호 (대상직원) 일치한 부서의 평균 구하여
        -- 비교함 (외부쿼리 튜플마다)
    );

exists 연산

-- 후임이 있는 직원들만 조회
SELECT e.empno, e.ename FROM emp e
    WHERE EXISTS (
        -- SELECT 1 : 행이 하나라도 존재하는 지
        SELECT 1 FROM emp i
        WHERE i.mgr = e.empno
        -- 내부쿼리 사원번호 = 외부 사원번호
        -- 나 자신이 후임이 있는 경우
    );

다중 컬럼

-- 부서마다 부서 내에서 최고급여 받는 사원
SELECT ename, deptno, sal FROM emp
    -- (부서, 급여) = ( 부서1의 최고급여, 부서2의 최고급여 ...)
    WHERE (deptno, sal) in (
        SELECT deptno, max(sal) FROM emp
        GROUP BY deptno
    );


-- 부서 내 최고 급여를 받고 있는 사원의 모든 정보와 최고급여 컬럼 추가
SELECT e.*, m.max_sal FROM emp e
    JOIN (
        SELECT deptno, max(sal) as max_sal FROM emp
        GROUP BY deptno
    ) m on e.deptno = m.deptno and e.sal = m.max_sal; 

4. 집합 연산자


/* 테스트용 테이블 */
CREATE TABLE A (name INT);
CREATE TABLE B (name INT);
INSERT INTO A VALUES (1), (2), (3);
INSERT INTO B VALUES (2), (3), (4);


/* UNION : 중복제거 합집합 */
SELECT name FROM A
UNION
SELECT name FROM B;
-- 결과: 1, 2, 3, 4


/* UNION ALL : 중복포함 합집합 */
SELECT name FROM A
UNION ALL
SELECT name FROM B;
-- 결과: 1, 2, 3, 2, 3, 4


/* INTERSECT : 교집합 */
/*
    SELECT name FROM A
    INTERSECT
    SELECT name FROM B;
*/
-- mysql은 지원안해서 조인 대체 
SELECT DISTINCT A.name 
FROM A 
INNER JOIN B ON A.name = B.name;
-- 결과: 2, 3



/* MINUS : 차집합 */
/*
    SELECT name FROM A
    MINUS
    SELECT name FROM B;
*/
-- mysql은 지원안해서 서브쿼리 대체 
SELECT name FROM A
WHERE name NOT IN (
    SELECT name FROM B
);
-- 결과: 1

5. 윈도우 함수

rank () 순위매김 : 동일순위 건너뜀 (1,2,2,4)

-- 급여 순위 매기기
SELECT 
    sal,
    ename,
    rank() over(ORDER BY sal desc) as ranking
FROM emp;

-- 부서별 급여 순위
SELECT 
    deptno,
    ename,
    sal,
    rank() OVER(PARTITION BY deptno ORDER BY sal desc) as dept_rank
FROM emp;

dense_rank() 순위매김 : 동일순위 무시 (1,2,2,3)

-- 동일 순위 다음 순위 건너뛰지 않음
SELECT
    sal,
    ename,
    dense_rank() over(ORDER BY sal desc) as dense_ranking
FROM emp;

row_number () 목록번호 : 1부터

-- 행 번호 부여
SELECT
    row_number() OVER(ORDER BY sal desc) as row_num,
    ename,
    sal
FROM emp;

6. DDL과 데이터베이스 설계

DDL, DML, DCL, TCL

SQL 분류 정의 설명 명령어
DDL 데이터 정의 언어 스키마(테이블/뷰/인덱스) 생성·변경·삭제 CREATE, ALTER, DROP, TRUNCATE
DML 데이터 조작 언어 행 데이터 삽입·수정·삭제·조회 INSERT, UPDATE, DELETE, SELECT
DCL 데이터 제어 언어 사용자/역할 권한 부여·회수 GRANT, REVOKE
TCL 트랜잭션 제어 언어 트랜잭션 확정·취소(필요 시 저장점) COMMIT, ROLLBACK

DDL 명령어 요약

  • CREATE TABLE: 테이블 생성
  • ALTER TABLE: 테이블 구조 변경
  • DROP TABLE: 테이블 삭제
  • RENAME: 이름 변경
  • TRUNCATE: 테이블의 모든 데이터 삭제
  • COMMENT: 테이블에 설명 추가

MySQL 데이터타입

분류 타입
숫자형 tinyint(1), int(small,medium,big), decimal(m,d), float, double, bit(m)
문자형 varchar, char, text
날짜형 date, time, datetime, timestamp, year
기타 json, enum, set, blob, varbinary, binary(16) uuid, geometry/point

-- 숫자형
tinyint(1)            -- 불리언(0/1), boolean 별칭
smallint              -- 작은 정수 (2byte)
mediumint             -- 중간 크기 정수 (3byte)
int                   -- 가장 흔한 정수 (4byte)
bigint                -- 매우 큰 정수 (8byte)
decimal(12,2)         -- 전체 12자리, 소수 2자리
float                 -- 근사 실수(센서값 등), 돈엔 사용 지양
double                -- 더 정밀한 근사 실수

-- 문자형
varchar(255)          -- 가변 길이 문자열(일반 텍스트)
char(2)               -- 고정 길이 코드(국가코드 등)
text                  -- 긴 텍스트

-- 날짜형
date                  -- 'yyyy-mm-dd'
time                  -- 'hh:mm:ss'
datetime              -- 'yyyy-mm-dd hh:mm:ss' (타임존 변환 없음, 범용)
timestamp             -- 타임존 변환/default/on update 편리(2038 이슈)
year                  -- 연도만 저장

-- 기타
json                  -- json 컬럼; 생성 컬럼+인덱스 조합 권장
enum('a','b',...)     -- 제한된 값 중 하나(스키마 변경 부담, 신중히)
set('a','b',...)      -- 제한된 값 중 다중 선택(역시 신중히)
blob                  -- 바이너리 데이터(큰 파일은 외부 스토리지 권장)
varbinary(16)         -- 바이너리 토큰/ip 등
binary(16)            -- uuid 압축 저장( uuid_to_bin()/bin_to_uuid() )
point / geometry      -- 위치 정보(gis); 공간 인덱스와 함께 사용

테이블 생성

기본 문법

CREATE TABLE 테이블이름 (
    필드이름1 필드타입1 [제약조건],
    필드이름2 필드타입2 [제약조건],
    ...
    [테이블 제약조건]
);

제악 조건

키워드 의미
primary key 행을 유일하게 식별하는 키(중복·NULL 불가)
foreign key 다른 테이블의 키를 참조하여 참조 무결성 보장
unique 값의 중복을 금지(MySQL에선 NULL 여러 개 허용)
not null NULL 값 입력 금지
check (조건) 값의 범위/패턴을 제한(MySQL 8.0.16+ 적용)
default 값 값 생략 시 기본값 자동 채움
auto_increment 정수 컬럼 값을 자동 증가
on delete … 참조 행 삭제 시 동작 지정(restrict/cascade/set null 등)
on update … 참조 키 변경 시 동작 지정(restrict/cascade/set null 등)

생성 및 삽입 예제

-- 스키마 : 학생 / id, name, age, email, created_date
CREATE TABLE students (
    id         int             PRIMARY KEY AUTO_INCREMENT, -- 기본키, 자동 증가 (반드시 존재)
    name     varchar(20)     NOT NULL, -- null 허용 X
    age     smallint        CHECK ( age > 0 and age <= 150 ), -- 범위 체크
    email    varchar(100)    UNIQUE,  -- 중복 허용 X
    created_date  datetime  DEFAULT CURRENT_TIMESTAMP
    -- 인설트할 때 빈값인 경우 기본값 현재날짜시간로 지정
);

INSERT into students(name,age,email) VALUE('짱구', 8, '짱구123@gmail.com');
INSERT into students VALUE(null, '철수', 12, '철수123@gmail.com', curdate());


SHOW TABLES;
desc students;

SELECT * FROM students;
DROP TABLE students;
starweb
@starweb :: starweb 님의 블로그

starweb 님의 블로그 입니다.

공감하셨다면 구독도 환영합니다!

목차