빈 키끼리도 합쳐질까: SQL NULL과 pandas merge의 차이

이 카테고리 글 목록

전체글 보기

두 표의 빈 키는 서로 연결되지 않을 것이라고 생각하기 쉽다. 그러나 SQL의 등호 결합과 pandas merge는 결측 키의 매칭 계약이 다르다. 같은 자료를 옮겼는데 행 수가 바뀌었다면, 빈 값을 먼저 채우기보다 실제 키 자료형과 결합 조건에서 결측값이 어떻게 취급되는지 확인해야 한다.

이 글은 추출·정리한 표를 SQL과 Python 사이에서 결합하는 담당자를 위한 설명이다. 아래 자료는 합성이다. Python 3.12.14·pandas 3.0.1·SQLite 3.53.1의 메모리 DB에서 결과를 확인했고 PostgreSQL 서버나 실제 고객·운영 DB는 실행하지 않았다.

같은 네 행과 세 행을 연결했는데 결과가 다르다

왼쪽 키는 ['A', None, None, ''], 오른쪽은 ['A', None, '']로 만들었다. pandas에서는 양쪽 키 Series의 dtype을 object로 명시했고, SQLite에서는 TEXT 열에 Python None을 NULL로 넣었다. 빈 문자열은 결측값으로 바꾸지 않았다. 각 입력 행은 별개의 ID를 갖는다.

이 자료의 결합 방법 결과 무엇이 연결됐나
pandas inner merge 4행 A 한 쌍, 결측 두 쌍, 빈 문자열 한 쌍
SQLite ON l.k = r.k 2행 A와 빈 문자열만 각각 한 쌍
SQLite IS NOT DISTINCT FROM 4행 이 예에서는 NULL끼리도 연결
양쪽 결측 키를 제외한 pandas inner 2행 A와 빈 문자열만 연결

pandas merge 공식 문서의 경고는 양쪽 키의 null 값이 서로 매칭되며 보통의 SQL 결합과 다르다고 밝힌다. 이 예에서는 왼쪽 결측 두 행이 오른쪽 결측 한 행과 각각 연결됐다. 결측 그룹도 여러 행이라면 결과가 더 늘 수 있으므로 ‘빈 키 한 건’이라는 생각으로 보지 않는다.

SQL의 NULL 검사와 키끼리의 등호 비교는 다르다

PostgreSQL의 비교 연산 안내는 NULL이 포함된 일반 비교가 알 수 없음이 되며 NULL 여부는 IS NULL로 검사하도록 설명한다. SQLite JOIN 설명에서 ON 조건은 참인 조합을 포함한다. 따라서 이 예의 NULL = NULL은 참인 매칭 조건이 되지 않는다.

반대로 null을 같은 것으로 취급하는 비교를 명시할 수도 있다. 이번 SQLite의 IS NOT DISTINCT FROM 관측은 그 조건을 따로 실행한 결과다. SQL에서는 절대로 NULL끼리 결합할 수 없다고 일반화하지 않는다. DB 제품·버전과 실제 조건을 확인해야 한다. PostgreSQL 문서를 읽었다는 사실을 그 서버에서 이번 예제를 실행한 것으로 바꾸지도 않는다.

같은 평가 카드가 청록색과 노란색 두 목록으로 나뉘고 돋보기로 실패 카드를 살펴보는 삽화
같은 입력을 두 결과 목록에 대조하는 기존 AI 생성 개념도. 동일한 키 자료를 다른 매칭 계약으로 읽었을 때의 차이를 비교하는 맥락이다. SQL NULL이나 pandas의 실제 출력 표를 그린 화면은 아니다.

빈 문자열과 공백 문자열도 별개다. 이번 SQLite/pandas 예에서 ''는 실제 값으로 양쪽에 있어 연결됐다. 이를 모든 DB의 빈 문자열 규칙으로 확대하지 않는다. None·NaN·pd.NA도 표시가 비슷하다는 이유로 모든 dtype 조합에서 같다고 간주하지 않는다. 로컬에서는 같은 dtype끼리 object의 None, object의 NaN, nullable string의 pd.NA, Int64의 pd.NA를 각각 확인했고 각 사례의 결측 한 쌍이 매칭됐다. 다른 dtype 혼합·복합 키·사용자 변환은 별도 확인 대상이다.

결측 키를 연결하지 않을 계약이라면 제외 범위를 정한다

양쪽 결측 행을 결과에서 빼도 되는 inner 결합이라면, 키의 notna 조건으로 양쪽 입력을 걸러 결합하는 한 가지 방법이 있다. 하지만 왼쪽 원본 행을 모두 보존해야 한다면 왼쪽 결측까지 버리면 안 된다. 아래는 오른쪽 결측 키를 매칭 후보에서 제외하고 왼쪽은 남기는, 이 글의 단일 키 자료에 대한 예다.

right_known = right[right['key'].notna()]
result = left.merge(
    right_known, on='key', how='left', indicator=True
)

위 계약을 합성 자료에 적용하자 왼쪽 네 행이 남았고, A와 빈 문자열은 연결되며 결측 키 두 행은 left_only로 남았다. 이 방식도 오른쪽의 비결측 키 중복까지 막아 주지는 않는다. 결합 키의 유일성과 예상 관계를 함께 검사한다. 복합 키라면 어느 구성 요소가 결측일 때 매칭을 금지하는지 먼저 정해야 한다.

임시 키로 채우면 진짜 키와 충돌할 수 있다

결측을 모두 'UNKNOWN'으로 채우는 방식은 자동 해결책이 아니다. 같은 문자열이 정상 키로도 존재하는 합성 자료를 양쪽에 한 건씩, 결측도 한 건씩 만들었다. 원래 pandas 결합은 정상 키끼리와 결측끼리 두 행이었지만, 채운 뒤에는 같은 키가 각각 두 개가 돼 네 조합이 나왔다. 실제 정상 행과 원래 결측 행이 교차 연결된 것이다.

SQL에서도 sentinel을 양쪽에 넣으면 원래 연결되지 않던 NULL 행을 같은 키로 연결할 수 있다. 어떤 임시 값이 도메인에 없다는 확인과 결측 매칭의 업무상 허용 여부 없이 fillna를 먼저 실행하지 않는다. 식별되지 않은 두 행을 같은 사람·같은 주문으로 취급해도 되는지는 도구가 정해 주지 않는다.

빈 값의 업무 뜻 자체는 누락·null·빈 문자열·0의 구별에서 다룬다. 여기서는 그 값을 두 표의 연결 키로 썼을 때의 결과가 핵심이다. 눈에는 같아도 키의 실제 문자열이 다르다면 인코딩·BOM과 머리글 점검을 먼저 구별하고, 결합 후 금액을 더할 때는 집계 단위와 합계 증폭까지 확인한다.

자주 묻는 질문

결측 키는 모두 버리는 것이 안전한가요?

업무가 왼쪽 행을 보존해야 한다면 누락을 숨길 수 있다. 매칭 후보에서 뺄 것인지 결과에서도 뺄 것인지 구분하고, 원본 행 수·미매칭 수를 함께 남긴다.

IS NULL을 쓰면 두 표의 빈 행이 올바르게 연결되나요?

IS NULL은 해당 값이 NULL인지 검사한다. 어느 왼쪽 행과 어느 오른쪽 행이 같은 대상을 뜻하는지 식별하는 조건을 대신하지 않는다. 양쪽 NULL 여부만 조건으로 삼으면 여러 미확인 행의 조합이 생길 수 있다.

pandas 결과를 SQL과 같게 만들면 업무도 정확한가요?

특정 매칭 계약이 같아졌다는 뜻이다. 잘못 선택한 키·대상 범위·입력 값이나 오른쪽 중복은 여전히 남을 수 있다. 실제 업무의 연결 의도와 결과 ID를 대조해야 한다.

이 카테고리의 다른 글

전체글 보기

댓글 남기기