본문 바로가기
SQL

[프로그래머스] 없어진 기록 찾기 (Lv.3 - select 에 혹시 유실된 데이터를 보려고 하지 않았는지??)

by itstime0809 2024. 2. 24.
728x90

SQL

https://school.programmers.co.kr/learn/courses/30/lessons/59042

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

쿼리

select b.ANIMAL_ID, b.NAME from ANIMAL_INS a
right outer join ANIMAL_OUTS b on a.ANIMAL_ID = b.ANIMAL_ID
where a.ANIMAL_ID is null
order by a.ANIMAL_ID ASC;

# ===== 아래는 오답 ======
# 두 쿼리의 차이점은 select 문에 존재.

select a.ANIMAL_ID, a.NAME from ANIMAL_INS a
right outer join ANIMAL_OUTS b on a.ANIMAL_ID = b.ANIMAL_ID
where a.ANIMAL_ID is null
order by a.ANIMAL_ID ASC;

 

설명

 문제는 비교적 간단하다. 다만, 포스팅을 쓰기로 했던 이유는 select를 아무 테이블이나 잡고 쓰면 안 된다는 것을 쿼리를 공부하면서 얻었기 때문에 이에 대해 작성하려고 한다. 문제가 난해 하지도 않지만, 해당 문제를 풀기 위해서 이산수학 시간에 배웠던, 논리집합 연산을 기준으로 문제를 풀이했다. 기본적인 아이디어는 아래와 같았다.

 

 

두 집합이 있다고 하자. 그리고 그 두 집합을 A, B라고 하자.

 

A - 보호소에 들어온 기록

B - 입양을 보낸 기록

 

( 두 개의 테이블을 집합 기호로 생각해 본다는 것. )

 

 

 문제에서 원하는 요구 조건만 간단하게 보자면, 입양을 보낸 기록은 있지만, 보호소에 들어온 기록은 없다는 설정이다. 이 조건을 집합으로 생각해보자.

 

 먼저, 생각해 볼 수 있는 부분은 입양을 보낸 기록이 있다는 것은, B라는 집합에 데이터가 존재한다고 볼 수 있다. 그리고 보호소에 들어온 기록이 없다는 것은 A집합에 데이터가 없다는 것을 의미한다. 이제 잘 생각해 보면, 입양을 보낸 기록이 존재한다는 것을 조금 풀어서 생각해 본다면, 입양을 보내는 건 '동물'이다. 즉 동물이 동물 보호소에 있었기 때문에, 입양을 보낸 기록이 존재할 수 있다. 즉 동물이 동물 보호소에 있다면, 입양을 보낼 수 있다. 이걸 대우로써 접근해 보면 입양을 보낼 수 없다면, 동물이 동물 보호소에 없다는 것이다.

 

 기본 적인 아이디어는 여기에서 출발한다. 결국 동물이 동물 보호소에 있어야만 입양을 보낼 수 있다는 것이다. 현재 문제에서는 천재지변으로 인해서 데이터가 유실되었다는 설정이다. 즉 천재지변이 일어나지 않았다면 유실되지 말아야 할 데이터는 결국, 동물이 동물 보호소에 들어온 기록이 사라지지 않았다는 것을 의미하며, 동물이 동물 보호소에 들어오고 나서 동물 보호소에 입양을 보낼 수 있는 동물이 될 수 있는 것이다.

 

 다시 돌아와서 이를 집합으로 접근해 보자. 우선 천재지변이 일어나지 않았을 때를 가정해 보자. 그렇다면 두 데이터는 유실되지 않은 상태로 보존된다. 그러면 A교 B (교 = 교집합) 두 데이터가 모두 존재하는 상태가 된다. 즉 보호소에 들어온 기록도 존재하고, 입양을 보낸 기록도 존재하는 상태가 바로 두 집합의 교집합 상태다. 하지만 문제에서 원하는 건 교집합 상태가 아니다. 두 데이터가 모두 존재하는 상황을 원한다는 게 아니라는 것이다. 말로 먼저 풀어보면 B집합에는 있지만, A에는 없는 상태 이걸 집합으로 표현하면 B교 A^c(여집합) 상태를 의미한다.

 

 다시 말해, B-A인 구간을 의미한다고 볼 수 있다. 이게 문제에서 의도하는 부분이다. B-A구간을 생각해 보면 알겠지만, 두 집합의 교집합은 포함하지 않는다. 왜냐하면 위에서 언급했듯, 두 데이터가 모두 유실되지 않은 상태기 때문이다. 즉 정상적인 상태는 필요가 없다는 것이다. (적어도 이 문제에서는)

 

 따라서 right outer join을 사용할 수 있다는 것을 알 수 있다. 그렇다고 해서 left outer join을 사용하지 못할 이유도 없다. 집합의 순서를 바꿔주게 되면 left outer join도 사용할 수 있기에 left outer join을 사용한 풀이도 정답이 될 수 있는 것이다.

 

select

 문제는 그렇게 join을 했다고 하자. right 또는 left 둘 중 어떤 join을 썼든지, 고민해 볼 부분은 이 select에 있고, 이번 포스팅 목적이다.

먼저 select를 통해서, 어떤 칼럼을 보여줄지가 결정이 된다. 만약 내가 동물의 ID를 보고 싶다면 컬럼 혹은 열의 이름을 적으면 되고, 한 개 이상 나열하게 되면 그 나열된 열의 수만큼 테이블 형식으로 보이게 된다. 

 

 join으로 가공한 테이블을 기준으로 select가 실행되기 때문에, 현재 join이 된 순간, left 또는 right를 했을 때 만약 left로 join을 했다면 왼쪽 데이터는 모두 가지고 오고, 오른쪽 데이터가 매칭 안 되는 것은 null로 처리가 될 것이고, right를 기준으로 join을 했다면 오른쪽 데이터는 모두 가지고오고, 왼쪽 데이터가 매칭이 안되는 것은 null로 표현이 될 것이다. 

 

 right를 기준으로 보자. ( left는 똑같이 응용하면 되므로 ) 그럼 왼쪽 데이터는 null로 처리되어 테이블이 구성이 될 것이다. 즉 다시 말해 right 데이터들은 입양한 기록이 있는 B 데이터가 될 것이고 왼쪽 데이터가 보호소에 들어온 데이터가 될 것이다. 그럼 코드에 작성했듯 오답 쿼리를 보면 select에 a에 있는 id, name을 보여주고 싶어 한다. 

 

 근데 잘 생각해 보자. where 조건문으로 왼쪽 데이터인 즉 보호소에 들어온 기록이 없는 id = null 인 데이터를 필터링하고 있다. 그렇다는 것은 a b 테이블이 right outer join으로 인해 테이블이 합쳐졌고, 그로 인해 a에는 b와 매칭이 안 되는 데이터가 존재하기 때문에 where 조건절이 가능하다. 즉 보호소에 들어온 데이터가 없으면 null로 표시될 거니까 join 기준이 id 기준이기 때문에 id가 null이 라면 보호소에 들어온 기록이 없다는 게 된다. 그러므로 select로 a 테이블의 id, name을 출력하게 된다면 그렇게 필터링된 것들 null인 것들을 출력하겠다는 의미다. 없는 데이터를 출력하겠다고 하였으니, 결과도 아무 값도 나오지 않는 것이다. 반면 b데이터를 기준으로 옳은 쿼리를 작성한다면 정상적으로 결과를 보여주게 되는데 그 이유 또한, a가 null일 때는 b에는 데이터가 존재하기 때문에 b데이터에 있는 칼럼을 보여줄때 null이 아닌 데이터를 보여주므로, 데이터가 정상적으로 보여질 수 있는 것이다.

 

 결론, select를 사용해서 컬럼을 보여주고자 할 때, 무슨 기준으로 무슨 테이블의 컬럼을 보여줄 것인지를 명확하게 알고 작성해야 된다는 것이 오늘의 결론이다.