DA# Macro(5): 사용상 주의사항/참고사항, 다운로드, 향후 추가 예정 기능, 일러두기
이 글에서는 DA# Macro 사용상 주의사항/참고사항, 다운로드, 향후 추가 예정 기능, 일러두기에 대해 살펴본다.
이전 글에서 이어지는 내용이다.
DA# Macro(4): DA# Macro(매크로) 기능(3)-Reverse
3. DA# Macro 사용상 주의사항/참고사항
3.1. DA# Macro 사용상 주의사항
- Entity/Attribute에 대한 Set Macro 실행 전에 대상 DA# v5 모델 파일의 백업을 권고한다.
- 메모리 부족 오류 방지와 성능 향상을 위하여 Macro내에서 Undo를 비활성화시키기 때문이다.
- UDP 항목은 해당 모델에 미리 생성되어 있어야 한다. 현재 배포되는 DA# Macro Version(v2.12)에서는 UDP 항목을 생성해 주지는 않는다. UDP 명을 잘못 입력하고 Macro를 실행시켜 잘못된 UDP가 생성되는 문제를 방지하기 위함이다. (향후 version에서 구현 예정)
- UDP 항목은 추가/제거/순서변경추가/제거/순서변경 가능하나, 기본 항목은 추가/제거/순서변경 금지이다.
3.2. DA# Macro 참고사항
3.2.1. Reverse 동시 실행 방법
Reverse 기능은 입력 파일에 지정된 여러 모델을 순차적으로 처리할 수 있는데, 시간이 너무 오래 걸리는 경우는 모델별로 파일을 나누어 동시에 처리할 수 있다.
파일을 나눌 때, “Model” 시트에서만 대상 모델을 남겨두면 된다. “Table”, “Column”, “FK” 시트에 전체 모델에 대한 정보가 있더라도 “Model” 시트를 기준으로 Reverse를 실행한다.
한 PC에서 동시에 처리하려면 엑셀 instance를 여러 개 실행하고 각 instance에서 처리할 모델 파일을 지정하여 실행하면 된다.
(참고: 네이버 국어사전/영어사전 검색 도구 개요 – 3.2. 여러개 엑셀 process 실행 방법)
하지만 한 PC에서 동시에 처리하는 방법은 CPU 자원 경합으로 기대한 것보다는 빠르게 처리되지 않는다. 여러 PC에 나누어 실행하면 동시에 빠르게 처리할 수 있다.
3.2.2. DA# Macro가 정상적으로 동작하지 않는 경우 조치
다음과 같은 현상이 발생할 경우 조치 방법이다.
- 현상
- 작업관리자에서 확인해 보았을 때, DA# v5(Modeler5.exe)가 실행되어 있고(Instance #1), 모델을 하나 열고 있는 상태에서,
- DA# Macro에서 “열린모델 추가” 버튼을 클릭하면 Modeler5.exe 프로세스가 새로 생성되면서 (Instance #2) “열린 모델이 없습니다”는 메시지가 출력되고,
- 기존 Instance #1에서 열고 있는 모델 정보를 가져오지 못하는 현상
- 원인
- DA#을 설치할 때 Registry에 정보를 제대로 반영하지 못하는 경우 발생한다.
- 이 현상이 발생하면 DA#을 사용하는 데는 아무 지장이 없으나 API로 DA#을 실행하는 경우에 정상적으로 실행되지 않는다.
- 조치 방법
- 명령 프롬프트를 관리자 모드로 실행(중요!!!)하고 아래 명령어를 입력하여 DA# v5 객체를 재등록한다.
- DA#이 기본 경로와 다른 경로에 설치되어 있는 경우 해당 경로로 이동하여 “RegisterServer.bat” 명령을 실행한다.
cd "C:\Program Files (x86)\ENCORE\DATAWARE_DA5\DA Modeler" RegisterServer.bat
위 명령어를 실행 후 DA# Macro를 다시 실행하여 비정상 현상이 해결되었는지 확인한다.
3.2.3. Reverse 정보 수집 SQL(Oracle용)
Oracle Database에서 Reverse에 필요한 Table, Column, FK 목록을 수집하는 SQL 예시이다.
이 SQL을 실행하려면 “SELECT ANY DICTIONARY” 권한이 필요하다.
-- TABLE 목록 SELECT ROW_NUMBER() OVER(ORDER BY A.OWNER, A.TABLE_NAME) AS RNO ,'모델1' AS 모델명, '주제영역1' AS 주제영역명, '그룹1' AS 엔터티그룹명 ,CASE WHEN INSTR(T.COMMENTS, CHR(10)) > 0 THEN A.TABLE_NAME -- COMMENT에 행분리 문자가 있는 경우 엔터티명 부적합하여 테이블명으로 사용 WHEN T.COMMENTS IS NOT NULL THEN T.COMMENTS ELSE A.TABLE_NAME END AS 엔터티명 ,A.TABLE_NAME AS 테이블명 ,TRIM(TO_CHAR(A.NUM_ROWS, '9,999,999,999,999')) AS 동의어 -- 동의어에 총건수를 문자형으로 추출(comma 포함) ,A.TABLE_NAME AS 보조명 ,A.OWNER AS DBOWNER ,NULL AS 분류, NULL AS "LEVEL", NULL AS 단계, NULL AS 유형, NULL AS 표준화, NULL AS 상태, NULL AS 발생주기, NULL AS 월간발생량, NULL AS "보존기한(월)" ,A.NUM_ROWS 총건수 ,T.COMMENTS AS 정의 ,NULL AS 데이터처리형태, NULL AS 특이사항, NULL AS Note, NULL AS TAG ,O.CREATED, O.LAST_DDL_TIME, A.LAST_ANALYZED, A.TEMPORARY ,'[COMMENT]: ' || T.COMMENTS || CHR(13) || CHR(10) || '[NUM_ROWS]: ' || TRIM(TO_CHAR(A.NUM_ROWS, '9,999,999,999,999')) || CHR(13) || CHR(10) || '[CREATED]: ' || TO_CHAR(O.CREATED, 'YYYY-MM-DD HH24:MI:SS') || CHR(13) || CHR(10) || '[LAST_DDL_TIME]: ' || TO_CHAR(O.LAST_DDL_TIME, 'YYYY-MM-DD HH24:MI:SS') || CHR(13) || CHR(10) || '[LAST_ANALYZED]: ' || TO_CHAR(A.LAST_ANALYZED, 'YYYY-MM-DD HH24:MI:SS') AS 정의2 FROM DBA_TABLES A INNER JOIN DBA_OBJECTS O ON ( A.OWNER = O.OWNER AND A.TABLE_NAME = O.OBJECT_NAME AND O.OBJECT_TYPE = 'TABLE') LEFT OUTER JOIN DBA_TAB_COMMENTS T ON ( A.OWNER = T.OWNER AND A.TABLE_NAME = T.TABLE_NAME ) WHERE 1=1 AND A.TABLE_NAME NOT LIKE 'BIN$%' AND A.OWNER IN ('OWNER1', 'OWNER2') -- 해당 OWNER 지정 -- AND A.TABLE_NAME = 'TABLE_NAME' -- 특정 TABLE만 포함 또는 제외 ORDER BY A.OWNER, A.TABLE_NAME ; -- COLUMN 목록 WITH WC AS ( SELECT A.OWNER, A.TABLE_NAME, A.COLUMN_NAME, A.COLUMN_ID, A.DATA_TYPE ,CASE WHEN A.DATA_TYPE= 'NUMBER' AND A.DATA_SCALE > 0 THEN A.DATA_PRECISION||','||A.DATA_SCALE WHEN A.DATA_TYPE= 'NUMBER' AND A.DATA_SCALE = 0 THEN TO_CHAR(A.DATA_PRECISION) WHEN A.DATA_TYPE= 'NUMBER' AND A.DATA_SCALE IS NULL THEN '' WHEN A.DATA_TYPE IN ('DATE','TIMESTAMP','BLOB', 'CLOB') THEN NULL WHEN A.DATA_TYPE LIKE 'TIMESTAMP%' THEN NULL ELSE TO_CHAR(A.DATA_LENGTH) END AS DATA_LENGTH ,A.DATA_PRECISION, A.DATA_SCALE ,DECODE(A.NULLABLE, 'Y','N','Y') AS NOT_NULL ,DECODE(B.COLUMN_NAME, NULL, 'N', 'Y') PRI_KEY ,B.POSITION PK_POSITION ,T.COMMENTS ,A.DEFAULT_LENGTH -- ,A.DATA_DEFAULT ,CASE WHEN A.DEFAULT_LENGTH IS NULL THEN NULL ELSE EXTRACTVALUE ( DBMS_XMLGEN.GETXMLTYPE ( 'SELECT DATA_DEFAULT FROM DBA_TAB_COLUMNS WHERE OWNER = ''' || A.OWNER || ''' AND TABLE_NAME = ''' || A.TABLE_NAME || ''' AND COLUMN_NAME = ''' || A.COLUMN_NAME || '''' ) , '//text()' ) END AS DATA_DEFAULT ,A.LAST_ANALYZED, A.NUM_DISTINCT -- ,A.LOW_VALUE ,DECODE(DATA_TYPE ,'NUMBER' ,TO_CHAR(UTL_RAW.CAST_TO_NUMBER(LOW_VALUE)) ,'VARCHAR2' ,TO_SINGLE_BYTE(UTL_RAW.CAST_TO_VARCHAR2(LOW_VALUE)) ,'CHAR' ,TO_SINGLE_BYTE(UTL_RAW.CAST_TO_VARCHAR2(LOW_VALUE)) ,'NVARCHAR2' ,TO_CHAR(UTL_RAW.CAST_TO_NVARCHAR2(LOW_VALUE)) ,'BINARY_DOUBLE',TO_CHAR(UTL_RAW.CAST_TO_BINARY_DOUBLE(LOW_VALUE)) ,'BINARY_FLOAT' ,TO_CHAR(UTL_RAW.CAST_TO_BINARY_FLOAT(LOW_VALUE)) ,'DATE',DECODE(LOW_VALUE, NULL, NULL, TO_CHAR(1780+TO_NUMBER(SUBSTR(LOW_VALUE,1,2),'XX') +TO_NUMBER(SUBSTR(LOW_VALUE,3,2),'XX'))||'-' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(LOW_VALUE,5,2), 'XX'), '00'))||'-' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(LOW_VALUE,7,2), 'XX'), '00'))||' ' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(LOW_VALUE,9,2),'XX')-1, '00'))||':' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(LOW_VALUE,11,2),'XX')-1, '00'))||':' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(LOW_VALUE,13,2),'XX')-1, '00'))) ,LOW_VALUE ) LOW_VALUE -- ,A.HIGH_VALUE ,DECODE(DATA_TYPE ,'NUMBER' ,TO_CHAR(UTL_RAW.CAST_TO_NUMBER(HIGH_VALUE)) ,'VARCHAR2' ,TO_SINGLE_BYTE(UTL_RAW.CAST_TO_VARCHAR2(HIGH_VALUE)) ,'CHAR' ,TO_SINGLE_BYTE(UTL_RAW.CAST_TO_VARCHAR2(HIGH_VALUE)) ,'NVARCHAR2' ,TO_CHAR(UTL_RAW.CAST_TO_NVARCHAR2(HIGH_VALUE)) ,'BINARY_DOUBLE',TO_CHAR(UTL_RAW.CAST_TO_BINARY_DOUBLE(HIGH_VALUE)) ,'BINARY_FLOAT' ,TO_CHAR(UTL_RAW.CAST_TO_BINARY_FLOAT(HIGH_VALUE)) ,'DATE',DECODE(HIGH_VALUE, NULL, NULL, TO_CHAR(1780+TO_NUMBER(SUBSTR(HIGH_VALUE,1,2),'XX') +TO_NUMBER(SUBSTR(HIGH_VALUE,3,2),'XX'))||'-' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(HIGH_VALUE,5,2), 'XX'), '00'))||'-' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(HIGH_VALUE,7,2), 'XX'), '00'))||' ' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(HIGH_VALUE,9,2),'XX')-1, '00'))||':' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(HIGH_VALUE,11,2),'XX')-1, '00'))||':' ||TRIM(TO_CHAR(TO_NUMBER(SUBSTR(HIGH_VALUE,13,2),'XX')-1, '00'))) ,HIGH_VALUE ) HIGH_VALUE ,TB.NUM_ROWS, A.NUM_NULLS, A.CHAR_USED, A.AVG_COL_LEN FROM DBA_TABLES TB LEFT OUTER JOIN DBA_TAB_COLUMNS A ON (TB.OWNER = A.OWNER AND TB.TABLE_NAME = A.TABLE_NAME) LEFT OUTER JOIN (SELECT C.OWNER, C.TABLE_NAME, C.COLUMN_NAME, C.POSITION FROM DBA_CONS_COLUMNS C INNER JOIN DBA_CONSTRAINTS S ON ( C.OWNER = S.OWNER AND C.TABLE_NAME = S.TABLE_NAME AND C.CONSTRAINT_NAME = S.CONSTRAINT_NAME ) WHERE S.CONSTRAINT_TYPE = 'P' ) B ON ( A.OWNER = B.OWNER AND A.TABLE_NAME = B.TABLE_NAME AND A.COLUMN_NAME = B.COLUMN_NAME ) LEFT OUTER JOIN ALL_COL_COMMENTS T ON ( T.OWNER = A.OWNER AND T.TABLE_NAME = A.TABLE_NAME AND T.COLUMN_NAME = A.COLUMN_NAME ) WHERE 1=1 AND A.OWNER IN ('OWNER1', 'OWNER2') -- 해당 OWNER 지정 AND A.TABLE_NAME NOT LIKE 'BIN$%' AND NOT EXISTS ( SELECT 'X' -- View column 제외 조건 FROM DBA_VIEWS V WHERE V.OWNER = A.OWNER AND V.VIEW_NAME = A.TABLE_NAME ) --AND A.TABLE_NAME = 'TABLE_NAME' -- ORDER BY OWNER, TABLE_NAME, COLUMN_ID ) SELECT ROW_NUMBER() OVER(ORDER BY OWNER, TABLE_NAME, COLUMN_ID) AS RNO -- ,ROW_NUMBER() OVER(PARTITION BY OWNER, TABLE_NAME ORDER BY COLUMN_ID) AS COLNO ,'모델1' AS 모델명 ,'' AS 엔터티명 ,CASE WHEN INSTR(COMMENTS, CHR(10)) > 0 THEN COLUMN_NAME -- COMMENT에 행분리 문자가 있는 경우 속성명 부적합하여 컬럼명으로 사용 WHEN COMMENTS IS NOT NULL THEN COMMENTS ELSE COLUMN_NAME END AS 속성명 ,TABLE_NAME AS 테이블명 ,COLUMN_NAME AS 컬럼명 ,COMMENTS AS 정의 ,COLUMN_NAME AS 보조명 ,COLUMN_NAME AS 동의어 ,TABLE_NAME AS Reverse테이블명 ,COLUMN_NAME AS Reverse컬럼명 ,DATA_TYPE AS ReverseType ,DATA_LENGTH AS ReverseLENGTH ,PRI_KEY AS PK ,NOT_NULL AS NOTNULL ,NULL AS 유형 ,DATA_TYPE AS 데이터타입 ,DATA_PRECISION AS 길이 ,DATA_SCALE AS 소수점 ,DATA_DEFAULT AS 기본값 ,NULL AS 기본값, NULL AS 도메인, NULL AS "FK", NULL AS 핵심속성여부, NULL AS 본질식별자여부 ,NULL AS 보조식별자여부, NULL AS 표준동기화여부, NULL AS 비상속여부, NULL AS 표준화 ,NULL AS 정보보호여부, NULL AS 정보보호등급, NULL AS 암호화여부, NULL AS 스크램블 ,'[COMMENT]: ' || COMMENTS || CHR(13) || CHR(10) || '[NUM_ROWS]: ' || TRIM(TO_CHAR(NUM_ROWS, '9,999,999,999,999')) || CHR(13) || CHR(10) || '[NUM_DISTINCT]: ' || TRIM(TO_CHAR(NUM_DISTINCT, '9,999,999,999,999')) || CHR(13) || CHR(10) || '[NUM_NULLS]: ' || TRIM(TO_CHAR(NUM_NULLS, '9,999,999,999,999')) || CHR(13) || CHR(10) || '[NULL%] : ' || DECODE(NVL(NUM_ROWS, 0), 0, 0, ROUND(NUM_NULLS / NUM_ROWS, 5) * 100) || '%' || CHR(13) || CHR(10) || '[MIN_VALUE]: ' || LOW_VALUE || CHR(13) || CHR(10) || '[MAX_VALUE]: ' || HIGH_VALUE AS 정의2 -- ,WC.* FROM WC WHERE NUM_NULLS > 0 ORDER BY OWNER, TABLE_NAME, COLUMN_ID ; -- FK 목록 WITH WFK AS ( SELECT DISTINCT C2.OWNER AS P_OWNER ,C2.TABLE_NAME P_TABLE_NAME ,LISTAGG (C2.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY C2.POSITION) OVER ( PARTITION BY C1.OWNER, C1.TABLE_NAME, C1.CONSTRAINT_NAME, C2.OWNER, C2.TABLE_NAME) AS P_COLUMN_LIST ,C1.OWNER AS C_OWNER ,C1.TABLE_NAME AS C_TABLE_NAME ,C1.CONSTRAINT_NAME AS C_CONSTRAINT_NAME FROM DBA_CONSTRAINTS C1 INNER JOIN DBA_CONS_COLUMNS C2 ON (C1.R_CONSTRAINT_NAME = C2.CONSTRAINT_NAME AND C1.R_OWNER = C2.OWNER) WHERE C1.OWNER IN ('OWNER1', 'OWNER2') -- 해당 DB의 테이블 OWNER 지정 AND C1.CONSTRAINT_TYPE = 'R' ORDER BY C2.OWNER, C2.TABLE_NAME ) SELECT '모델1' AS 모델명 ,P_TABLE_NAME AS 부모엔터티명 ,P_TABLE_NAME AS 부모테이블명 ,C_TABLE_NAME AS 자식엔터티명 ,C_TABLE_NAME AS 자식테이블명 ,P_TABLE_NAME || '->' || C_TABLE_NAME AS 관계명 ,NULL AS 정의, NULL AS 관계유형, NULL AS 기수성, NULL AS 선택성, NULL AS 식별성 ,NULL AS 부모엔터티관계동사, NULL AS 자식엔터티관계동사 FROM WFK ;
위 SQL 사용 시 다음 내용을 참고한다.
- 대상 Owner(Schema)를 확인하고 수정한다. (32, 116, 179 행)
- Table의 정의를 Comment로 지정(15행)할지, 여러 column을 취합한 값으로 지정(18~22행)할지 선택한다.
- Column의 정의에는 Oracle의 통계정보를 활용하여 다음 정보를 포함하도록 추출한다.
- COMMENT: Column coment
- NUM_ROWS: Table Row count
- NUM_DISTINCT: 중복을 제거한 값의 수
- NUM_NULLS: 해당 Column의 값이 NULL인 Row count
- NULL%: (NUM_NULLS / NUM_ROWS) * 100 으로 계산한 전체 Row 중 Null인 Row의 비율
- MIN_VALUE: 해당 Column의 최소값 (DBA_TAB_COLUMNS.LOW_VALUE 에서 DATA_TYPE에 따라 추출)
- MAX_VALUE: 해당 Column의 최대값 (DBA_TAB_COLUMNS.HIGH_VALUE 에서 DATA_TYPE에 따라 추출)
- Column의 MIN_VALUE, MAX_VALUE는 다음 내용을 참고한다. (63~78, 80~95행)
- ORA-29275: partial multibyte character (부분 다중 바이트 문자) 오류가 발생한다면, to_single_byte 함수를 적용해 보고, 그래도 오류가 발생한다면 최대, 최소값은 제외하고 추출한다.
- 최대, 최소값을 제외하고 추출하는 경우 Column 정의 내용(158, 159행)도 함께 수정한다.
- SQL을 실행할 수 있는 계정, 접속정보, 권한을 얻기까지 시간이 오래 걸리거나 불가능한 경우는 DBA 또는 IT운영자에게 해당 SQL을 전달하고 실행결과를 엑셀파일로 저장해서 회신 요청한다.
- SQL 세 개를 실행한 결과는 세 개의 파일(각 파일에 시트 한 개)로 받아도 되고, 하나의 파일(시트 세 개)로 받아도 된다.
4. DA# Macro 다운로드, 향후 추가 예정 기능, 일러두기
4.1. DA# Macro 다운로드
아래 github repository에서 확인할 수 있다.
https://github.com/DAToolset/DA-Macro
또는, 이 URL에서 직접 다운로드할 수 있다.
https://github.com/DAToolset/DA-Macro/raw/main/DA%23%20Macro_v2.12_20210814.xlsm
4.2. DA# Macro 향후 추가 예정 기능
DA# Macro는 현재 구현되어 있는 기능 외에 다음 기능을 추가할 예정이다.
- Subtype 속성 Get/Set: Subtype 내에 관리되는 속성 Get/Set (계층 속성 포함)
- AR 모델 Get/Set: AR 모델을 Treeview로 조회하고 여러 모델을 선택하여 Get/Set
- Relationship Get/Set: 엔터티 간 Relationship Get/Set
- 엔터티 배치 방법 확장
4.3. 사용자 일러두기
버그, 기능 개선, 추가 기능 등에 대한 요구사항은 블로그 댓글 또는 이메일로 보내주기 바란다. 다만, 이 도구는 어디까지나 취미로 개발하고 있으므로 요구사항에 대한 빠른 대응은 할 수 없다. 여유시간이 있을 때, 해당 기능이 필요할 때 반영하고 배포하겠다. 배포 주기도 규칙적이지 않다.
소스코드를 함께 제공하므로 필요한 기능이 있다면 반영하고 다시 나에게 공유해 주면 대단히 감사하겠다.
지금까지 DA# Macro에 대한 사용 방법에 대하여 모두 알아보았다. 다음에는 DA# Macro의 소스코드에 대해 살펴보겠다.
<< 관련 글 목록 >>
- DA# Macro(1): DA#, DA# API, DA# Macro (매크로) 개요
- DA# Macro(2): DA# Macro(매크로) 기능(1)-공통기능, Entity Get/Set
- DA# Macro(3): DA# Macro(매크로) 기능(2)-Attribute Get/Set
- DA# Macro(4): DA# Macro(매크로) 기능(3)-Reverse
- DA# Macro(5): 사용상 주의사항/참고사항, 다운로드, 향후 추가 예정 기능, 일러두기
- DA# Macro(6): DA# Modeler API
- DA# Macro 기능 시연 영상 (YouTube)
- DA# Macro 설명글 목차 , 다운로드