1. SQL

코딩하는감자·2022년 3월 16일
0

예제+과제를 풀며 sql은 어떤 것인지에 대해 감을 잡고자 하였다.
예제를 풀기에 앞서, 여러 table 사이의 관계를 시각적으로 정리해 놓으면 도움이 될 것 같아서 시도해 봤는데 생각보다 시간이 오래 걸려서 좋은 방법을 찾아 봐야 할 것 같다,,,,
사실 교육용 ppt에 관계도가 나와 있긴 했는데 항상 이런게 있으리란 보장이 없으니까 직접 만들어 봄..!
+정리 과정이 어찌됐든 일단 정리를 해 놓으니까 확실히 문제 풀 때 편했다

예제

  1. 총 고객 수는? 122
select count(distinct customerNumber) from customers;
  1. lastname이 Brown인 고객 수는? 3
select count(contactLastName) from customers 
where contactLastName = 'Brown';
  1. 핸드폰 번호가 4로 시작하는 고객 수는? 5
select count(phone) from customers where phone like '4%';
#count(customerNumber)을 쓰는 게 더 적절해보임
  1. 고객들이 거주하는 국가 리스트는?
select distinct country from customers;
  1. 세일즈 사원이 각각 담당하는 고객 수는?
select salesRepEmployeeNumber as 직원,
count(customerNumber) as 담당고객수 from customers
where salesRepEmployeeNumber is not null
group by salesRepEmployeeNumber;
  1. 7명 이상의 고객을 담당하는 세일즈 사원 리스트
select salesRepEmployeeNumber,count(customerNumber) from customers
where salesRepEmployeeNumber is not null
group by salesRepEmployeeNumber
having count(customerNumber) >= 7;
  1. 월별 고객 수는?
select extract(year from orderDate), extract(month from orderDate), count(customerNumber) from orders
group by 1, 2;
#근데 같은 고객이 한 달에 여러 번 구매를 할 수도 있지 않나??
그냥 월별 구매 수를 세는 건가?
만약 구매를 한 실제 고객수를 세는 거면 count(distinct customerNumber)라고 해야 함
  1. 월별 주문을 담당한 office 수
select extract(year from orderDate), extract(month from orderDate), count(officeCode) from orders
join customers on customers.customerNumber = orders.customerNumber
join employees on employees.employeeNumber = customers.salesRepEmployeeNumber
group by 1,2;
#left join과 결과는 같음.. 그 차이는??
  1. 2003년도 주문의 월별 주문 건수와 평균 주문 날짜~배송일자(주문에서 배송까지 얼마나 걸렸는지)
select extract(year from orderDate), extract(month from orderDate), 
count(orderNumber), avg(datediff(shippedDate,orderDate)) from orders
where extract(year from orderDate) = 2003
group by 1,2;
  1. 국가별 고객의 주문 건수와 평균 주문 날짜~배송일자
select country, count(orderNumber), avg(datediff(shippedDate,orderDate))
from orders
left join customers on orders.customerNumber = customers.customerNumber
group by country;
  1. 미국의 연, 월별 주문 건수와 평균 주문날짜~배송일자
select extract(year from orderDate), extract(month from orderDate),
count(orderNumber), avg(datediff(shippedDate, orderDate))
from orders
group by 1,2;
  1. 월매출
select extract(year from orderDate), extract(month from orderDate), sum(amount)
from orders
left join payments on orders.customerNumber = payments.customerNumber
group by 1,2;

#사실 payments 테이블의 paymentDate를 써도 된다....

#만약 같은 주문 건에 대해서
#orderDate와 paymentDate가 다르면??
#매출이 회계장부상 기록되는 revenue를 말하는 것일 듯...
#그럼 paymentDate가 아니라 orderDate를 쓰는게 맞지 않나??

select orders.customerNumber, orderNumber, orderDate, paymentDate from orders
left join payments on orders.customerNumber = payments.customerNumber
where orderDate != paymentDate;

#이걸로 확인해 보면, 같은 customer의 동일한 order여도 여러 번 나눠서 payment를 실행한 경우가 있다. 
#따라서, orderDate를 월 매출 산정의 기준으로 쓰는 것이 더 타당해 보임.
#근데 이건 내 뇌피셜이라는 점... 

#그런데 저 변수들에 대해 명확한 설명이 공식 제공되지 않아서 해석하기 나름인 듯!! 이렇게 해도 맞고 paymentDate기준으로 봐도 맞다 !!!! !!

#근데.... 분할결제를 1년 단위로도 하나.......?
다시 생각해보니 paymentDate가 좀더 적절해 보임
  1. 미국의 월매출
select extract(year from paymentDate), 
extract(month from paymentDate), 
sum(amount) from payments
left join customers on payments.customerNumber = customers.customerNumber
where country = 'USA'
group by 1,2
order by 1,2;
#group by를 숫자로 할 수 있는게 정말 편한 것 같다
  1. 국가별 월매출
select country, extract(year from paymentDate),
extract(month from paymentDate),
sum(amount) from payments
left join customers 
on payments.customerNumber = customers.customerNumber
group by 1,2,3
order by 1,2,3;
  1. 누적결제금액 100,000 이상인 고객들의 정보(name, 국가, phone)**
select customerName, country, phone from customers
where customerNumber in
(select customerNumber from payments
group by 1 
having sum(amount>=100000));
#이건 서브쿼리문에 sum(amount)를 추가하는 실수를 했는데,
서브쿼리 안에 컬럼을 두 개 넣는다는건
'이 순서쌍을 찾는다'는 의미인 듯
  1. 월별 주문건수와 매출
select extract(year from orderDate),
extract(month from orderDate),
count(extract(month from orderDate)),
sum(amount)
from orders
left join payments
on orders.customerNumber = payments.customerNumber
group by 1,2;
라고 했는데 예제 답과 너무 다름.. 어디가 잘못된 거지??
이건 나중에..

과제

  1. 국가별 office와 employee의 수
#처음 짠 코드
select country, count(offices.officeCode), count(employeeNumber) from offices
left join employees 
on offices.officeCode = employees.officeCode
group by 1;

#결과가 이상해서 국가, officeCode, 직원번호 조회해보니 역시나 이상했음
select country, employeeNumber, offices.officeCode from offices left join employees
on offices.officeCode = employees.officeCode;

#수정: officeCode 중복 고려해서 distinct 넣어줌
select country, count(distinct offices.officeCode), count(employeeNumber) from offices
left join employees 
on offices.officeCode = employees.officeCode
group by 1
order by 1;
  1. cutomerFirstName이 R로 시작하는 고객 리스트
#걍 공부용으로 customerNumber까지 포함되게 출력되도록 했다.. 그런데 자꾸 null이 살아있어서 난감했다. 
where절에서 and조건을 사용해 not null인 값만 표시되도록 했는데도 왜 null이 안 없어지지???????? 노 이해..

SELECT customerNumber in (select customerNumber from customers 
where customerNumber is not Null), customerName, contactFirstName 
FROM customers
where contactFirstName like 'R%';

이렇게 하니까 내가 원하는 customerNumber 정보가 표시가 안 됐다

SELECT customerNumber, customerName, contactFirstName 
FROM (select * from customers
where customerNumber is not null) customers
where contactFirstName like 'R%'
order by 1;

드디어 성공 ㅠ 근데 해놓고 나니 정말 별 거 아니었다 ㅋㅋ
정말 이해가 안 되는게 왜 is not null조건은
where절 안에 다중조건으로 들어가질 않는 거지??
  1. order상태가 'Cancelled' 또는 'On Hold'인 미국 고객의 주문 건수
On Hold인 것만 살펴봤다
select * from orders
where status = 'On Hold';

근데 여기도 Null 있다... 범인은

이번에도 식별코드(Number) 컬럼이었다... 킹받악

select orders.customerNumber, orderNumber, status, country from orders
left join customers
on orders.customerNumber = customers.customerNumber
where country = 'USA'
and status in ('Cancelled', 'On Hold');

왜 이번에는 null이 안 뜨지? 하여튼 조건에 맞는 고객 리스트를 뽑긴 함
이제 주문 건수를 세는 코드로 바꿔 보자

select count(orders.customerNumber) from orders
left join customers
on orders.customerNumber = customers.customerNumber
where country = 'USA'
and status in ('Cancelled', 'On Hold');
  1. 가장 많은 고객을 담당한 officeCode
select officeCode, count(distinct customerNumber) from employees
left join customers
on employees.employeeNumber = customers.salesRepEmployeeNumber
group by 1
order by 2 DESC;


select officeCode, count(distinct customerNumber) from customers
left join employees
on employees.employeeNumber = customers.salesRepEmployeeNumber
group by 1
order by 2 DESC;

null값 빼면 결과는 어쨌든 같긴 같다... 뭔 차이지??


select officeCode, customerNumber from customers
left join employees
on employees.employeeNumber = customers.salesRepEmployeeNumber;

select officeCode, customerNumber from employees
left join customers
on employees.employeeNumber = customers.salesRepEmployeeNumber;

이렇게 하면 officeCode가 null인건 아예 안 뜸

select officeCode from employees
where officeCode is null;

이걸 실행시켜 보면 조회되는 값이 나오지 않는데,
도대체 아까는 왜 officeCode가 null로 나왔지??????

select salesRepEmployeeNumber, employeeNumber from customers
left join employees
on employees.employeeNumber = customers.salesRepEmployeeNumber;

이유를 찾았다!
고객의 담당직원이 null값일 줄은 몰랐지;;

허무한 결말.. table을 제대로 살펴보지 않고 검색만 대충 하니까
이렇게 먼 길을 돌게 되는 것 같다
  1. 2004년 11월 가장 많은 금액을 결제한 고객의 정보(name,국가,phone?)
이번에는 전체 테이블부터 살펴보고 시작..

select payments.customerNumber, customerName, country, phone, paymentDate, amount from payments
left join customers
on payments.customerNumber = customers.customerNumber;

null값은 없다


select customerName, country, phone from customers
left join payments
on payments.customerNumber = customers.customerNumber
where amount in
(select max(결제금액) 
from (select sum(amount) as 결제금액 from payments
where extract(year from paymentDate)=2004
and extract(month from paymentDate) = 11
group by customerNumber) payment);

이게 ㄹㅇ 힘들었던게,
그냥 결제금액 순서로 쭉 나열해서 사람 눈으로 제일 첫번째 자료를 찾는 방식이 아니라
제일 많이 결제한 고객만 조회하려고 하니까
서브쿼리가 들어가야 해서 굉장히 헷갈렸다,,,,
라고 생각했는데 sql에도 그런 기능이 있었다
아무래도 계속 앉아서 달렸더니 뇌가 굳은 듯??
그냥 from절 다음에 limit N 해주면 됐다 ㅎㅎ;;;;;;
내 40분 ㅠ

검토:
select payments.customerNumber, customerName, country, phone, 
paymentDate, sum(amount) from payments
left join customers
on payments.customerNumber = customers.customerNumber
where extract(year from paymentDate)=2004
and extract(month from paymentDate) = 11
group by customerNumber
order by 6 DESC;

여기에 맨 끝에 LIMIT 1 붙여주니까 
앞에서 힘들게 짠 쿼리와 같은 결과가 나온다 ㅎㅎㅎㅎㅎㅎㅎ....
  1. 2005년 1월의 orderDate와 shippedDate 사이 기간의 최대, 최소값
select max(배송기간), min(배송기간) from 
(select dateDiff(shippedDate,orderDate) as 배송기간 from orders
where extract(year from orderDate) = 2005
and extract(month from orderDate) = 1) orders;

서브쿼리로 찾는 것도 힘들게 해봤으니까 괜히 여기도 적용해 보기,,,ㅎㅎㅎ
아 그런데 이 문제는 limit로는 풀기 힘들 듯?? 헛고생한건 아닌가
  1. 2004년 1년간 가장 많은 금액을 결제한 고객의 담당 employee 정보
select * from (select * from employees where employeeNumber is not null) employees
where employeeNumber = (select salesRepEmployeeNumber
 from (select payments.customerNumber, sum(amount), 
salesRepEmployeeNumber
from payments
left join customers
on payments.customerNumber = customers.customerNumber
where extract(year from paymentDate) = 2004
group by 1
order by 2 DESC
limit 1) employees);

어려웠다 ㅠㅠ 우선은
제일 많이 결제한 고객을 골라내는 데 그치지 않고
그 고객의 담당 직원 정보를 얻어내야 하는 게 낯설었다
거의 이것 때문에 제일 오래 걸린듯
그리고 그 다음 단계에서 서브쿼리문으로 필터링 하려고 했는데
한 컬럼만 별칭 지정하니까 에러 나더라?? 거참.. 공평해야 한다 이건가
그래서 아싸리 다 지정 안하고도 해보고 지정하고도 해봤는데 둘 다 정상작동
마지막으로 null값 지워주기,, 이거 정말 귀찮았다


select employeeNumber, lastName, firstName,
extension, email, officeCode, reportsTo, jobtitle, customers.customerNumber,
paymentDate, sum(amount) from employees
left join customers
on employees.employeeNumber = customers.salesRepEmployeeNumber
left join payments
on customers.customerNumber = payments.customerNumber
group by 9
order by 11 desc
limit 1;
이렇게도 할 수 있었다!
일단 필요한 정보를 담은 컬럼들을 싹 join 해준 다음에
그 테이블에서 정렬을 통해 원하는 값을 얻어내는 방식인데
위의 방식은 깔끔하게 직원정보만 나오는 반면
이렇게 하면 고객 정보까지 다 나와서 조금 지저분스
그렇지만 코드 작성할 때 쉽게 접근할 수 있어서 훨씬 편리했다

마지막으로 할 말이 있다면,,,
왜 from 뒤에 서브쿼리를 쓸 때는 꼭 그 뒤에 일반 테이블명을 넣어 줘야 하는지 이해가 안 간 채로 일단 에러가 안 뜨니까 막 실행했는데
알고 보니 from절에 서브쿼리가 오면 그걸 인라인 뷰(inline view)라고 하고
무조건 별칭을 지어 줘야 된다고 한다.
예를 들면
from ( 서브쿼리 ) as ~~
근데 내 코드에서나, 구글링한 결과들에서도 as는 빈번히 생략이 되었기 때문에
위의 코드들에 as를 넣어서 다시 실행해 본 결과
너무 잘 돌아간다....
어쩐지 컬럼들에 별칭을 붙일 때 자꾸 별칭 에러가 뜨더라니.....
분명 중복이 없는데...? 라고만 생각하고 그냥 별칭을 떼 버렸는데
역시 좀더 찾아보길 잘한듯

기타 참고자료
[링크텍스트]( 출처: https://inpa.tistory.com/entry/MYSQL-📚-서브쿼리-정리 [👨‍💻 Dev Scroll])

profile
데분 데분 데분

0개의 댓글