[SQL] NULL은 =가 아닌 IS로 사용, 서브쿼리는 WITH 사용

Junkyu_Kang·2024년 12월 24일

WITH이 뭔데?

문제를 풀다가, 혹은 쿼리문을 작성할 때 서브쿼리를 작성할 때가 많다.

그 때마다 테이블 작성시에 어찌해야하나 고민했는데
간단한 방법으로 WITH이 있다.

WITH은 SUBQUERY보단 일종의 임시 결과 집합으로 보는게 맞다. 이는 복잡한 쿼리를 단순화하고, 가독성을 높이는데 목적이 있다.

중복도 줄일 수 있고!

기본구조는 다음과 같다.

WITH CTE_Name AS (
    -- 여기에 서브쿼리를 적습니다.
    SELECT column1, column2
    FROM table_name
    WHERE condition
)
SELECT *
FROM CTE_Name
WHERE condition;

위 코드를 보면 WITH 뒤에 이름을 붙이고 조건절에 대한 쿼리문을 작성한다. 그리고 WITH의 장점은 여러개가 사용이 가능하다.

WITH DepartmentSalary AS (
    SELECT
        DepartmentID,
        MAX(Salary) AS MaxSalary
    FROM Employees
    GROUP BY DepartmentID
)
SELECT E.Name, E.Salary, E.DepartmentID
FROM Employees E
JOIN DepartmentSalary DS ON E.DepartmentID = DS.DepartmentID AND E.Salary = DS.MaxSalary;

이런 식으로 MAX 절을 사용하는데 있어 해당 결과를 기반으로 JOIN하여 비교문을 작성할 수 있다.

프로그래머스에 유사한 문제가 있다.

특정 세대의 대장균 찾기 문제다.

문제 설명
대장균들은 일정 주기로 분화하며, 분화를 시작한 개체를 부모 개체, 분화가 되어 나온 개체를 자식 개체라고 합니다.
다음은 실험실에서 배양한 대장균들의 정보를 담은 ECOLI_DATA 테이블입니다. ECOLI_DATA 테이블의 구조는 다음과 같으며, ID, PARENT_ID, SIZE_OF_COLONY, DIFFERENTIATION_DATE, GENOTYPE 은 각각 대장균 개체의 ID, 부모 개체의 ID, 개체의 크기, 분화되어 나온 날짜, 개체의 형질을 나타냅니다.

Column name	Type	Nullable
ID	INTEGER	FALSE
PARENT_ID	INTEGER	TRUE
SIZE_OF_COLONY	INTEGER	FALSE
DIFFERENTIATION_DATE	DATE	FALSE
GENOTYPE	INTEGER	FALSE
최초의 대장균 개체의 PARENT_ID 는 NULL 값입니다.

문제
3세대의 대장균의 ID(ID) 를 출력하는 SQL 문을 작성해주세요. 이때 결과는 대장균의 ID 에 대해 오름차순 정렬해주세요.

예시
예를 들어 ECOLI_DATA 테이블이 다음과 같다면

ID	PARENT_ID	SIZE_OF_COLONY	DIFFERENTIATION_DATE	GENOTYPE
1	NULL	10	2019/01/01	5
2	NULL	2	2019/01/01	3
3	1	100	2020/01/01	4
4	2	16	2020/01/01	4
5	2	17	2020/01/01	6
6	4	101	2021/01/01	22
7	3	101	2022/01/01	23
8	6	1	2022/01/01	27
PARENT ID 가 NULL 인 ID 1, ID 2가 1 세대이며 ID 1에서 분화된 ID 3, ID 2에서 분화된 ID 4, ID 5 가 2 세대입니다. ID 4 에서 분화된 ID 6, ID 3에서 분화된 ID 7 이 3 세대이며 ID 6에서 분화된 ID 8은 4 세대입니다.

따라서 결과를 ID 에 대해 오름차순 정렬하면 다음과 같아야 합니다.

ID
6
7

위와 같은 문제를 풀 때 FROM에서 SELECT 절을 파고 파고 들어갈 순 없지않는가
서브쿼리를 작성해보자

WITH FIRST_GEN 
    AS (SELECT ID FROM ECOLI_DATA WHERE PARENT_ID IS NULL),
    SECOND_GEN AS (SELECT B.ID, B.PARENT_ID FROM ECOLI_DATA B 
                    JOIN FIRST_GEN ON B.PARENT_ID = FIRST_GEN.ID)
    SELECT A.ID
    FROM ECOLI_DATA A JOIN SECOND_GEN ON A.PARENT_ID = SECOND_GEN.ID;

보이는 것 처럼 나는 FIRST_GEN과 SECOND_GEN 쿼리를 만들어 해당 내용을 서로 JOIN하고 답을 도출 할 수 있게 하였다.

처음 이해만 하면 어렵지않다.

SQL은 NULL을 조건에 넣을 때 IS를 써야한다.

가끔 문제 조건문에 NULL을 조건으로 두어 PARENT 중 ROOT 조건으로 명시한게 있는데 그럴 땐 루트노드를 PARENT = NULL로 찾으려고 했다.

이게 문제인게

SQL에서는 NULL을 =로 비교하면 항상 FALSE를 반환한다.

이는 SQL 표준에서 NULL은 '값이 없음'을 의미하고 데이터의 부재를 뜻하기 때문에 일반 비교연산자를 사용할 수 없다..

그래서 알 수 없는 값, 결정할 수 없는 값으로 처리되어 결과가 알 수 없음으로 처리되는 것 으로 다른 문법을 사용해야한다.

SELECT column_name FROM table_name WHERE column_name IS NULL;
SELECT column_name FROM table_name WHERE column_name IS NOT NULL;

IS와 IS NOT 간단하죠? 틀리지 맙시다 이런거로..

profile
강준규

0개의 댓글