diff --git "a/Document/2023/1105/seungho/1\354\236\245.md" "b/Document/2023/1105/seungho/1\354\236\245.md" new file mode 100644 index 0000000..e69de29 diff --git "a/Document/2023/1105/seungho/2\354\236\245.md" "b/Document/2023/1105/seungho/2\354\236\245.md" new file mode 100644 index 0000000..e69de29 diff --git a/Document/2023/1105/seungho/SQL_The Number of Employees Which Report to Each Employee.md.md b/Document/2023/1105/seungho/SQL_The Number of Employees Which Report to Each Employee.md.md new file mode 100644 index 0000000..0001771 --- /dev/null +++ b/Document/2023/1105/seungho/SQL_The Number of Employees Which Report to Each Employee.md.md @@ -0,0 +1,16 @@ +# Write your MySQL query statement below +```sql +SELECT E.employee_id, E.name, F.reports_count, F.average_age +FROM Employees as E, +(SELECT reports_to as employee_id, count(employee_id) as reports_count, ROUND(avg(age),0) as average_age +FROM Employees +GROUP BY reports_to) as F +WHERE E.employee_id = F.employee_id +ORDER BY E.employee_id +``` + +### 풀이 방법 +- Subquery +- reports_to로 그룹화함으로써, 지목을 받은 사람들(managers)의 정보를 담아냈다. + +- 그 다음, Employees 테이블 내의 직원들 중 매니저 id와 동일한 직원들을 골라 알맞게 적어주었다. \ No newline at end of file diff --git "a/Document/2023/1203/seungho/5\354\236\245_\353\260\230\353\263\265\353\254\270_\354\240\225\353\246\254.md" "b/Document/2023/1203/seungho/5\354\236\245_\353\260\230\353\263\265\353\254\270_\354\240\225\353\246\254.md" new file mode 100644 index 0000000..8fbb7db --- /dev/null +++ "b/Document/2023/1203/seungho/5\354\236\245_\353\260\230\353\263\265\353\254\270_\354\240\225\353\246\254.md" @@ -0,0 +1,193 @@ +# 5장 : 반복문 + +> 관계 조작은 관계 전체를 모두 조작의 대상으로 삼는다. 이러한 것의 목적은 반복을 제외하는 것이다. +> 최종 사용자의 생산성을 생각하면 이러한 조건을 만족해야 한다. 그래야만 응용 프로그래머의 생산성에도 기여할 수 있을 것이다. + +- SQL은 내부적으로는 반복문을 사용하지만, 구문 자체적으로는 반복문을 사용하지 않는다. + +## 반복계와 포장계 + +- 반복계 : 한 번에 한 레코드 (record at a time)를 반복하는 SQL +- 포장계 : 여러 행을 한꺼번에 처리하는 SQL + +### 반복계의 단점 + +1. 성능 + - 레코드 수가 적다면 반복계가 포장계에 비해 빠를지라도, 많을 경우엔 포장계가 확실히 좋다. + - SQL 실행의 오버헤드 + - 특히 SQL 구문 파싱 → 실행계획 설계 과정에서 많은 오버헤드가 생긴다. + - 구문 파싱 : 데이터베이스가 SQL을 받을 때마다 실행하고, DBMS에 따라 최대 0.1초~1초 걸림. + - SQL문을 반복적으로 실행하는 반복계에선 매 반복 + - 병렬 분산이 힘들다. + - 반복 1회마다의 처리가 단순한 반복계의 특징 상 병렬 분산 시 효율이 낮다. + - 데이터베이스의 진화로 인한 혜택을 받을 수 없다. + - Software Vendor + - 옵티마이저의 발전 → 효율적인 실행계획 / 데이터에 고속으로 접근할 수 있는 아키텍처 발전 + - Hardware Vendor + - HDD → SSD : 더 좋은 I/O 성능 + → 하지만 이런 발전들은 “복잡한 SQL 구문을 빠르게 해준다.” + 반복계에서 사용되는 단순한 SQL 구문들은 이런 혜택을 받지 못한다. +2. 해결책이 있을까? + - 반복계를 포장계로 다시 작성 : 이론은 좋으나 실전에서 사용하기 힘들다. (어떻게 빌딩을 옮겨요..) + - 각각의 SQL을 빠르게 수정 : 그러기엔 반복계에서 사용하는 SQL 구문이 너무 단순하다..(수정할 게 X) + - 다중화 처리 + - 리소스 여유 + 처리를 나눌 수 있는 키가 명확하게 정해져 있다면 ⇒ 다중화로 성능 향상 + - 질문 : 위에서 병렬 분산이 힘들다고 했는데, 이게 해결책이 되나? 전제조건이 위에서 말한 병렬 분산의 전제와 정반대라서 그런가? + +### 반복계의 장점 + +> Base : 반복계의 SQL 구문이 단순해서 생기는 장점들이다. + +1. 실행 계획의 안정성 + - 실행 계획이 단순하다 == 실행 계획에 변동 위험이 거의 없다. + - 특히나 결합을 사용하지 않기 때문에 더더욱 안정성 있다. +2. 예상 처리 시간의 정밀도 + - 성능이 안정적 → 예상 처리시간을 정밀하게 예측할 수 있다. + - <처리 시간> = <한 번의 실행 시간> \* <실행 횟수> +3. 트랜잭션 제어가 편리 + - 각 반복마다 커밋을 했다고 하면, 중간에 오류가 발생하더라도 마지막 커밋시점부터 재개할 수 있다. + - 포장계에서는 처음부터 다시 해야한다. + +# SQL에서 반복을 표현하는 방법 + +### 1. CASE 함수와 윈도우 함수 + +```sql +INSERT INTO Sales2 +SELECT company, year, sale, + CASE SIGN(sale - MAX(sale) + OVER (PARTITION BY company + ORDER BY year + ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) ) + WHEN 0 THEN '=' + WHEN 1 THEN '+' +``` + +- SIGN 함수 → 더 많은 윈도우 함수 사용을 하나의 상수값 리턴으로 대체. +- ROWS BETWEEN → 직전 레코드를 뽑을 수 있게끔 레코드 제한. + +→ 이렇게 CASE문 속에 윈도우 함수를 사용함으로써 반복문을 대체할 수 있다. + +### 2. 상관 서브쿼리 + +> 서브쿼리 내부에서 외부 쿼리와의 결합 조건을 사용하는 기술 + +```sql +INSERT INTO Sales2 +SELECT company, year, sale, + (SELCT company FROM Sales2 + WHERE S1.company = S2.company + AND year=(SELECT MAX(year) FROM Sales S3 + WHERE S1.company = S3.company AND S1.year > S3.year) + ) AS pre_company, + (SELECT sale FROM Sales S2 + WHERE S1.company = S2.company + AND year=(SELECT MAX(year) FROM Sales S3 + WHERE S1.company = S3.company AND S1.year > S3.year) + ) AS pre_sale +FROM Sales S1; +``` + +→ 2번째, 3번째 데이터 구하는 것이 어렵고, 실행 계획도 굉장히 복잡해지므로 비추. + +## 최대 반복 횟수가 정해진 경우 + +- 문제 + - 우편번호 ‘4130033’에 최대한 비슷한 레이블 찾기 + | pcode(우편번호) | district_name(지역 이름) | + | --------------- | ------------------------ | + | 4130001 | 이즈미 | + | 4130002 | 이즈산 | + | 4130103 | 아지로 | + | 4130041 | 아오바초 | + | 4103213 | 아오바네 | + | 4380824 | 아카 | + +```sql +# 기본적인 CASE 함수 풀이 +SELECT pcode, district_name FROM PostalCode +WHERE CASE WHEN pcode = '4130033' THEN 0 + WHEN pcode = '413003%' THEN 1 + WHEN pcode = '41300%' THEN 2 + WHEN pcode = '4130%' THEN 3 + WHEN pcode = '413%' THEN 4 + WHEN pcode = '41%' THEN 5 + WHEN pcode = '4%' THEN 6 + ELSE NULL END = + (SELECT MIN(CASE WHEN pcode = '4130033' THEN 0 + WHEN pcode LIKE '413003%' THEN 1 + WHEN pcode LIKE '41300%' THEN 2 + WHEN pcode LIKE '4130%' THEN 3 + WHEN pcode LIKE '413%' THEN 4 + WHEN pcode LIKE '41%' THEN 5 + WHEN pcode LIKE '4%' THEN 6 + ELSE NULL END) + FROM PostalCode); + +``` + +- 반복을 지양하고, CASE를 이용한 포장계 방법 +- 보다시피 테이블에 접근이 2회 발생한다. + - 해결책 : 윈도우 함수 사용 + +```sql +# 윈도우 함수 사용한 CASE문 +SELECT pcode, district_name +FROM (SELECT pcode, district_name, + CASE WHEN pcode = '4130033' THEN 0 + WHEN pcode LIKE '413003%' THEN 1 + WHEN pcode LIKE '41300%' THEN 2 + WHEN pcode LIKE '4130%' THEN 3 + WHEN pcode LIKE '413%' THEN 4 + WHEN pcode LIKE '41%' THEN 5 + WHEN pcode LIKE '4%' THEN 6 + ELSE NULL END AS hit_code, + MIN(CASE WHEN pcode = '4130033' THEN 0 + WHEN pcode LIKE '413003%' THEN 1 + WHEN pcode LIKE '41300%' THEN 2 + WHEN pcode LIKE '4130%' THEN 3 + WHEN pcode LIKE '413%' THEN 4 + WHEN pcode LIKE '41%' THEN 5 + WHEN pcode LIKE '4%' THEN 6 + ELSE NULL END) + OVER(ORDER BY CASE WHEN pcode = '4130033' THEN 0 + WHEN pcode LIKE '413003%' THEN 1 + WHEN pcode LIKE '41300%' THEN 2 + WHEN pcode LIKE '4130%' THEN 3 + WHEN pcode LIKE '413%' THEN 4 + WHEN pcode LIKE '41%' THEN 5 + WHEN pcode LIKE '4%' THEN 6 + ELSE NULL END) AS min_code + FROM PostalCode) Foo +WHERE hit_code = min_code; +``` + +- 윈도우 함수를 사용하며 정렬이 추가 사용되기 때문에, 테이블 크기가 크다면 오히려 나쁘다. + +## 최대 반복 횟수가 정해지지 않은 경우 + +→ 자료구조를 이용해서 해결해보자. + +### 인접 리스트 모델 + +> 해당 레이블에 연관된 레이블의 키값을 함께 저장하는 방식. 추후에 체이닝을 통해 원하는 레이블까지 찾아갈 수 있다. + +→ 보통 재귀 공통 테이블 식(Recursion common table expression)을 통해 해결한다. + + + +### 중첩 집합 모델 + +> 각 레코드의 데이터를 집합으로 보고, 계층 구조를 집합의 중첩 관계로 나타내어 조회 + +- 재귀 연산을 사용하지 않는다. 원의 좌측위치, 우측위치를 저장한 다음, 해당 원(집합)의 포함관계를 통해 찾아내는 모델. + +### 단점 + +![Untitled](./Untitled.png) diff --git "a/Document/2023/1203/seungho/6\354\236\245_\352\262\260\355\225\251_\354\240\225\353\246\254.md" "b/Document/2023/1203/seungho/6\354\236\245_\352\262\260\355\225\251_\354\240\225\353\246\254.md" new file mode 100644 index 0000000..c38395c --- /dev/null +++ "b/Document/2023/1203/seungho/6\354\236\245_\352\262\260\355\225\251_\354\240\225\353\246\254.md" @@ -0,0 +1,221 @@ +# 6장 : 결합 + +# 결합의 종류 + +> 크로스 결합, 내부 결합, 외부 결합, 자기 결합, 등가/비등가 결합, 자연 결합 +> + +## 기능적 관점으로의 결합 분류 + +1. 크로스 결합 (=데카르트 곱) + + n개 레코드의 A 테이블과 m개 레코드의 B 테이블을 결합할 때, 모든 경우의 수(n*m)를 결합. + + - 모든 경우의 수가 필요한 경우가 드물고, 비용이 많이 들기 때문에 실무에선 거의 사용하지 않는다. + - 개발자의 실수로부터 사용되는 경우를 많이 볼 수 있다. + - `SELECT * FROM Employees, Departments;` + - 결합 조건이 없기 때문에 크로스 결합으로 계산된다. + + ```sql + SELECT * FROM Employees + CROSS JOIN Departments; + ``` + +2. 내부 결합 (Inner Join) + - ‘내부’의 뜻 : ‘크로스 결합의 부분집합’ + - 결합 키가 양쪽 테이블 모두에 존재하는 레코드들만 추출 + + ```sql + SELECT E.emp_id, E.emp_name, E.dept_id, D.dept_name + FROM Employees E INNER JOIN Departmnts D + ON E.dept_id = D.dept_id; + ``` + + - 상관 서브쿼리로 대체할 수도 있다. + + ```sql + SELECT E.emp_id, E.emp_name, E.dept_id, + (SELECT D.dept_name FROM Departments D + WHERE E.dept_id=D.dept_id) AS dept_name + FROM Employees E; + ``` + + - `dept_name` 테이블의 기본 키와 `Employee` 테이블의 기본 키가 같은 레이블들만 포함되게끔 WHERE문 설정 → INNER JOIN과 동일하다. +3. 외부 결합 (Outer Join) + - ‘외부’의 뜻 = ‘내부’가 아닌 = ‘크로스 결합의 부분집합’이 아닌. + - 그러나 때때로는 데카르트 곱의 부분집합이 되기도 한다. + - 마스터 테이블의 정보를 모두 보존하고자 NULL을 생성한다는 것이 핵심적인 차이. + - 종류 + - 왼쪽 외부결합 (LEFT OUTER JOIN) + + ```sql + SELECT E.emp_id, E.emp_name, E.dept_id, D.dept_name + FROM Departments D LEFT OUTER JOIN Employees E + ON D.dept_id = E.dept_id; + ``` + + - 오른쪽 외부결합 (RIGHT OUTER JOIN) + + ```sql + SELECT E.emp_id, E.emp_name, E.dept_id, D.dept_name + FROM Employees E RIGHT OUTER JOIN Departments D + ON D.dept_id = E.dept_id; + ``` + + - 왼쪽, 오른쪽은 사실 똑같다. 마스터가 되는 테이블을 왼쪽에 적느냐, 오른쪽의 적느냐의 차이. + - 마스터 테이블 쪽에만 존재하는 키가 있을 때는 해당 키를 제거하지 않고 결과에 보존. + - 키를 모두 가진 레이아웃의 리포트를 만들 때 자주 사용. + - 완전 외부결합 (FULL OUTER JOIN) + - Mysql에선 지원하지 않는다. + - 필요하다면 `LEFT UNION RIGHT`으로 우회할 수 있다. + - LEFT OUTER JOIN + RIGHT OUTER JOIN이라고 생각하면 된다. + +### 자기 결합 (Self Join) + +- 자기 자신과 결합하는 연산. +- 기능적 분류에는 들지 못한다. (생성 결과가 기준이 아닌, 무엇을 대상으로 연산하는지에 대한 기준) +- 물리 레벨에서 보면 같은 테이블과 결합, 논리 레벨에서 보면 서로 다른 두 테이블 결합. + +```sql +SELECT D1.digit + (D2.digit * 10) AS seq +FROM Digits D1 CROSS JOIN Digits D2; +``` + +# 결합 알고리즘과 성능 + +> Optimizer → 데이터 크기나 결합 키의 분산이라는 요인에 의존하여 알고리즘 선택 +> + +## Optimizer가 선택 가능한 결합 알고리즘 + +1. Nested Loops +2. Hash +3. Sort Merge + +## 1. Nested Loops + +중첩 반복을 사용하는 알고리즘 (마치 2중 반복문) + +![Untitled](./Untitled6.png) + +1. 결합 대상 테이블 (Driving table)에서 레코드를 하나씩 반복 스캔 +2. Driving Table의 레코드 하나마다 내부 테이블(Inner Table / Driven Table)의 레코드를 하나씩 스캔 후 결합 조건에 맞으면 리턴 +3. Driving Table의 모든 레코드에 반복 + +→ Driving Table’s Record: m , Drivne Tables’s Record: n ⇒ m*n + +### 장점 + +1. 한 번의 단계에서 처리하는 레코드 수가 적으므로 Hash, Sort Merge에 비해 메모리소비 적다. +2. 모든 DBMS에서 지원 +3. 구동 테이블이 작을수록 Nested Loops의 성능이 좋아진다. + +### 구동 테이블의 중요성 + +> 구동 테이블이 작을수록 Nested Loops의 성능이 좋아진다. +> +- 조건 + 1. 내부 테이블의 결합 키 필드에 인덱스가 존재할 때 좋다 + 2. 내부 테이블의 결합 키가 내부 테이블에 대해 유일할수록 좋다 + +성능 = (# of Records in Table A) * 2(1 Driving Table Record + 1 Indexed Driven Table Record) + +### 단점 + +1. 결합 키로 내부 테이블에 접근할 때 히트되는 레코드가 많으면 성능이 떨어진다. +2. 결국엔 반복문이다. 내부 테이블의 절대적인 레코드 양이 많다면 지연이 발생한다. + - 해결책 + 1. 구동 테이블로 큰 테이블을 선택해보자. + (전제 : 현재 내부 테이블의 결합 키가 유일하지 않을 때) + 2. 해시 알고리즘으로 구현해보자. + +## 2. Hash + +### 작동 과정 + +1. 작은 테이블을 스캔하고, 결합 키에 해시 함수를 적용해서 해시값으로 변환한다. +2. 다른 테이블(상대적으로 큰 테이블)을 스캔하고, 결합 키가 해시값에 존재하는지 확인 + +### 왜 작은 테이블에 해시값을 걸까? + +1장 → 해시 테이블은 DBMS의 워킹 메모리에 저장된다. 따라서 워킹 메모리에 조금이라도 작은 것이 저장되는게 효율적이다. + +### 특징 + +1. 결합 테이블로부터 해시 테이블을 만들어서 활용하므로, Nested Loops에 비해 메모리를 크게 소모한다. (먼저 스캔한 테이블만큼의 메모리를 하나 더 만드는 셈) +2. 메모리가 부족하면 저장소를 사용 → 지연 +3. 출력되는 해시값은 입력값의 순서를 알지 못한다. 따라서 동치 결합에만 사용할 수 있다. + +### Hash 사용하기 좋은 경우 + +1. Nested Loops에서 적절한 구동 테이블이 존재하지 않는 경우. 즉 두 테이블 간의 레이블 수가 크게 차이나지 않는 경우 +2. 두 테이블 간 레이블 수가 크게 차이난다 할지라도, 내부 테이블에서 히트되는 레코드 수가 너무 많은 경우 +3. Nested Loops의 내부 테이블에 인덱스가 존재하지 않는 경우 + +즉, Nested Loops가 별로면 대안으로서 Hash가 존재한다. + +### Hash의 단점 + +1. 메모리 소비량이 많기 때문에, 동시 실행성이 높은 처리를 할 때 메모리 부족에 조심해야 한다. +2. OLTP 처리를 할 때 hash 사용은 지양해야 한다. (For 지연) +3. Hash는 반드시 양쪽 테이블의 레코드를 Full Scan한다. 테이블의 규모가 굉장히 크다면 스캔 시간을 고려해야 할 것. + +## 3. Sort Merge + +### 작동 과정 + +1. 결합 대상 테이블 2개 모두를 각각 결합 키로 정렬한다. +2. (투 포인터 알고리즘처럼) 결합 대상 테이블들을 스캔하며, 일치하는 결합 키를 찾으면 결합한다. + +### 특징 + +1. 대상 테이블을 모두 정렬해야 한다. → 많은 메모리 소비 +2. Hash보다도 메모리 소비가 많다. (Hash는 해시값 처리를 위해 한 테이블만 소비, 얘는 두개 다 소비) + - 메모리 부족 → TEMP 탈락 → I/O 비용 늘어남 → 지연 발생 위험 +3. 동치 결합뿐만 아니라 부등호를 사용한 결합에도 사용 가능하다. (부정 조건(`<>`) 제외) +4. 테이블이 결합 키로 정렬되어 있다면 정렬 생략 가능. + - 전제 : SQL에서 테이블에 있는 레코드의 물리적인 위치를 알고 있을 때 +5. 테이블을 정렬하므로 한쪽 테이블을 모두 스캔한 시점에 결합 완료 (Merge Sort의 특징) + +## 의도하지 않은 크로스 결합 + +```jsx +SELECT A.col_a, B.col_b, C.col_c +FROM Table_A A + INNER JOIN Table_B B + ON A.col_a = B.col_b + INNER JOIN Table_C C + ON A.col_a = C.col_c; +``` + +![Untitled](./Untitled1.png) + +결합 과정에서 옵티마이저가 어떤 알고리즘을 선택하는가에 따라 결정되지만, 크게 2가지 방법이 있다. + +1. Nested Loops로 선택 + - 최선의 방법. GOOD +2. Cross Join을 선택 + - 비효율적인 성능 + - 옵티마이저가 크로스 결합을 선택하는 이유 (추측) + 1. 구현상의 이유 + 2. Table B와 Table C의 크기를 작다고 평가했을 수도..? + 3. 이번 쿼리 전에 이미 비슷한 테이블을 가지고 크로스결합을 선택했었을 가능성 + +### 의도하지 않은 크로스 결합을 회피하는 방법 + +> B와 C를 의도적으로 걸어주면 된다.. +> + +# 정리 + +1. 소규모 - 소규모 + - 뭘 사용해도 GOOD +2. 소규모 - 대규모 + - Nested Loops 먼저 해보되, 내부 테이블의 결합 대상 레코드가 많거나 애초에 아예 레코드가 많을때는 Hash 검토 +3. 대규모 - 대규모 + - Hash 사용 + - 정렬이 이미 좀 되어있다? Nested Loops 사용 + +Join 연산에서 실행계획의 변동이 가장 잘 발생할 수 있다. + +따라서, SQL 성능의 변동 위험을 줄이려면 되도록 결합을 피해야 한다. \ No newline at end of file diff --git a/Document/2023/1203/seungho/Untitled.png b/Document/2023/1203/seungho/Untitled.png new file mode 100644 index 0000000..b6ad2b7 Binary files /dev/null and b/Document/2023/1203/seungho/Untitled.png differ diff --git a/Document/2023/1203/seungho/Untitled1.png b/Document/2023/1203/seungho/Untitled1.png new file mode 100644 index 0000000..6cb6734 Binary files /dev/null and b/Document/2023/1203/seungho/Untitled1.png differ diff --git a/Document/2023/1203/seungho/Untitled6.png b/Document/2023/1203/seungho/Untitled6.png new file mode 100644 index 0000000..3a3f71d Binary files /dev/null and b/Document/2023/1203/seungho/Untitled6.png differ