계층 구조 데이터의 동적 필터링과 집계 방법에 관해 질문드립니다. 0 5 8,438

by bobong [Tibero] sql tibero group statistics [2024.09.04 18:50:17]


안녕하세요, 계층 구조를 가진 데이터를 동적으로 필터링하고 집계하는 복잡한 SQL 쿼리에 대해 감이 전혀 잡히지 않아 질문 글 남기게 되었습니다. 문제 상황은 다음과 같습니다:

테이블 구조

부서 테이블 (DEPARTMENT):

CREATE TABLE DEPARTMENT (
    DEPT_ID INT PRIMARY KEY,
    DEPT_NAME VARCHAR(100),
    PARENT_DEPT_ID INT,
    HIERARCHY_LEVEL INT,
    FOREIGN KEY (PARENT_DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID)
);

 

카테고리 테이블 (CATEGORY): (어플리케이션 단에서 ENUM 계층 구조로 관리하였는데 TABLE로도 추가해놓은 상태)

CREATE TABLE CATEGORY (
    CATEGORY_ID INT PRIMARY KEY,
    CATEGORY_NAME VARCHAR(100),
    PARENT_CATEGORY_ID INT,
    HIERARCHY_LEVEL INT,
    FOREIGN KEY (PARENT_CATEGORY_ID) REFERENCES CATEGORY(CATEGORY_ID)
);

 

A 테이블 (데이터 저장 테이블):

CREATE TABLE A (
    ID INT PRIMARY KEY,
    DEPT_ID INT,
    CATEGORY_ID INT,
    -- 기타 필요한 컬럼들
    FOREIGN KEY (DEPT_ID) REFERENCES DEPARTMENT(DEPT_ID),
    FOREIGN KEY (CATEGORY_ID) REFERENCES CATEGORY(CATEGORY_ID)
);

 

예시 데이터

DEPARTMENT 테이블:

INSERT INTO DEPARTMENT VALUES
(1, '전체', NULL, 1),
(2, '영업본부', 1, 2),
(3, '영업1팀', 2, 3),
(4, '영업2팀', 2, 3),
(5, '고객서비스본부', 1, 2),
(6, 'CS팀', 5, 3);

 

CATEGORY 테이블:

INSERT INTO CATEGORY VALUES
(1, '전체', NULL, 1),
(2, '제품', 1, 2),
(3, '품질', 2, 3),
(4, '배송', 1, 2),
(5, '지연', 4, 3),
(6, '파손', 4, 3);

 

A 테이블:

INSERT INTO A (ID, DEPT_ID, CATEGORY_ID) VALUES
(1, 3, 3),  -- 영업1팀, 품질
(2, 4, 3),  -- 영업2팀, 품질
(3, 6, 5),  -- CS팀, 지연
(4, 3, 6),  -- 영업1팀, 파손
(5, 4, 3),  -- 영업2팀, 품질
(6, 6, 5),  -- CS팀, 지연
(7, 3, 3),  -- 영업1팀, 품질
(8, 4, 6);  -- 영업2팀, 파손

요구사항

사용자는 화면에서 여러 부서 ID와 여러 카테고리 ID를 필터로 선택할 수 있습니다.

선택된 부서 ID들은 서로 다른 계층 레벨일 수 있습니다 (예: 영업본부와 CS팀을 동시에 선택 가능).

선택된 카테고리 ID들도 서로 다른 계층 레벨일 수 있습니다 (예: 제품과 지연을 동시에 선택 가능).

쿼리는 선택된 부서와 그 하위 부서의 데이터를 모두 포함해야 합니다.

쿼리는 선택된 카테고리와 그 하위 카테고리의 데이터를 모두 포함해야 합니다.

결과는 선택된 부서별, 선택된 카테고리별로 집계되어야 합니다.

예시 시나리오

사용자가 다음과 같이 필터를 선택했다고 가정하면:

선택된 부서 ID: 2 (영업본부), 6 (CS팀)

선택된 카테고리 ID: 2 (제품), 4 (배송)

이 경우, 원하는 결과는 다음과 같습니다:

부서

카테고리

개수

영업본부

제품

4

영업본부

배송

2

CS팀

제품

0

CS팀

배송

2

설명:

영업본부의 '제품' 카테고리 개수 4는 영업1팀과 영업2팀의 '품질' 관련 데이터를 모두 포함합니다 (품질은 제품의 하위 카테고리).

영업본부의 '배송' 카테고리 개수 2는 영업1팀과 영업2팀의 '파손' 관련 데이터를 포함합니다 (파손은 배송의 하위 카테고리).

CS팀의 '배송' 카테고리 개수 2는 '지연' 관련 데이터입니다 (지연은 배송의 하위 카테고리).

질문

위의 테이블 구조와 요구사항을 바탕으로, 어떻게 효율적인 SQL 쿼리를 작성할 수 있을까요? SQL로만 해결이 불가능하다면 어디까지 SQL로 처리하고 어플리케이션에서 어느 부분을 처리해야 할지 도움 주시면 감사하겠습니다. 특히 다음 사항들에 대해 말씀 남겨주시면 감사하겠습니다:

계층 구조를 가진 부서와 카테고리를 어떻게 효과적으로 쿼리할 수 있을까요?

선택된 부서와 카테고리의 모든 하위 항목을 포함하여 집계하는 방법은 무엇인가요?

동적으로 변하는 필터 조건을 어떻게 쿼리에 반영할 수 있을까요?

이러한 복잡한 요구사항을 충족하면서도 성능을 최적화할 수 있는 방법이 있을까요?

사용 중인 데이터베이스는 Tibero입니다. 어떤 조언이든 감사하게 받아들이겠습니다. 감사합니다!

by 아발란체 [2024.09.05 15:40:18]

결국 A 테이블 빼고 기준 정보이고,

A 테이블이 데이터 테이블인데

밀어넣은 데이터 테이블에는 제품 2와 배송 4가 단 한건도 입력이 되지 않았는데

최종 표에 영업본부 제품 4가 어떻게 카운트 될 수 있을까요??


by bobong [2024.09.09 23:21:49]

안녕하세요 확인이 늦어 죄송합니다. 

최종 표에 영업 본부 제품4가 카운트 될 수 있는 이유는 제품과 배송의 하위 카테고리로 데이터를 가질 경우, 상위 부모에서도 집계되어야 하기 때문입니다!


by 마농 [2024.09.06 11:28:21]
WITH department AS
(
SELECT 1 dept_id, '전체' dept_name, null parent_dept_id, 1 hierarchy_level FROM dual
UNION ALL SELECT 2, '영업본부'      , 1, 2 FROM dual
UNION ALL SELECT 3, '영업1팀'       , 2, 3 FROM dual
UNION ALL SELECT 4, '영업2팀'       , 2, 3 FROM dual
UNION ALL SELECT 5, '고객서비스본부', 1, 2 FROM dual
UNION ALL SELECT 6, 'CS팀'          , 5, 3 FROM dual
)
, category AS
(
SELECT 1 category_id, '전체' category_name, null parent_category_id, 1 hierarchy_level FROM dual
UNION ALL SELECT 2, '제품', 1, 2 FROM dual
UNION ALL SELECT 3, '품질', 2, 3 FROM dual
UNION ALL SELECT 4, '배송', 1, 2 FROM dual
UNION ALL SELECT 5, '지연', 4, 3 FROM dual
UNION ALL SELECT 6, '파손', 4, 3 FROM dual
)
, a AS
(
SELECT 1 id, 3 dept_id, 3 category_id FROM dual  -- 영업1팀, 품질
UNION ALL SELECT 2, 4, 3 FROM dual  -- 영업2팀, 품질
UNION ALL SELECT 3, 6, 5 FROM dual  -- CS팀, 지연
UNION ALL SELECT 4, 3, 6 FROM dual  -- 영업1팀, 파손
UNION ALL SELECT 5, 4, 3 FROM dual  -- 영업2팀, 품질
UNION ALL SELECT 6, 6, 5 FROM dual  -- CS팀, 지연
UNION ALL SELECT 7, 3, 3 FROM dual  -- 영업1팀, 품질
UNION ALL SELECT 8, 4, 6 FROM dual  -- 영업2팀, 파손
)
SELECT d.r_dept_id
     , d.r_dept_name
     , c.r_category_id
     , c.r_category_name
     , COUNT(a.dept_id) cnt
  FROM (SELECT CONNECT_BY_ROOT(dept_id) r_dept_id
             , CONNECT_BY_ROOT(dept_name) r_dept_name
             , dept_id
          FROM department
         START WITH dept_id IN (2, 6)
         CONNECT BY PRIOR dept_id = parent_dept_id
        ) d
 CROSS JOIN
       (SELECT CONNECT_BY_ROOT(category_id) r_category_id
             , CONNECT_BY_ROOT(category_name) r_category_name
             , category_id
          FROM category
         START WITH category_id IN (2, 4)
         CONNECT BY PRIOR category_id = parent_category_id
        ) c
  LEFT OUTER JOIN a
    ON d.dept_id     = a.dept_id
   AND c.category_id = a.category_id
 GROUP BY d.r_dept_id, d.r_dept_name, c.r_category_id, c.r_category_name
 ORDER BY d.r_dept_id, c.r_category_id
;

 


by bobong [2024.09.09 23:26:49]

안녕하세요 확인이 늦어 죄송합니다.

직접 이렇게 쿼리까지 작성해주셔서 감사합니다!

CONNECT BY로 계층형 쿼리를 어떤식으로 구현하는지, 그리고 계층 테이블을 어떤식으로 join해서 집계에 사용할지 알 수 있었습니다. 감사드립니다!! 해당 쿼리 참고하면서, 제 db에 맞게 구현해보겠습니다~


by 마농 [2024.09.10 09:17:33]

오라클에서 테스트 했습니다.
티베로라면 오라클과 동일한 구문으로 실행 가능할 것으로 예상됩니다.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입