diff --git a/docs/design/730-adb-rds-postgres-dblink-demo/README.md b/docs/design/730-adb-rds-postgres-dblink-demo/README.md index 08a2173..48edb2a 100644 --- a/docs/design/730-adb-rds-postgres-dblink-demo/README.md +++ b/docs/design/730-adb-rds-postgres-dblink-demo/README.md @@ -95,6 +95,36 @@ View comment에 기록한다. 각 View는 원격 소문자 컬럼을 Oracle의 일반 대문자 식별자로 명시적으로 alias한다. 따라서 후속 SQL과 Select AI metadata에서는 큰따옴표 없이 안정적으로 사용할 수 있다. +### 5.4 기존 접근 그룹을 이용한 직원별 선사 배정 + +직원별 담당 선사는 신규 업무 테이블을 만들지 않고 기존 백오피스 접근 그룹 모델을 +재사용한다. + +| 기존 객체 | 선사 배정에서의 역할 | +|---|---| +| `HMM_HR_EMPLOYEES` | 직원, 팀장, 팀 관계 | +| `HMM_ACCESS_GROUPS` | `CARRIER_C001`부터 `CARRIER_C008`까지 선사 접근 그룹 | +| `HMM_ACCESS_GROUP_MEMBERS` | 직원과 담당 선사의 연결 | +| `HMM_CARRIER_ASSIGNMENTS_V` | 기존 세 테이블을 Select AI가 사용하기 쉬운 형태로 정규화 | + +`HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE`는 그룹 코드에서 `CARRIER_` 접두어를 제거해 +만들며, PostgreSQL 기반 `HMM_RDS_*_V.CARRIER_CODE`와 조인한다. 이 구조를 사용하면 기존 +백오피스의 접근 그룹 화면에서 담당자 변경이 가능하고 별도 관리 화면이나 중복 테이블이 +필요하지 않다. + +`HMM_ACCESS_PERMISSION_RULES`는 행 접근 정책을 정의하는 보안 메타데이터이므로 담당 선사 +원장으로 사용하지 않는다. + +Select AI가 이 관계를 추론에만 의존하지 않도록 네 View의 table comment와 +`CARRIER_CODE` column comment 양쪽에 상대 객체명과 조인 키를 기록한다. + +- `HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE` + ↔ `HMM_RDS_CARRIERS_V.CARRIER_CODE` +- `HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE` + ↔ `HMM_RDS_CARRIER_PERF_V.CARRIER_CODE` +- `HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE` + ↔ `HMM_RDS_CARRIER_LATEST_V.CARRIER_CODE` + ## 6. 녹화용 실행 파일 | 순서 | 파일 | 화면에서 확인할 결과 | @@ -107,6 +137,8 @@ View comment에 기록한다. | 5 | `78_hmm_rds_pg_05_views.sql` | Oracle 로컬 View 3개 | | 6 | `79_hmm_rds_pg_06_verify.sql` | `8 / 144 / 8`, 위험도 3종 | | 재촬영 전 | `80_hmm_rds_pg_99_cleanup.sql` | View, DB Link, Credential만 제거 | +| 7 | `81_hmm_carrier_access_groups.sql` | 선사 그룹 8건, 구성원 8건, federation 조인 8건 | +| 8 | `82_hmm_federation_relationship_comments.sql` | 양쪽 View의 명시적 조인 관계 metadata | ## 7. 성공 기준 @@ -116,6 +148,8 @@ View comment에 기록한다. - 원격 직접 조회가 선사 8건과 실적 144건을 반환한다. - 로컬 View 3개가 `VALID` 상태다. - 최신 View가 8건을 반환하고 `GREEN`, `AMBER`, `RED`가 모두 존재한다. +- `CARRIER_%` 접근 그룹 8개와 직원-그룹 구성원 8건이 존재한다. +- `HMM_CARRIER_ASSIGNMENTS_V`와 `HMM_RDS_CARRIER_LATEST_V`의 조인이 8건을 반환한다. - Credential password가 SQL 파일, Git diff, 화면 출력에 남지 않는다. ## 8. 실패 시 판별 @@ -132,6 +166,7 @@ View comment에 기록한다. ## 9. 제외 범위 -- 이번 단계에서는 ADB 직원과 선사의 담당 배정 테이블 및 VPD 정책을 만들지 않는다. +- 별도 직원-선사 담당 테이블은 만들지 않고 기존 접근 그룹 객체를 재사용한다. +- 선사별 VPD 정책 적용은 후속 단계에서 수행한다. - Select AI profile과 MCP tool 등록은 로컬 View 검증 후 후속 단계에서 수행한다. - 실제 실행은 녹화를 진행하는 사용자가 SQL Worksheet에서 수행한다. diff --git a/sql/adb/81_hmm_carrier_access_groups.sql b/sql/adb/81_hmm_carrier_access_groups.sql new file mode 100644 index 0000000..03e12ff --- /dev/null +++ b/sql/adb/81_hmm_carrier_access_groups.sql @@ -0,0 +1,209 @@ +-- ============================================================ +-- HMM carrier assignments using existing access-group tables. +-- +-- Existing tables: +-- HMM_HR_EMPLOYEES +-- HMM_ACCESS_GROUPS +-- HMM_ACCESS_GROUP_MEMBERS +-- +-- No new assignment table is created. +-- Korean values are reconstructed from UTF-8 base64 so SQLcl +-- cannot corrupt the literals. +-- Redmine: #730 +-- ============================================================ + +WHENEVER SQLERROR EXIT SQL.SQLCODE +SET DEFINE OFF +SET FEEDBACK ON +SET SERVEROUTPUT ON + +MERGE INTO hmm_access_groups dst +USING ( + SELECT + base.max_group_id + carrier.seq_no AS group_id, + 'CARRIER_' || carrier.carrier_code AS group_code, + carrier.carrier_code || ' ' || + utl_i18n.raw_to_char( + utl_encode.base64_decode( + utl_raw.cast_to_raw('7ISg7IKsIOuLtOuLuSDqt7jro7k=') + ), + 'AL32UTF8' + ) AS group_name, + utl_i18n.raw_to_char( + utl_encode.base64_decode( + utl_raw.cast_to_raw( + '642w66qo7JqpIOyEoOyCrCDsoJHqt7wg67CPIOuLtOuLueyekCDqt7jro7k=' + ) + ), + 'AL32UTF8' + ) || ' (' || carrier.carrier_code || ')' AS description + FROM ( + SELECT 1 seq_no, 'C001' carrier_code FROM dual + UNION ALL SELECT 2, 'C002' FROM dual + UNION ALL SELECT 3, 'C003' FROM dual + UNION ALL SELECT 4, 'C004' FROM dual + UNION ALL SELECT 5, 'C005' FROM dual + UNION ALL SELECT 6, 'C006' FROM dual + UNION ALL SELECT 7, 'C007' FROM dual + UNION ALL SELECT 8, 'C008' FROM dual + ) carrier + CROSS JOIN ( + SELECT NVL(MAX(group_id), 0) max_group_id + FROM hmm_access_groups + ) base +) src +ON (dst.group_code = src.group_code) +WHEN MATCHED THEN UPDATE SET + dst.group_name = src.group_name, + dst.description = src.description, + dst.active_yn = 'Y', + dst.updated_at = SYSTIMESTAMP +WHEN NOT MATCHED THEN INSERT ( + group_id, + group_code, + group_name, + description, + active_yn, + created_at, + updated_at +) VALUES ( + src.group_id, + src.group_code, + src.group_name, + src.description, + 'Y', + SYSTIMESTAMP, + SYSTIMESTAMP +); + +MERGE INTO hmm_access_group_members dst +USING ( + SELECT + access_group.group_id, + employee.employee_id + FROM ( + SELECT 'E1002' employee_code, 'CARRIER_C001' group_code FROM dual + UNION ALL SELECT 'E1002', 'CARRIER_C002' FROM dual + UNION ALL SELECT 'E1003', 'CARRIER_C003' FROM dual + UNION ALL SELECT 'E1004', 'CARRIER_C004' FROM dual + UNION ALL SELECT 'E1005', 'CARRIER_C005' FROM dual + UNION ALL SELECT 'E1006', 'CARRIER_C006' FROM dual + UNION ALL SELECT 'E1006', 'CARRIER_C007' FROM dual + UNION ALL SELECT 'E1007', 'CARRIER_C008' FROM dual + ) assignment + JOIN hmm_hr_employees employee + ON employee.employee_code = assignment.employee_code + AND employee.employment_status = 'ACTIVE' + JOIN hmm_access_groups access_group + ON access_group.group_code = assignment.group_code + AND access_group.active_yn = 'Y' +) src +ON ( + dst.group_id = src.group_id + AND dst.employee_id = src.employee_id +) +WHEN NOT MATCHED THEN INSERT ( + group_id, + employee_id, + created_at +) VALUES ( + src.group_id, + src.employee_id, + SYSTIMESTAMP +); + +CREATE OR REPLACE VIEW hmm_carrier_assignments_v AS +SELECT + employee.employee_id, + employee.employee_code, + employee.full_name AS employee_name, + employee.job_title, + employee.team_id, + employee.manager_employee_id, + manager.employee_code AS manager_employee_code, + manager.full_name AS manager_name, + SUBSTR(access_group.group_code, LENGTH('CARRIER_') + 1) AS carrier_code, + access_group.group_code, + access_group.group_name +FROM hmm_access_group_members membership +JOIN hmm_access_groups access_group + ON access_group.group_id = membership.group_id + AND access_group.active_yn = 'Y' +JOIN hmm_hr_employees employee + ON employee.employee_id = membership.employee_id + AND employee.employment_status = 'ACTIVE' +LEFT JOIN hmm_hr_employees manager + ON manager.employee_id = employee.manager_employee_id +WHERE access_group.group_code LIKE 'CARRIER_C%'; + +COMMENT ON TABLE hmm_carrier_assignments_v IS + 'ADB employee-to-carrier assignments from HMM_HR_EMPLOYEES, HMM_ACCESS_GROUPS, and HMM_ACCESS_GROUP_MEMBERS. Join relationship: HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE joins HMM_RDS_CARRIERS_V.CARRIER_CODE, HMM_RDS_CARRIER_PERF_V.CARRIER_CODE, and HMM_RDS_CARRIER_LATEST_V.CARRIER_CODE.'; +COMMENT ON COLUMN hmm_carrier_assignments_v.EMPLOYEE_CODE IS + 'HMM employee code sourced from HMM_HR_EMPLOYEES.EMPLOYEE_CODE. Example: E1002.'; +COMMENT ON COLUMN hmm_carrier_assignments_v.MANAGER_EMPLOYEE_CODE IS + 'Direct manager employee code sourced from HMM_HR_EMPLOYEES. Use E1001 to identify the HMM demo manager team.'; +COMMENT ON COLUMN hmm_carrier_assignments_v.CARRIER_CODE IS + 'Join Key and Foreign Key to HMM_RDS_CARRIERS_V(CARRIER_CODE), HMM_RDS_CARRIER_PERF_V(CARRIER_CODE), and HMM_RDS_CARRIER_LATEST_V(CARRIER_CODE).'; +COMMENT ON COLUMN hmm_carrier_assignments_v.GROUP_CODE IS + 'Existing access-group code in the form CARRIER_C001.'; + +COMMIT; + +DECLARE + v_group_count PLS_INTEGER; + v_member_count PLS_INTEGER; + v_assignment_count PLS_INTEGER; + v_federated_count PLS_INTEGER; +BEGIN + SELECT COUNT(*) + INTO v_group_count + FROM hmm_access_groups + WHERE group_code BETWEEN 'CARRIER_C001' AND 'CARRIER_C008' + AND active_yn = 'Y'; + + SELECT COUNT(*) + INTO v_member_count + FROM hmm_access_group_members membership + JOIN hmm_access_groups access_group + ON access_group.group_id = membership.group_id + WHERE access_group.group_code BETWEEN 'CARRIER_C001' AND 'CARRIER_C008'; + + SELECT COUNT(*) + INTO v_assignment_count + FROM hmm_carrier_assignments_v; + + SELECT COUNT(*) + INTO v_federated_count + FROM hmm_carrier_assignments_v assignment + JOIN hmm_rds_carrier_latest_v performance + ON performance.carrier_code = assignment.carrier_code; + + IF v_group_count != 8 + OR v_member_count != 8 + OR v_assignment_count != 8 + OR v_federated_count != 8 THEN + RAISE_APPLICATION_ERROR( + -20081, + 'Expected group/member/assignment/federated counts 8/8/8/8; found ' + || v_group_count || '/' + || v_member_count || '/' + || v_assignment_count || '/' + || v_federated_count + ); + END IF; +END; +/ + +SELECT + assignment.employee_code, + assignment.employee_name, + assignment.manager_employee_code, + assignment.carrier_code, + performance.carrier_name, + performance.performance_month, + performance.revenue_usd, + performance.risk_level +FROM hmm_carrier_assignments_v assignment +JOIN hmm_rds_carrier_latest_v performance + ON performance.carrier_code = assignment.carrier_code +ORDER BY assignment.employee_code, assignment.carrier_code; diff --git a/sql/adb/82_hmm_federation_relationship_comments.sql b/sql/adb/82_hmm_federation_relationship_comments.sql new file mode 100644 index 0000000..a5418ca --- /dev/null +++ b/sql/adb/82_hmm_federation_relationship_comments.sql @@ -0,0 +1,67 @@ +-- ============================================================ +-- Explicit Select AI relationship metadata for ADB + RDS Views. +-- Redmine: #730 +-- ============================================================ + +WHENEVER SQLERROR EXIT SQL.SQLCODE +SET DEFINE OFF +SET FEEDBACK ON + +COMMENT ON TABLE hmm_carrier_assignments_v IS + 'ADB employee-to-carrier assignments from HMM_HR_EMPLOYEES, HMM_ACCESS_GROUPS, and HMM_ACCESS_GROUP_MEMBERS. Join relationship: HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE joins HMM_RDS_CARRIERS_V.CARRIER_CODE, HMM_RDS_CARRIER_PERF_V.CARRIER_CODE, and HMM_RDS_CARRIER_LATEST_V.CARRIER_CODE.'; + +COMMENT ON COLUMN hmm_carrier_assignments_v.EMPLOYEE_CODE IS + 'HMM employee code sourced from HMM_HR_EMPLOYEES.EMPLOYEE_CODE. Example: E1002.'; + +COMMENT ON COLUMN hmm_carrier_assignments_v.MANAGER_EMPLOYEE_CODE IS + 'Direct manager employee code sourced from HMM_HR_EMPLOYEES. Use E1001 to identify the HMM demo manager team.'; + +COMMENT ON COLUMN hmm_carrier_assignments_v.CARRIER_CODE IS + 'Join Key and Foreign Key to HMM_RDS_CARRIERS_V(CARRIER_CODE), HMM_RDS_CARRIER_PERF_V(CARRIER_CODE), and HMM_RDS_CARRIER_LATEST_V(CARRIER_CODE).'; + +COMMENT ON TABLE hmm_rds_carriers_v IS + 'PostgreSQL fictional carrier master exposed through HMM_RDS_PG_LINK. Primary Key: CARRIER_CODE. Join relationship: HMM_RDS_CARRIERS_V.CARRIER_CODE joins HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE and HMM_RDS_CARRIER_PERF_V.CARRIER_CODE.'; + +COMMENT ON COLUMN hmm_rds_carriers_v.CARRIER_CODE IS + 'Primary Key. Join Key to HMM_CARRIER_ASSIGNMENTS_V(CARRIER_CODE), HMM_RDS_CARRIER_PERF_V(CARRIER_CODE), and HMM_RDS_CARRIER_LATEST_V(CARRIER_CODE).'; + +COMMENT ON TABLE hmm_rds_carrier_perf_v IS + 'PostgreSQL monthly carrier KPI exposed through HMM_RDS_PG_LINK. Composite Primary Key: CARRIER_CODE and PERFORMANCE_MONTH. Foreign Key: CARRIER_CODE references HMM_RDS_CARRIERS_V(CARRIER_CODE). Join relationship: CARRIER_CODE joins HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE.'; + +COMMENT ON COLUMN hmm_rds_carrier_perf_v.CARRIER_CODE IS + 'Foreign Key to HMM_RDS_CARRIERS_V(CARRIER_CODE). Join Key to HMM_CARRIER_ASSIGNMENTS_V(CARRIER_CODE).'; + +COMMENT ON COLUMN hmm_rds_carrier_perf_v.PERFORMANCE_MONTH IS + 'Part of the composite Primary Key with CARRIER_CODE. First calendar day of the KPI month.'; + +COMMENT ON TABLE hmm_rds_carrier_latest_v IS + 'Latest PostgreSQL KPI for each fictional carrier exposed through HMM_RDS_PG_LINK. Primary Key: CARRIER_CODE. Join relationship: HMM_RDS_CARRIER_LATEST_V.CARRIER_CODE joins HMM_CARRIER_ASSIGNMENTS_V.CARRIER_CODE and HMM_RDS_CARRIERS_V.CARRIER_CODE.'; + +COMMENT ON COLUMN hmm_rds_carrier_latest_v.CARRIER_CODE IS + 'Primary Key. Join Key to HMM_CARRIER_ASSIGNMENTS_V(CARRIER_CODE) and HMM_RDS_CARRIERS_V(CARRIER_CODE).'; + +SELECT table_name, comments +FROM user_tab_comments +WHERE table_name IN ( + 'HMM_CARRIER_ASSIGNMENTS_V', + 'HMM_RDS_CARRIERS_V', + 'HMM_RDS_CARRIER_PERF_V', + 'HMM_RDS_CARRIER_LATEST_V' +) +ORDER BY table_name; + +SELECT table_name, column_name, comments +FROM user_col_comments +WHERE table_name IN ( + 'HMM_CARRIER_ASSIGNMENTS_V', + 'HMM_RDS_CARRIERS_V', + 'HMM_RDS_CARRIER_PERF_V', + 'HMM_RDS_CARRIER_LATEST_V' +) + AND column_name IN ( + 'EMPLOYEE_CODE', + 'MANAGER_EMPLOYEE_CODE', + 'CARRIER_CODE', + 'PERFORMANCE_MONTH' + ) +ORDER BY table_name, column_name;