diff --git "a/MYSQL \354\265\234\354\240\201\355\231\224 \353\214\200\354\203\201-\354\241\260\354\235\270,\354\204\234\353\270\214\354\277\274\353\246\254 \354\230\210\354\240\234.md" "b/MYSQL \354\265\234\354\240\201\355\231\224 \353\214\200\354\203\201-\354\241\260\354\235\270,\354\204\234\353\270\214\354\277\274\353\246\254 \354\230\210\354\240\234.md"
new file mode 100644
index 0000000..51c52a7
--- /dev/null
+++ "b/MYSQL \354\265\234\354\240\201\355\231\224 \353\214\200\354\203\201-\354\241\260\354\235\270,\354\204\234\353\270\214\354\277\274\353\246\254 \354\230\210\354\240\234.md"
@@ -0,0 +1,38 @@
+### 1. 200개 이상 팔린 상품명과 그 수량을 수량 기준 내림차순으로 보여주세요.
+
+```sql
+SELECT p.ProductID as '상품 아이디', p.ProductName as '상품이름', sum(od.Quantity) as '총수량'
+FROM Products as p
+JOIN OrderDetails as od on p.ProductID = od.ProductID
+GROUP BY od.ProductID HAVING `총수량` >= 200
+ORDER BY `총수량` desc;
+```
+
+
+
+### 2. 많이 주문한 순으로 고객 리스트(ID, 고객명)를 구해주세요. (고객별 구매한 물품 총 갯수)
+
+```sql
+SELECT ct.CustomerID as '고객아이디', ct.ContactName as '고객이름', sum(od.Quantity) as '주문량'
+FROM Customers as ct
+JOIN Orders as o on ct.CustomerID = o.CustomerID
+JOIN OrderDetails as od on o.OrderID = od.OrderID
+GROUP BY ct.CustomerID
+ORDER BY `주문량` desc;
+```
+
+
+
+### 3. 많은 돈을 지출한 순으로 고객 리스트를 구해주세요.
+
+```sql
+SELECT ct.CustomerID as '고객아이디', ct.ContactName as '고객이름', sum(od.Quantity) * pd.Price as '지출금액'
+FROM Customers as ct
+JOIN Orders as o on ct.CustomerID = o.CustomerID
+JOIN OrderDetails as od on o.OrderID = od.OrderID
+JOIN Products as pd on od.ProductID = pd.ProductID
+GROUP BY ct.CustomerID
+ORDER BY `지출금액` desc
+```
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index e2d8498..d77ab19 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,321 @@
# 🚀 조회 성능 개선하기
+## 생각해보기
+
+
+
## A. 쿼리 연습
-### * 실습환경 세팅
+### 실습환경 세팅
-```sh
+```
$ docker run -d -p 23306:3306 brainbackdoor/data-tuning:0.0.1
```
+
- [workbench](https://www.mysql.com/products/workbench/)를 설치한 후 localhost:23306 (ID : user, PW : password) 로 접속합니다.
+1. 쿼리 작성만으로 1s 이하로 반환한다.
+2. 인덱스 설정을 추가하여 50 ms 이하로 반환한다.
+- [x] 활동중인(Active) 부서의 현재 부서관리자 중 연봉 상위 5위안에 드는 사람들이 최근에 각 지역별로 언제 퇴실했는지 조회해보세요.(사원번호, 이름, 연봉, 직급명, 지역, 입출입구분, 입출입시간)
+- 급여 테이블의 사용여부 필드는 사용하지 않습니다. 현재 근무중인지 여부는 종료일자 필드로 판단해주세요.
-
+
-> 활동중인(Active) 부서의 현재 부서관리자 중 연봉 상위 5위안에 드는 사람들이 최근에 각 지역별로 언제 퇴실했는지 조회해보세요.
-(사원번호, 이름, 연봉, 직급명, 지역, 입출입구분, 입출입시간)
+### ERD 테이블 보기
+테이블을 하나하나 살펴보기 힘드네요, Mysql에서 제공하는 ERD를 통해 확인해봐요. [ERD 자동생성](https://blog.naver.com/PostView.nhn?blogId=ajdkfl6445&logNo=221540488900&categoryNo=0&parentCategoryNo=0&viewDate=¤tPage=1&postListTopCurrentPage=1&from=postView)
-
-
+
+생성된 DB의 ERD는 위와 같아요.
-## B. 인덱스 설계
+### 1. 쿼리 작성만으로 1s 이하로 반환한다.
-### * 실습환경 세팅
+**결과 쿼리**
-```sh
-$ docker run -d -p 13306:3306 brainbackdoor/data-subway:0.0.2
+```sql
+SELECT `높은_연봉의_사원`.사원번호, `높은_연봉의_사원`.이름, `높은_연봉의_사원`.연봉, `높은_연봉의_사원`.직급명, 사원출입기록.입출입시간, 사원출입기록.지역, 사원출입기록.입출입구분
+FROM (SELECT 사원.사원번호, 사원.이름, 급여.연봉, 직급.직급명
+ FROM 사원
+ JOIN 급여 ON 급여.사원번호 = 사원.사원번호
+ JOIN 직급 ON 직급.사원번호 = 급여.사원번호
+ JOIN 부서관리자 ON 부서관리자.사원번호 = 직급.사원번호
+ JOIN 부서 ON 부서.부서번호 = 부서관리자.부서번호
+ WHERE 급여.종료일자 = '9999-01-01' and 직급.종료일자 = '9999-01-01'
+ and 직급.직급명 = 'Manager' and 부서.비고 = 'active'
+ ORDER BY 급여.연봉 desc
+ LIMIT 0,5) as `높은_연봉의_사원`
+ JOIN 사원출입기록 ON 사원출입기록.사원번호 = `높은_연봉의_사원`.사원번호
+WHERE 사원출입기록.입출입구분 = 'O'
+ORDER BY `높은_연봉의_사원`.연봉 desc, 사원출입기록.입출입시간 desc;
```
-- [workbench](https://www.mysql.com/products/workbench/)를 설치한 후 localhost:13306 (ID : root, PW : masterpw) 로 접속합니다.
-
+**결과 이미지**
+
+
+
+
+
+실행계획 (EXPLAIN)
+
+
+
+**실행계획(WorkBench)**
+
+
+
+### 2. 인덱스 설정을 추가하여 50 ms 이하로 반환한다.
-### * 요구사항
+**인덱스 추가 명령어**
-- [ ] 주어진 데이터셋을 활용하여 아래 조회 결과를 100ms 이하로 반환
+```sql
+CREATE INDEX `idx_사원_입출입구분_사원` ON `tuning`.`사원출입기록` (사원번호,입출입구분);
+```
+
+하나의 인덱스를 추가했어요.
+
+**결과 이미지**
+
+
+
+
+
+**실행계획 (EXPLAIN)**
+
+
- - [ ] [Coding as a Hobby](https://insights.stackoverflow.com/survey/2018#developer-profile-_-coding-as-a-hobby) 와 같은 결과를 반환하세요.
+**실행계획(WorkBench)**
- - [ ] 각 프로그래머별로 해당하는 병원 이름을 반환하세요. (covid.id, hospital.name)
+
- - [ ] 프로그래밍이 취미인 학생 혹은 주니어(0-2년)들이 다닌 병원 이름을 반환하고 user.id 기준으로 정렬하세요. (covid.id, hospital.name, user.Hobby, user.DevType, user.YearsCoding)
+## B. 인덱스 설계
+
+### 실습환경 세팅
+
+```bash
+$ docker run -d -p 13306:3306 brainbackdoor/data-subway:0.0.2
+```
+
+- [workbench](https://www.mysql.com/products/workbench/)를 설치한 후 localhost:13306 (ID : root, PW : masterpw) 로 접속합니다.
- - [ ] 서울대병원에 다닌 20대 India 환자들을 병원에 머문 기간별로 집계하세요. (covid.Stay)
+### ERD 테이블 보기
- - [ ] 서울대병원에 다닌 30대 환자들을 운동 횟수별로 집계하세요. (user.Exercise)
+이것도 테이블을 하나하나 살펴보기 힘드네요, Mysql에서 제공하는 ERD를 통해 확인해봐요.
-
-
+
-## C. 프로젝트 요구사항
+💉
-### a. 페이징 쿼리를 적용
+### 요구사항
-### b. Replication 적용
+- [x] 주어진 데이터셋을 활용하여 아래 조회 결과를 100ms 이하로 반환
+ - [x] [Coding as a Hobby](https://insights.stackoverflow.com/survey/2018#developer-profile-_-coding-as-a-hobby) 와 같은 결과를 반환하세요.
+
+ programmer hobby 인덱싱
+
+ ### **쿼리**
+
+ ```sql
+ SELECT
+ SUM(CASE WHEN hobby='Yes' THEN percentage ELSE 0 END) as Yes,
+ SUM(CASE WHEN hobby='No' THEN percentage ELSE 0 END) as No
+ FROM (
+ SELECT hobby, ROUND(COUNT(hobby)*100/(SELECT COUNT(*) FROM programmer) ,1) as percentage FROM subway.programmer GROUP BY hobby ORDER BY null
+ ) tb_derived;
+ ```
+
+ 
+
+ 
+
+ **실행계획 (EXPLAIN)**
+
+ 
+
+ **실행계획 (Workbench)**
+
+ 
+
+ 쿼리가 확실히 줄어둔 것을 확인할 수 있어요.
+
+ ### 참고사항
+
+ 기존의 쿼리가 너무 마음에 들지않아. 위와 같이 쿼리를 변경했어요.
+ 하지만 좁혀지지 않는 통곡의 0.1초대의 벽이 느껴졌어요. (0.11~2초의 저주)
+
+ hobby의 카디널리티가 낮아(카디널리티 2, 선택도 0.5), (id, hobby) 복합유니크키를 유지했던 것이 문제였여요.
+ 쿼리에서 복합 unique키중 하나인 hobby를 Group by를 하려하니, Using temporary, 즉 내부에 임시 테이블이 추가되며 속도 저하가 일어난게 화근이었네요.
+ 인덱스가 아니기에 정렬됨을 보장하지 않아 생기는 문제였어요.
+
+ 쿼리 상으로, 또한 실행계획상으로도 더이상 줄일 수 없다고 판단이 되어. 기존에 유지하던 방식을 버리기로 했어요.
+
+ ```sql
+ DROP INDEX `id_hobby_unique` ON `subway`.`programmer`;
+ ```
+
+ 기존에 존재하던 UNIQUE 제약조건을 삭제하고,
+
+ ```sql
+ CREATE INDEX `idx_programmer_hobby` ON `subway`.`programmer` (hobby);
+ ```
+
+ hobby만 가지는 인덱스를 추가했어요.
+
+ - [x] 프로그래머별로 해당하는 병원 이름을 반환하세요. (covid.id, hospital.name)
+
+ ### 제약조건
+
+ covid id 컬럼 PK, UQ 제약조건
+
+ hospital id 컬럼 pk, UQ 제약조건,
+
+ programmer id 컬럼, pk, UQ 제약조건
+
+ ### **쿼리**
+
+ ```sql
+ SELECT covid.id as covid_id, hospital.name as hospital_name
+ FROM subway.hospital
+ JOIN covid ON covid.hospital_id = hospital.id
+ JOIN programmer ON programmer.id = covid.programmer_id;
+ ```
+
+ 
+
+ 
+
+ **실행계획 (EXPLAIN)**
+
+ 
+
+ **실행계획 (Workbench)**
+
+ 
+
+ - [x] 프로그래밍이 취미인 학생 혹은 주니어(0-2년)들이 다닌 병원 이름을 반환하고 user.id 기준으로 정렬하세요. (covid.id, hospital.name, user.Hobby, user.DevType, user.YearsCoding)
+
+ ### 제약조건
+
+ 없음
+
+ ### **쿼리**
+
+ ```sql
+ SELECT C.id, H.name, P.Hobby, P.Dev_Type, P.Years_Coding, P.student
+ FROM (SELECT id, Hobby, Dev_Type, Years_Coding, student FROM programmer
+ WHERE (hobby = 'Yes'
+ AND student <> 'NO'
+ AND student <> 'NA')
+ OR (years_coding = '0-2 years')) AS P
+ INNER JOIN covid AS C ON C.programmer_id = P.id
+ INNER JOIN hospital AS H ON H.id = C.hospital_id
+ ;
+ ```
+
+ 
+
+ 
+
+ **실행계획 (EXPLAIN)**
+
+ 
+
+ **실행계획 (Workbench)**
+
+ 
+
+ ### 참고사항
+
+ 전체 테이블에서 where 절을 거는 것이 마음에 들지 않았어요. FROM절에서 JOIN을 할때, 조건이 필요한 테이블은 조인 전 조건에 따라 분류해주면, 시간도 절약될 것이라 생각했어요.
+
+ covid에 programmer_id를 인덱싱하면, 실행계획읜 Query Cost가 줄어듭니다.
+
+ 
+
+ 하지만 실행시간에 있어서, 큰 차이점을 느끼지 못해 인덱싱을 하지 않았어요.
+ 이후 Query Cost가 중요하다면, programmer_id의 인덱싱을 고려해봐도 좋을 듯 합니다.
+
+ ### hostpital의 name컬럼이 unique한가?에 대한 고찰.
+
+ 병원이름이 과연 겹칠까? 라고 생각을 했는데, 어쩌면 겹칠 수 있다고 생각해요. 사람 이름도 동일이름이 많은데 병원 이름도 분명 겹칠거에요 (수많은 "김내과"들..)
+ hospital에 있던 name의 UNIQUE 속성을 제거했어요.
+
+ ```sql
+ DROP INDEX `name_UNIQUE` ON `subway`.`hospital`;
+ ```
+
+ 실행되는 쿼리도 차이가 없네요! ("마음대로 UNIQUE 속성을 정의하지 말자"를 배웠네요 ㅎㅎ)
+
+ 
+
+ - [x] 서울대병원에 다닌 20대 India 환자들을 병원에 머문 기간별로 집계하세요. (covid.Stay)
+
+ ### 제약조건
+
+ member id PK 제약조건
+
+ coivd (hospital_id, member_id) 인덱싱
+
+ ### **쿼리**
+
+ ```sql
+ SELECT stay, COUNT(stay)
+ FROM (SELECT id FROM subway.member WHERE age BETWEEN 20 AND 29) AS M
+ INNER JOIN covid AS C ON C.member_id = M.id
+ INNER JOIN (SELECT id, member_id FROM programmer WHERE country = 'India') AS P ON P.id = C.programmer_id
+ INNER JOIN (SELECT id, name FROM hospital WHERE name = '서울대병원') AS H ON H.id = C.hospital_id
+ GROUP BY stay;
+ ```
+
+ 
+
+ 
+
+ **실행계획 (EXPLAIN)**
+
+ 
+
+ **실행계획(Workbench)**
+
+ 
+
+ ### 참고 사항
+
+ covid 테이블의 hospital_id의 선택도(카디널리티)가 32로 나오고 있어요,
+ member_id는 95667로 나와서, 먼저 member_id로 인덱스를 잡았어요. (카디널리티가 높으면 좋다~ 라는 생각으로)
+ 하지만, 속도가 생각보다 좋게 나오지 않았어요.
+ 그다음으로, 복합 인덱스를 구성하며 (member_id, hospital_id)로 인덱스를 잡았는데 이또한 생각보다 늦은 속도를 보여주기에, (hospital_id, member_id)로 인덱스를 잡았습니다.
+ 복합 인덱스에서, 선행되는 테이블의 카디널리티가 너무 높으면, 그만큼 검색 비용이 더 들지 않을까 합니다 :)
+
+ - [x] 서울대병원에 다닌 30대 환자들을 운동 횟수별로 집계하세요. (user.Exercise)
+
+ ### 제약조건
+
+ covid hospital_id, member_id 인덱싱
+
+ ### **쿼리**
+
+ ```sql
+ SELECT P.exercise, COUNT(exercise)
+ FROM (SELECT id FROM subway.member WHERE age BETWEEN 30 AND 39) AS M
+ INNER JOIN covid AS C ON C.member_id = M.id
+ INNER JOIN programmer AS P ON P.id = C.programmer_id
+ INNER JOIN (SELECT id, name FROM hospital WHERE name = '서울대병원') AS H ON H.id = C.hospital_id
+ GROUP BY P.exercise;
+ ```
+
+ 
+
+ 
+
+ **실행계획 (EXPLAIN)**
+
+ 
+
+ **실행계획(Workbench)**
+
+ 
+
+ ### 참고사항
+
+ ORDER BY null을 통해, 실행계획 Extra의 filesort를 없앨 수는 있지만, 실행 속도의 차이가 크지 않았어요. 그룹화 된 exercise의 모수가 그렇게 크지 않아서 정렬을 하건, 하지 않던 차이가 없는듯 합니다.
+ GROUP BY가 기본적으로 정렬을 하기에, 다른 옵션을 추가하지 않았어요.
\ No newline at end of file
diff --git a/subway.mwb b/subway.mwb
new file mode 100644
index 0000000..2dedddb
Binary files /dev/null and b/subway.mwb differ
diff --git a/tuning-erd.mwb b/tuning-erd.mwb
new file mode 100644
index 0000000..2fa82d5
Binary files /dev/null and b/tuning-erd.mwb differ
diff --git "a/\354\277\274\353\246\254\354\227\260\354\212\2651.sql" "b/\354\277\274\353\246\254\354\227\260\354\212\2651.sql"
new file mode 100644
index 0000000..83b5d4f
--- /dev/null
+++ "b/\354\277\274\353\246\254\354\227\260\354\212\2651.sql"
@@ -0,0 +1,39 @@
+SELECT 사원.사원번호, 사원.이름, 급여.연봉, 직급.직급명
+FROM 사원
+JOIN 급여 ON 급여.사원번호 = 사원.사원번호
+JOIN 직급 ON 직급.사원번호 = 급여.사원번호
+JOIN 부서관리자 ON 부서관리자.사원번호 = 직급.사원번호
+JOIN 부서 ON 부서.부서번호 = 부서관리자.부서번호
+WHERE 급여.종료일자 = '9999-01-01' and 직급.종료일자 = '9999-01-01'
+ and 직급.직급명 ='Manager' and 부서.비고 = 'active'
+ORDER BY 급여.연봉 desc
+LIMIT 0,5;
+
+explain SELECT `높은_연봉의_사원`.사원번호, `높은_연봉의_사원`.이름, `높은_연봉의_사원`.연봉, `높은_연봉의_사원`.직급명, 사원출입기록.입출입시간, 사원출입기록.지역, 사원출입기록.입출입구분
+FROM (SELECT 사원.사원번호, 사원.이름, 급여.연봉, 직급.직급명
+ FROM 사원
+ JOIN 급여 ON 급여.사원번호 = 사원.사원번호
+ JOIN 직급 ON 직급.사원번호 = 급여.사원번호
+ JOIN 부서관리자 ON 부서관리자.사원번호 = 직급.사원번호
+ JOIN 부서 ON 부서.부서번호 = 부서관리자.부서번호
+ WHERE 급여.종료일자 = '9999-01-01' and 직급.종료일자 = '9999-01-01'
+ and 직급.직급명 = 'Manager' and 부서.비고 = 'active'
+ ORDER BY 급여.연봉 desc
+ LIMIT 0,5) as `높은_연봉의_사원`
+ JOIN 사원출입기록 ON 사원출입기록.사원번호 = `높은_연봉의_사원`.사원번호
+WHERE 사원출입기록.입출입구분 = 'O'
+ORDER BY `높은_연봉의_사원`.연봉 desc, 사원출입기록.입출입시간 desc;
+
+CREATE INDEX `idx_사원_입출입구분_사원` ON `tuning`.`사원출입기록` (사원번호,입출입구분);
+
+CREATE INDEX `idx_부서_비고_부서번호` ON `tuning`.`부서` (비고, 부서번호);
+
+CREATE INDEX `idx_부서관리자_사원번호_부서번호` ON `tuning`.`부서관리자` (사원번호, 부서번호);
+
+CREATE INDEX `idx_부서관리자_사원번호_부서번호` ON `tuning`.`부서관리자` (부서번호, 사원번호);
+
+DROP INDEX `idx_사원_입출입구분_사원` ON `tuning`.`사원출입기록`;
+
+DROP INDEX `idx_부서관리자_사원번호_부서번호` ON `tuning`.`부서관리자`;
+
+DROP INDEX `idx_부서_비고_부서번호` ON `tuning`.`부서`;
\ No newline at end of file
diff --git "a/\354\277\274\353\246\254\354\227\260\354\212\2652.sql" "b/\354\277\274\353\246\254\354\227\260\354\212\2652.sql"
new file mode 100644
index 0000000..1bacd70
--- /dev/null
+++ "b/\354\277\274\353\246\254\354\227\260\354\212\2652.sql"
@@ -0,0 +1,53 @@
+-- Coding as a Hobby 와 같은 결과를 반환하세요.
+CREATE INDEX `idx_programmer_hobby` ON `subway`.`programmer` (hobby);
+DROP INDEX `idx_programmer_hobby` ON `subway`.`programmer`;
+
+ALTER TABLE programmer ADD UNIQUE `id_hobby_unique` (id, hobby);
+DROP INDEX `id_hobby_unique` ON `subway`.`programmer`;
+
+SELECT
+ SUM(CASE WHEN hobby='Yes' THEN percentage ELSE 0 END) as Yes,
+ SUM(CASE WHEN hobby='No' THEN percentage ELSE 0 END) as No
+FROM (
+ SELECT hobby, ROUND(COUNT(hobby)*100/(SELECT COUNT(*) FROM programmer) ,1) as percentage FROM subway.programmer GROUP BY hobby ORDER BY null
+) tb_derived;
+
+SELECT * FROM subway.hospital;
+-- 프로그래머별로 해당하는 병원 이름을 반환하세요. (covid.id, hospital.name)
+SELECT programmer_id, hospital.name as hospital_name
+FROM subway.hospital
+JOIN covid ON covid.hospital_id = hospital.id
+AND covid.programmer_id IS NOT NULL;
+
+-- 프로그래밍이 취미인 학생 혹은 주니어(0-2년)들이 다닌 병원 이름을 반환하고 user.id 기준으로 정렬하세요. (covid.id, hospital.name, user.Hobby, user.DevType, user.YearsCoding)
+DROP INDEX `name_UNIQUE` ON `subway`.`hospital`;
+
+SELECT C.id, H.name, P.Hobby, P.Dev_Type, P.Years_Coding, P.student
+FROM (SELECT id, Hobby, Dev_Type, Years_Coding, student FROM programmer
+ WHERE (hobby = 'Yes'
+ AND student <> 'NO'
+ AND student <> 'NA')
+ OR (years_coding = '0-2 years')) AS P
+INNER JOIN covid AS C ON C.programmer_id = P.id
+INNER JOIN hospital AS H ON H.id = C.hospital_id
+;
+
+-- 서울대병원에 다닌 20대 India 환자들을 병원에 머문 기간별로 집계하세요. (covid.Stay)
+-- EXPLAIN
+SELECT stay, COUNT(stay)
+FROM (SELECT id FROM subway.member WHERE age BETWEEN 20 AND 29) AS M
+INNER JOIN covid AS C ON C.member_id = M.id
+INNER JOIN (SELECT id, member_id FROM programmer WHERE country = 'India') AS P ON P.id = C.programmer_id
+INNER JOIN (SELECT id, name FROM hospital WHERE name = '서울대병원') AS H ON H.id = C.hospital_id
+GROUP BY stay;
+
+SELECT patient_id, covid.Stay, city_code_patient
+FROM subway.covid;
+
+-- 서울대병원에 다닌 30대 환자들을 운동 횟수별로 집계하세요. (user.Exercise)
+SELECT P.exercise, COUNT(exercise)
+FROM (SELECT id FROM subway.member WHERE age BETWEEN 30 AND 39) AS M
+INNER JOIN covid AS C ON C.member_id = M.id
+INNER JOIN programmer AS P ON P.id = C.programmer_id
+INNER JOIN (SELECT id, name FROM hospital WHERE name = '서울대병원') AS H ON H.id = C.hospital_id
+GROUP BY P.exercise;