질문에 대한 답변
관계 나누기는 1970년 Edgar F. Codd에 의해 카르테시안 곱의 역으로 공식 정의되었으며, 관계 대수에서 보편 양을 표현하기 위해 설계되었습니다. ANSI SQL은 WHERE 절과 조인을 통해 존재 양을 자연스럽게 구현하지만, 본래의 나누기 연산자가 없기 때문에 개발자들은 논리적 부정이나 카운팅 전략을 사용하여 이 집합 이론적 작업을 시뮬레이션해야 합니다. 이 패턴은 규제 준수, 권한 매트릭스 및 역량 추적 시스템에서 "완전한 집합"을 식별하는 것이 중요합니다.
배당 테이블 EmployeeTraining(employee_id, module_id)와 나누기 테이블 RequiredModules(module_id)가 주어졌을 때, 목표는 나누기 테이블의 모든 행과 관련된 모든 employee_id를 반환하는 것입니다. 이 도전 과제는 단순 조인을 넘어 격리된 모든 매치를 찾기 위해 총 커버리지를 검증해야 합니다. 특히, 해결 방법은 중복 완료 기록, 비어 있는 요구 사항 집합(진공 진리)을 처리하고 절차적 로직 없이 효율적으로 실행해야 합니다.
정식 ANSI SQL 접근법은 이중 부정을 사용합니다: 완료되지 않은 필수 모듈이 존재하지 않는 직원들을 선택하는 것입니다. 이는 중첩된 NOT EXISTS 절로 변환됩니다. 대안적으로, 카운팅 방법은 필요 총수와의 구별된 완료를 비교하지만 중복 처리를 요구합니다.
-- 이중 부정: 순수 관계 나누기 SELECT DISTINCT e.employee_id FROM EmployeeTraining e WHERE NOT EXISTS ( SELECT 1 FROM RequiredModules r WHERE NOT EXISTS ( SELECT 1 FROM EmployeeTraining e2 WHERE e2.employee_id = e.employee_id AND e2.module_id = r.module_id ) ); -- 카운팅 방법 (중복 처리 포함) SELECT employee_id FROM ( SELECT e.employee_id, COUNT(DISTINCT e.module_id) AS completed_count FROM EmployeeTraining e JOIN RequiredModules r ON e.module_id = r.module_id GROUP BY e.employee_id ) sub WHERE completed_count = (SELECT COUNT(*) FROM RequiredModules);
생활 속 상황
항공 유지 보수 회사는 엔진 수리를 위한 기계공 인증이 필요했습니다. FAA는 Mechanic_Completions에서 추적되는 다섯 개의 특정 안전 모듈의 완료를 요구했지만, 기계공들은 종종 실패한 모듈을 다시 수강하여 중복 행을 생성했습니다. 1,200명의 기계공과 200개의 가능한 모듈에 대해 매일 이 검사를 실행하려면 중복을 무시하고 요구 사항 목록이 일시적으로 비어 있을 수 있는 감사 시나리오를 처리하는 쿼리가 필요했습니다.
해결 방법 1: GROUP BY와 COUNT(DISTINCT)
이 접근법은 테이블을 조인하고 기계공별로 그룹화한 후 이질적인(count distinct) 수를 비교했습니다. 주요 장점은 가독성이었습니다; 주니어 개발자들은 논리를 즉시 이해했습니다. 그러나 2백만 개의 이력 기록에 대해 DISTINCT 연산으로 인해 성능 저하가 심각하게 발생했습니다. 더욱 중요하게, 명시적인 COALESCE 처리가 없으면 RequiredModules 테이블이 비어 있을 때 기계공을 0으로 반환하여 공리적 수량화의 원칙을 위반했습니다.
해결 방법 2: NOT EXISTS를 사용한 이중 부정
이 방법은 두 개의 중첩된 NOT EXISTS 절을 사용하여 부족한 모듈을 확인했습니다. 이는 중복 완료 기록을 자연스럽게 처리했으며, 발생 횟수를 세지 않고 존재만 확인했습니다(세미 조인 행동). 요구 사항 집합이 비어 있을 때 모든 기계공을 올바르게 반환했습니다. 단점은 더 복잡한 실행 계획을 포함했으며, 옵티마이저가 가끔씩 해시 조인 대신 중첩 루프 조인을 선택하기도 했지만, module_id에 대한 적절한 인덱싱은 이를 완화했습니다.
선택된 해결 방법과 결과 팀은 데이터 무결성 규칙이 중복 완료 항목을 허용했기 때문에 카운팅 방법이 비싼 DISTINCT 연산 없이 위험하다고 판단하여 이중 부정 접근 방식을 선택했습니다. 쿼리는 1,200명 중 847명의 완전 인증 기계공을 150ms 이내에 식별했습니다. 요구 사항이 일시적으로 중단된 다음 규제 감사에서 쿼리는 모든 1,200명의 기계공을 준수 사항으로 올바르게 식별하여, 인력이 불필요하게 정지되는 것을 방지하고 논리적 정확성을 유지했습니다.
후보자들이 자주 놓치는 것
RequiredModules 테이블에 행이 없을 때 쿼리가 어떻게 작동하며, 이것이 수학적으로 왜 중요한가요?
나누기가 비어 있을 때 관계 나누기는 전체 배당 세트를 반환해야 합니다(모든 직원) 왜냐하면 진공 진리 원리에 따르면 "모든 요소는 비어 있는 집합의 모든 항목을 만족한다고 합니다." 이중 부정 방법은 이를 자연스럽게 처리합니다. 필수 모듈이 존재하지 않기 때문에 내부 NOT EXISTS는 결코 누락된 모듈을 찾지 못하고, 외부 절은 아무도 제외하지 않습니다. 반면, 카운팅 방법인 completed_count = (SELECT COUNT(*) FROM RequiredModules)는 특수 집합의 수를 0에 맞춰서 0개의 완료만 있는 기계공만 반환합니다. 후보자들은 나누기가 비어 있을 때 모든 행을 반환하기 위해 COALESCE 래퍼 또는 CASE 논리를 구현해야 하거나, 이 경우를 내재적으로 처리하는 이중 부정 패턴을 사용할 필요가 있습니다.
COUNT(*)를 사용하는 카운팅 방법이 COUNT(DISTINCT module_id)로 잘못된 긍정을 생성하는 이유는 무엇이며, 중복이 이중 부정 접근 방식에 어떤 영향을 미칩니까?
기계공이 모듈 A를 두 번 완료할 경우 (초기 실패, 재수강), **COUNT(*)**는 2를 반환합니다. 요구되는 모듈이 A와 B 뿐인 경우, B가 누락되었지만 A 기록이 두 개 있는 기계공은 2개의 카운트를 보여주어 정확히 동일한 차이를 만족시키게 됩니다. 이는 중요한 준수 격차를 생성합니다. 후보자들은 외래 키 제약 조건이 중복을 방지한다고 가정하여 DISTINCT를 자주 누락합니다. 이중 부정 방법은 존재만 체크하므로 (SELECT 1) 배당 테이블에서 중복 행에 면역이 있습니다. 어떤 연관성이 exist할 경우, 모듈은 충족됩니다. 이 차이를 이해하는 것은 완벽한 고유 제약이 없는 데이터 환경에서 필수적입니다.
정확한 관계 나누기와 잔여가 있는 나누기의 차이는 무엇이며, 해당 쿼리를 수정하여 추가 없이 정확히 필요한 모듈만 완료한 직원을 찾으려면 어떻게 해야 합니까?
위의 솔루션은 "잔여가 있는 나누기" (느슨한 나누기)를 구현하여 필요한 모듈을 최소한 가진 직원을 반환합니다 (상위 집합). 정확한 나누기는 직원이 요구되는 모듈 이외의 추가 모듈을 소유하지 않아야 합니다. 이를 달성하기 위해 후보자들은 기계공의 총 구별된 모듈 수가 요구된 수와 같음을 보장하는 필터링 조건을 추가해야 합니다: HAVING COUNT(DISTINCT module_id) = (SELECT COUNT(*) FROM RequiredModules). 많은 후보자들이 관계 나누기가 "정확히 이러한 것과 이러한 것뿐"을 의미한다고 잘못 가정하여 권한 버그를 일으키는 경우가 많습니다.