DBMS

[DBMS] Join, Sub Query

shb 2022. 2. 23. 15:41

* Join 이란
하나의 테이블뿐이 아니라,  여러개의 테이블에서 정보를 가져와서 결과를 만들어 주는 기법을 Join 이라 합니다. 

관계형 데이터베이스(RDBMS) 의 가장 핵심 기술중 하나.
ANSI join 방식이 있고, DBMS마다 특화된 방식이 있긴 하나 하나 원리를 알면 다른 방법은 손쉽게 알수 있습니다.

 

* 카티션 곱 (Cartesian Product)

- 두개의 테이블을 JOIN 하게 되면, 각 테이블의 레코드들의 모든 조합이 출력된다.
- WHERE 나 ON 등으로 JOIN 조건이 주어지지 않으면 모든 카티션곱이 출력된다.

-> t_emp 는 14개, t_dept 는 4개  
그래서 t_emp X t_dept → 56개로 이루어진 카티션 곱이 나온다.


** 출력하는 필드에 별명을 붙일수 있듯이 테이블에도 별명을 붙여서 사용 가능하다.   

복수개의 테이블에서 같은 이름의 필드가 있는 경우는 반드시 서로 다른 테이블 별명을 사용하여 접근해야 한다.

* Join 의 종류


Equi Join (등가 Join)
일반적으로 많이 쓰이는 Join 이며, 양쪽 테이블에 같은 조건이 존재할 경우만 값을 가져오는 것

 

-- 예) #6101 
-- t_student 테이블과 t_department 테이블을 사용하여 학생이름, 1전공 학과번호, 1전공 학과 이름을 출력하세요

-- MySQL 구문.
SELECT s.name 학생이름, s.deptno1 학과번호, d.dname 학과이름
FROM t_student s, t_department d
WHERE s.deptno1 = d.deptno  -- JOIN 조건
;

-- ANSI SQL 구문
SELECT s.name 학생이름, s.deptno1 학과번호, d.dname 학과이름
FROM t_student s JOIN t_department d
		ON s.deptno1 = d.deptno  -- JOIN 조건
;


-- 제2전공은?
-- null 값은 JOIN 조건에 참여 안함.
SELECT s.name 학생이름, s.deptno2 제2전공번호, d.dname 학과이름
FROM t_student s, t_department d
WHERE s.deptno2 = d.deptno  -- JOIN 조건
-- 연습 #6102)
-- t_student 테이블, t_professor 테이블 을 join하여 
-- ‘학생이름’, ‘지도교수번호’, ‘지도교수이름’ 을 출력하세요

-- MySQL 구문
SELECT s.name "학생이름", s.profno "지도교수", p.name "지도교수이름" 
FROM t_student s, t_professor p 
WHERE s.profno = p.profno;

-- ANSI 구문
SELECT s.name "학생이름", s.profno "지도교수", p.name "지도교수이름"
FROM t_student s JOIN  t_professor p 
ON s.profno = p.profno;

-- #6103
-- MySQL
SELECT s.name "학생이름", d.dname "학과이름", p.name "교수이름"
FROM t_student s, t_department d, t_professor p
WHERE s.deptno1 = d.deptno AND s.profno = p.PROFNO;

-- ANSI
SELECT s.name "학생이름", d.dname "학과이름", p.name "교수이름"
FROM t_student s
	JOIN t_department d ON s.deptno1 = d.deptno -- 먼저 이 JOIN 을 실행후 나온 결과값과 
	JOIN t_professor p ON s.profno = p.PROFNO   -- 다음 JOIN 수행
- #6105
-- t_student - t_professor 테이블 join 하여 
-- 제1전공(deptno1) 이 101번인 학생들의 
-- 학생이름과 지도교수 이름을 출력하세요

-- MySQL
SELECT s.name "학생이름", p.name "교수이름"
FROM t_student s, t_professor p
WHERE 
	s.profno = p.PROFNO  -- JOIN 조건  카티션곱에서 걸러내기
	AND s.deptno1 = 101  -- 검색 조건.  카티션곱 만들기 전에 걸러내기
;

-- ANSI
SELECT s.name "학생이름", p.name "교수이름"
FROM t_student s 
	JOIN t_professor p ON s.profno = p.PROFNO AND s.deptno1 = 101 
-- WHERE s.deptno1 = 101
;

* Non-Equi Join (비등가 Join)
Equi Join은 서로 같은 조건 (=)을 가진 데이터를 Join 해서 가져오는 방법.
그러나 ‘크거나 작거나 하는 경우의 조건도 있을수 있습니다’  그러한 조건으로 join 을 수행하는 것을 Non-Equi Join 이라 합니다.

- 비등가 조인 (Non-Equi Join)

-- #6201) 
-- t_customer 테이블, t_gift 테이블을 join 하여  
-- 고객의 마일리지 포인트별로 받을수 있는 상품을 조회하여 
-- 고객의 '이름(c_name)'과 포인트(c_point) 상품명(g_name)을 출력하세요
-- BETWEEN ~ AND ~ 사용

SELECT * FROM t_customer; -- 일단 함 보자
SELECT * FROM t_gift;

-- MySQL
SELECT c.c_name "고객명", c.c_point "POINT", g.g_name "상품명"
FROM t_customer c, t_gift g
WHERE c.c_point BETWEEN g.g_start AND g.g_end;  

-- ANSI
SELECT c.c_name "고객명", c.c_point "POINT", g.g_name "상품명"
FROM t_customer c 
	JOIN t_gift g ON c.c_point BETWEEN g.g_start AND g.g_end;  
--  #6202) 연습 
-- 앞 예제에서 조회한 상품의 이름(gname)과 필요수량이 몇개인지 조회하세요
-- (그룹함수 동원되어야 한다)

-- MySQL
SELECT g.g_name "상품명", count(*) "필요수량"
FROM t_customer c, t_gift g
WHERE c.c_point BETWEEN g.g_start AND g.g_end
GROUP BY g.g_name;

-- ANSI
SELECT g.g_name "상품명", count(*) "필요수량"
FROM t_customer c JOIN t_gift g
ON c.c_point BETWEEN g.g_start AND g.g_end
GROUP BY g.g_name;
-- #6204
-- MySQL
SELECT c.c_name "고객명", c.c_point "POINT", g.g_name "상품명"
FROM t_customer c, t_gift g 
WHERE g.g_start <= c.c_point
AND g.g_name = '산악용자전거';

-- ANSI
SELECT c.c_name "고객명", c.c_point "POINT", g.g_name "상품명"
FROM t_customer c 
	JOIN t_gift g ON g.g_start <= c.c_point 
WHERE g.g_name = '산악용자전거';
-- #6203 연습
SELECT * FROM t_credit;
-- MySQL	
SELECT s.name "학생이름", e.total "점수", h.grade "학점"
FROM t_student s, t_exam01 e, t_credit h
WHERE s.studno = e.studno
	AND
	e.total BETWEEN h.min_point AND h.max_point;
	
-- ANSI 구문 
SELECT s.name "학생이름", e.total "점수", h.grade "학점"
FROM t_student s
	JOIN t_exam01 e ON s.studno = e.studno 
	JOIN t_credit h ON e.total 
	BETWEEN h.min_point AND h.max_point;
-- #6205 연습
SELECT * FROM t_post;

-- MySQL 
SELECT e.name "이름", year(now()) - year(BIRTHDAY) + 1 "현재나이",
	ifnull(e.post, '') "현재직급", p.post "예상직급"
FROM t_emp2 e, t_post p
WHERE year(now()) - year(BIRTHDAY) + 1 BETWEEN p.s_age AND p.e_age;

-- ANSI
SELECT e.name "이름", year(now()) - year(BIRTHDAY) + 1 "현재나이",
	ifnull(e.post, '') "현재직급", p.post "예상직급"
FROM t_emp2 e JOIN t_post p
ON year(now()) - year(BIRTHDAY) + 1 BETWEEN p.s_age AND p.e_age;
-- 지금까지의 JOIN 은 모두 INNER JOIN 이다.

SELECT s.name, p.name
FROM t_student s, t_professor p
WHERE s.profno = p.profno
;

SELECT s.name, p.name
FROM t_student s INNER JOIN t_professor p
ON s.profno = p.profno
;

-- 카티션곱 --> cross join 이라 함ㄴ
SELECT s.name, p.name
FROM t_student s, t_professor p
;

SELECT s.name, p.name
FROM t_student s CROSS JOIN t_professor p
;

OUTER Join (아우터 조인)
앞서본 equi join, non-equi join  Join 에 참여하는 모든 테이블에 데이터가 존재하는 경우에만 결과 값을 출력.

이런 Join 을 INNER Join (이너 조인) 이라 함.
OUTER Join 이란 한쪽 테이블에 데이터가 있고 한쪽 테이블에 없는 경우 데이터가 있는 쪽 테이블의 내용을 전부 출력
필연적으로 OUTER Join 은 DB 성능에 나쁜 영향 줌.

-- OUTER JOIN

-- #6206)
-- t_student 테이블과 t_professor 테이블 Join : 
-- 학생이름과 지도교수 이름을 출력하세요. 
-- 단! 지도교수가 결정되지 않은 학생의 명단도 함께 출력하세요


SELECT s.name, p.name
FROM t_student s LEFT OUTER JOIN t_professor p
ON s.profno = p.profno
;

-- #6207
-- 지도학생이 결정되지 않은 교수도 출력하기
SELECT s.name, p.name
FROM t_student s RIGHT OUTER JOIN t_professor p
ON s.profno = p.profno
;


-- #6208
-- MySQL 은 Full Outer 를 지원하지 않는다.
-- 대신 UNION 을 사용하여 full outer 를 구현

SELECT s.name, p.name
FROM t_student s LEFT JOIN t_professor p
ON s.profno = p.profno
UNION  -- 합집합
SELECT s.name, p.name
FROM t_student s RIGHT JOIN t_professor p
ON s.profno = p.profno
;

SELF Join 
만약 원하는 데이터가 하나의 테이블에 다 들어 있다면?
이럴때 사용하는 것이 SELF Join

-- SELF JOIN
-- '부서명' 과 그 부서의 '상위 부서명'을 출력
SELECT * FROM t_dept2;

-- MySQL
SELECT d1.dname 부서명, d2.dname 상위부서명
FROM t_dept2 d1, t_dept2 d2
WHERE d1.pdept = d2.dcode;

-- ANSI
SELECT d1.dname 부서명, d2.dname 상위부서명
FROM t_dept2 d1 JOIN t_dept2 d2
ON d1.pdept = d2.dcode;

-- #6210
-- 우선 자기보다 빨리 입사한 사람들의 입사일을 나열해보자

SELECT a.profno "교수번호", a.name "교수명", a.hiredate "입사일",
       b.name "빠른교수명", b.hiredate "빠른입사일"
FROM t_professor a LEFT OUTER JOIN t_professor b
ON b.hiredate < a.hiredate
ORDER BY 3
;

-- GROUP BY
SELECT a.profno "교수번호", a.name "교수명", a.hiredate "입사일",
       count(b.hiredate) "빠른사람"
FROM t_professor a LEFT OUTER JOIN t_professor b
ON b.hiredate < a.hiredate
GROUP BY a.profno
ORDER BY 4
;

* Sub Query 
“t_emp 테이블에서 scott 보다 급여를 많이 받는 사람은 누구인가요?”
일단 ‘scott 의 급여’ 를 알아야  ‘scott 보다 많이 받는 사람의 급여’를 조회할수 있다
즉!  2번의 쿼리를 작성해야 하는데,  서버에 2차례 쿼리 I/O 를 발생시키는 것은 성능 저하로 이어집니다.   

그래서 한번에 여러개의 쿼리를 처리 할수 있는 방법으로 Sub Query 가 제공됩니다.

 

Sub Query 주의사항

1. SubQuery 는 연산자 오른쪽에 위치해야 하며 반드시 괄호로 묶어야 한다.
2. 특별한 경우를 제외하고는 SubQuery절에는 Order By 가 올수 없습니다.
3. 단일행 SubQuery , 다중행 SubQuery 에 따라 연산자를 잘 선택해야 한다.

 

1. 단일행 서브쿼리
- SubQuery 결과가 한개 1행만 나오는 것.
- 단일행 Sub Query의  WHERE 에서 사용되는 연산자
- = , <> , !=, >, >= , <, <= 

-- #7103)연습
-- t_student, t_department 테이블 사용하여 
-- 이윤나 학생과 1전공(deptno1)이 동일한 학생들의 
-- 이름(name)과 1전공이름(dname)을 출력하세요

SELECT deptno1 FROM t_student WHERE name='이윤나';

SELECT s.name "학생이름", d.dname "제1전공"
FROM t_student s, t_department d
WHERE s.deptno1 = d.deptno
	AND s.deptno1 = (SELECT deptno1 FROM t_student WHERE name='이윤나');
-- #7104
SELECT p.name "교수명", 
	date_format(p.hiredate, '%Y-%m-%d') "입사일", 
	d.dname "학과명"
FROM t_professor p, t_department d
WHERE p.deptno = d.deptno
	AND p.hiredate > (SELECT hiredate FROM t_professor WHERE name = '송도권');

-- #7105
SELECT name "이름", weight "몸무게"
FROM t_student
WHERE weight > (SELECT avg(weight) FROM t_student WHERE deptno1=101);

-- #7106
SELECT name "이름", pay "급여", hiredate "입사일"
FROM t_professor
WHERE hiredate = (SELECT hiredate FROM t_professor WHERE name = '심슨')
	AND pay < (SELECT pay FROM t_professor WHERE name = '조인형');
-- #7107) 예제
-- t_emp2, t_dept2 테이블 : 
-- 근무지역 (t_dept2.area) 이 서울 지사인 모든 사원들의 
-- 사번(empno)과 이름(name), 부서번호(deptno)를 출력하세요
SELECT * FROM t_emp2;
SELECT * FROM t_dept2;

SELECT dcode FROM t_dept2 WHERE area = '서울지사';

SELECT empno, name, deptno
FROM t_emp2 
WHERE deptno IN (SELECT dcode FROM t_dept2 WHERE area = '서울지사');

다중행 Sub Query

Sub Query 결과가 2건(row) 이상 출력되는 것을 말합니다.
다중행 Sub Query 와 함께 사용하는 연산자

-- #7108
SELECT pay FROM t_emp2 WHERE post = '과장';

SELECT name "이름", post "직급", concat(format(pay, 0), '원') "연봉"
FROM t_emp2
WHERE pay >ANY (SELECT pay FROM t_emp2 WHERE post = '과장')
;

-- 아래와 같이 단일행 서브쿼리로도 가능.
SELECT name "이름", post "직급", concat(format(pay, 0), '원') "연봉"
FROM t_emp2
WHERE pay > (SELECT min(pay) FROM t_emp2 WHERE post = '과장')
;
-- #7109
SELECT name "이름", grade "학년", weight "몸무게"
FROM t_student
WHERE weight <ALL (SELECT weight FROM t_student WHERE grade = 4 );

-- #7110
SELECT d.dname "부서명", e.name "사원명", e.pay "연봉"
FROM t_emp2 e, t_dept2 d
WHERE e.deptno = d.dcode
AND e.pay < ALL ( SELECT AVG(pay) FROM t_emp2 GROUP BY deptno )
ORDER BY e.pay;

'DBMS' 카테고리의 다른 글

[DBMS] Constraint 제약조건  (0) 2022.02.24
[DBMS] View  (0) 2022.02.24
[DBMS] AggregateFunction  (0) 2022.02.23
[DBMS] Single-Row Function  (0) 2022.02.23
[DBMS] Transaction, Select  (0) 2022.02.22