채널에서 받은 카테고리 삼천 건을 channel_categories 의 기존 목록과 비교해 신규만 뽑아야 했다. 기존 행의 형식을 보다가 leaf_code 로 행을 찾으면 여러 개가 나오는 것을 발견했다.
Table of contents
Open Table of contents
원인 — 여러 곳에 있는 같은 말단 코드
같은 말단 코드가 서로 다른 상위 경로 아래에 각각 존재하고 있었다. 말단 코드는 형제 안에서만 유일하고 트리 전체에서는 유일하지 않았다.
표로 펼쳐 놓고 보면 그냥 코드 컬럼처럼 보이므로 식별자로 쓰고 싶어진다. 계층 구조를 평평한 표로 볼 때 생기는 착시였다.
검증 — 유니크 제약이 걸린 자리
SHOW CREATE TABLE 로 보니 UNIQUE 제약이 leaf_code 가 아니라 full_path_code 에 걸려 있었다. 채널 응답도 code 와 fullPath 를 나란히 담고 있었다.
두 값이 나란히 있으면 어느 쪽이 키인지는 응답만 봐서는 알 수 없다. 어느 컬럼이 식별자인지는 스키마의 유니크 제약이 알려 주고 그것을 확인하지 않으면 짧은 쪽을 쓰게 된다.
주의 — 틀려도 오류가 안 나는 이유
말단 코드로 중복을 제거하면 서로 다른 카테고리가 하나로 합쳐지고 신규 항목이 조용히 빠진다. 말단 코드로 조인하면 엉뚱한 행이 매칭되어 갱신이 다른 카테고리에 들어간다.
두 경우 다 쿼리는 성공하고 건수도 그럴듯하게 나온다. 사후에 발견하려면 누군가 특정 카테고리가 이상하다고 말해 줘야 하므로 발견 시점이 한참 뒤가 된다.
조치 — 스크립트 안의 집합 연산
두 가지 방법이 있었는데 삼천 건을 임시 테이블에 넣고 LEFT JOIN 하는 것과 PHP 스크립트 안에서 집합 연산으로 비교하는 것이다. 뒤쪽을 골랐다. 이유는 앞쪽은 대량 INSERT 문을 만들어야 하고 쓰던 도구가 한 번에 문장 하나만 실행해서 번거로워서다. 대신 비교 결과가 DB 에 남지 않아 다시 보려면 스크립트를 또 돌려야 하는 것을 감수했다. 응답과 조회 결과를 각각 full_path_code 를 키로 하는 배열로 만들어 array_diff_key 로 비교했다.
키 집합의 차집합으로 신규와 삭제가 나오고 교집합에서 이름이 다른 것으로 변경이 나온다. 이번 결과는 신규만 백 건이고 이름 변경과 삭제는 없었다.
배경 — 신규와 변경과 삭제
차이를 하나의 숫자로 보고하지 않고 added 와 renamed 와 removed 세 갈래로 나눠 적었다. 백 건 차이라고만 하면 그것이 전부 신규인지 신규와 삭제가 섞인 것인지 알 수 없다.
삭제가 있으면 성격이 완전히 다른 이야기가 되는데 외부에서 카테고리를 접었다는 뜻이고 그 아래 붙은 상품을 어떻게 할지 정해야 한다. 이번에는 삭제가 없어서 신규만 넣으면 되는 작업으로 끝났다.
정리
- 계층 코드의 말단은 형제 안에서만 유일하다
- 평평한 표로 보면 말단 코드가 식별자처럼 보인다
- 식별은 전체 경로 코드로 한다
- 어느 컬럼이 키인지는 스키마의 유니크 제약이 알려 준다
- 말단으로 중복을 제거하면 신규가 조용히 빠진다
- 말단으로 조인하면 엉뚱한 행이 매칭된다
- 두 경우 다 오류가 안 나서 발견이 늦다
- 차이는 신규와 변경과 삭제 셋으로 나눠 보고한다