여행을 개발하다

[Oracle] XMLELEMENT, XMLAGG 함수 - 다수 레코드 1열로 가져오기 본문

데이터베이스/Oracle

[Oracle] XMLELEMENT, XMLAGG 함수 - 다수 레코드 1열로 가져오기

yhtragramming 2020. 3. 7. 19:01

 

이번 포스팅에서는 오라클의 XMLELEMENT, XMLAGG 함수에 대해 이야기하고자 한다.

하지만 각 함수의 고유한 기능보다는, 두 개의 함수를 조합한 '활용'에 더 초점을 맞추려고 한다.

 

* Oracle 10g 버전 이상부터XMLAGG, WM_CONCAT 함수를 조합하면 보다 간단하게 사용 가능하다.

 

먼저 두 함수의 정의에 대해 알아본다.

 

1. XMLELEMENT

- 정의 : 반환되는 레코드를 XML 요소로 바꾸어준다.

- 함수 인수 :XMLELEMENT([태그명], 레코드명)

 

함수의 이름 자체가 멀리서 보면 가독성이 떨어진다. 하지만 자세히 보면'XML + ELEMENT = XMLELEMENT'이다.

 

첫 번째 인수에는 XML의 요소로 사용할 태그를 지정하고, 그 뒤에는 SELECT할 필드명을 적어준다.태그명은 옵션 항목으로, 별도로 지정하지 않으면 레코드명 자체가 XML 요소로 반환된다. 따라서, FROM 절 뒤에 나오는 테이블의 레코드는 철저하게 무시된다.

예1) APPROVER 테이블의 APPR_USER 컬럼을 XML <A> 요소로 리턴한다.

SELECT  XMLELEMENT(A, APPR_USER) 
	 FROM APPROVER; 
예1 결과)

XMLELEMENT(A,APPR_USER)
------------------------- 
<A>2012010111</A>
<A>2012121202</A>
<A>2012050308</A>
<A>2013010101</A>
<A>2012050310</A>
<A>2011030402</A>
<A>2013032102</A>
예2) XML 요소를 지정하지 않고, APPROVER 테이블의 APPR_USER 컬럼을 가져온다.

SELECT  XMLELEMENT(APPR_USER) 
	 FROM APPROVER; 
예2 결과) APPROVER 테이블의 레코드는 무시되며, 해당 테이블의 레코드 수만큼의 XML 요소가 나타난다.

XMLELEMENT(APPR_USER)
----------------------
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>
<APPR_USER></APPR_USER>

그래서 특정 테이블의 컬럼이 아닌DUAL 테이블을 활용하여, XMLELEMENT 함수 인수 그 자체를 XML 요소로 반환받을 수도 있다.

예3) DUAL 테이블의 활용

SELECT XMLELEMENT(APPR_USER) 
     FROM DUAL;
예3 결과)

XMLAGG(XMLELEMENT(APPR_USER))
------------------------------
<APPR_USER></APPR_USER>

 

2. XMLAGG

- 정의 : XML 요소들로 이루어진 다수의 레코드들을 한 줄로 이어서 표현한다.

- 함수 인수 : XMLAGG(XML 요소 레코드)

 

'XML + AGG(aggregate) = XMLAGG'로, XML 요소들을 집합화한다는 뜻이다.XML 형식의 레코드를 하나의 ROW로 SELECT 해오기 때문에, XMLELEMENT와 적절히 결합하여 사용할 수 있다.

예1) APPROVER 테이블의 APPR_USER 컬럼을 XML <A> 요소로 감싸, 1개의 ROW로 리턴한다.

SELECT   XMLAGG(XMLELEMENT(A, APPR_USER)) 
	  FROM APPROVER; 
예1 결과)

XMLAGG(XMLELEMENT(A,APPR_USER))
--------------------------------
<A>2012010111</A><A>2012121202</A><A>2012050308</A><A>2013010101</A>...<A>2013032102</A>

 

3. 활용 예제 

(결재 건에 포함된 다수의 결재권자들을 ','로 이어 1개의 ROW로 가져오기)

 

한 회사 프로젝트에서 다음과 같은 요구 사항을 받았는데, XMLELEMENT와 XMLAGG 함수를 활용하여 쉽게 문제를 해결할 수 있었다.

 

어떤 부서에서 품의를 올리려고 한다.
기안자는 품의 내용을 작성하여, 결재권자에게 상신한다.
기안자의 품의 내용은 1건이지만, 결재권자는 여러 명이 될 수도 있다.
이때, 사용자에게 품의 내용 1건당, 결재권자들을 한 row로 표현하여 보여줘야 한다.

 

간단히 말해, 기안자가 기안서 작성 후 결재권자에게 상신하는 워크플로 인터페이스를 개발하는 작업이었다. 보안상 관련된 테이블을 구조를 상세하게 설명할 수는 없지만, 대략적으로 다음과 같은 테이블들을 사용했다.

 

1. APPR_MST (결재 건들을 저장하는 마스터성 테이블)
2. APPROVER (APPR_MST의 결재 건들에 대한 결재권자들의 정보를 저장하는 테이블)
3. INSA (회사의 전 직원 정보를 담고 있는 테이블)
1) TABLE APPR_MST

TABLE_NAME	COLUMN_NAME	 DATA_TYPE	 NULLABLE	 IS_PK	           COMMENTS
---------- ------------ ----------  ----------  ------  ----------------------------
APPR_MST	APPR_SEQ	 NUMBER		               Y	   결재건 시퀀스
APPR_MST	START_DT	   DATE	        Y                  결재 요청일
APPR_MST	END_DT	       DATE	        Y		           모든 결재권자의 승인 완료일
APPR_MST	REG_DT	       DATE	        Y                  등록일
APPR_MST	REG_USER	VARCHAR2(10)	Y				   등록자
APPR_MST	UPT_USER	VARCHAR2(10)	Y                  수정일
APPR_MST	UPT_DT	      DATE	        Y 		           수정자	
2) TABLE APPROVER 

TABLE_NAME	COLUMN_NAME	 DATA_TYPE	NULLABLE	IS_PK	    COMMENTS
----------  -----------  --------- ----------  -------    ---------------
APPROVER	APPR_SEQ	  NUMBER		         Y	       결재건 시퀀스
APPROVER	APPROVER_SEQ  NUMBER		         Y	       결재권자 시퀀스
APPROVER	UPT_DT	       DATE         Y	                   수정일
APPROVER	REG_USER	VARCHAR2(10)	Y		               등록자
APPROVER	REG_DT	        DATE	    Y		               등록일
APPROVER	APPR_USER	VARCHAR2(10)	Y		              결재권자
APPROVER	UPT_USER	VARCHAR2(10)	Y		               수정자
3) TABLE INSA 

TABLE_NAME	  COLUMN_NAME 	 DATA_TYPE	  NULLABLE 	 IS_PK	   COMMENTS
------------ -------------  -----------  ---------  ------- -----------------
INSA	          SABUN	    VARCHAR2(10)		       Y		 사번
INSA	          NAME	    VARCHAR2(100)			             이름

INSA 테이블은 단순히 사번을 가지고 이름을 조회하는 용도로만 사용될 예정이기에 배제하고, 아래 그림과 같이 APPR_MST 테이블과 APPROVER 테이블의 관계를 살펴보면 1 : N 관계임을 알 수 있다. 결재 1건당 다수의 결재자가 있을 수 있기 때문이다.

이를 다음 형식으로 SELECT 해오고자 한다.

결재번호     기안자                  결재자                       결재요청일     결재승인일 
--------  ---------  ------------------------------------       -----------   -----------
    1       정영훈      결재권자1, 결재권자2, ..., 결재권자N        20200301       20200307

 

* 결재 건 테이블 (APPR_MST) 데이터(날짜 형식은 TO_CHAR 함수를 통해 가독성 있게 가져옴)

SELECT  *
  FROM APPR_MST;
APPR_SEQ	START_DT	 END_DT	     REG_DT	     REG_USER	  UPT_DT	 UPT_USER
---------  -----------  --------   ---------    ----------  --------   --------------
  1	        20200303	20200310	20200303	2020011901	20200303	2020011901
  2	        20200307	20200314	20200307	2012010108	20200307	2012010108

 

* 승인자 (APPROVER) 데이터(날짜 형식은 TO_CHAR 함수를 통해 가독성 있게 가져옴)

SELECT  *
  FROM APPROVER;
APPR_SEQ	APPROVER_SEQ	APPR_USER	  REG_DT	  REG_USER	  UPT_DT	  UPT_USER
--------   --------------  -----------  ---------    ----------  --------  --------------
  1         	1	       2012010111	20200307	2020011901	20200307	2020011901
  1	            2	       2012121202	20200307	2020011901	20200307	2020011901
  1	            3	       2012050308	20200307	2020011901	20200307	2020011901
  2	            1	       2013010101	20200307	2012010108	20200307	2012010108
  2	            2	       2012050310	20200307	2012010108	20200307	2012010108
  2	            3	       2011030402	20200307	2012010108	20200307	2012010108
  2	            4	       2013032102	20200307	2012010108	20200307	2012010108

이에 결재건 시퀀스를 변수로 받는 전체 SQL을 작성해본다.

SELECT   AM.APPR_SEQ AS 결재번호,
	     (SELECT INSA.NAME
	        FROM INSA INSA
	       WHERE AM.REG_USER = INSA.SABUN) AS  기안자,
	     SUBSTR((  SELECT XMLAGG(XMLELEMENT(A, ',' || (SELECT INSA.NAME
	                                                     FROM INSA INSA
	                                                    WHERE APPR.APPR_USER = INSA.SABUN)   
	                                        )
	                             ).EXTRACT('//text()').GETSTRINGVAL() 
	                  FROM  APPROVER APPR
	                  WHERE AM.APPR_SEQ = APPR_SEQ    	          
	           ),2) AS 결재자,           
	     TO_CHAR(AM.START_DT, 'YYYYYMMDD') AS 결재요청일,
	     TO_CHAR(AM.END_DT,   'YYYYMMDD')  AS 결재승인일
	          
		 FROM APPR_MST AM;
		
		 --WHERE AM.APPR_SEQ = 1;
결재번호	    기안자	            결재자	            결재요청일     결재승인일
---------  --------      ----------------------    -----------  -------------  
  1	        정영훈	       유지연,김주영,이태경	    202000303	   20200310
  2	        구인철	    최민석,김동우,전지희,남지영	202000307	   20200314

 

그중 XML 함수가 사용된 부분만 뜯어본다.

 

 SUBSTR((  SELECT XMLAGG(XMLELEMENT(A, ',' || (SELECT INSA.NAME
	                                                     FROM INSA INSA
	                                                    WHERE APPR.APPR_USER = INSA.SABUN)   
	                                        )
	                             ).EXTRACT('//text()').GETSTRINGVAL() 
	         FROM  APPROVER APPR
	        WHERE  AM.APPR_SEQ = APPR_SEQ    	          
	     ),2) AS 결재자

 

1단계)

· APPROVER 테이블의 APPR_USER 컬럼을 SELECT 해오되, <A> 태그를 붙여서 가져온다.

· 단, 하나의 ROW에는 APPR_MST 테이블의 APPR_SEQ와 APPORVER의 APPR_SEQ가 일치하는 레코드들만 가져온다.

· 또한, 쉼표(,)로 구분해서 가져오기로 했으므로, 레코드 앞에 ',' || 를 추가한다.

SELECT  (SELECT XMLAGG(XMLELEMENT(A, ',' || APPR_USER ))
	             FROM  APPROVER APPR
	             WHERE AM.APPR_SEQ = APPR_SEQ    	          
	     ) AS 결재자	                 
	        	          
		FROM APPR_MST AM;
                               결재자
------------------------------------------------------------------------- 
<A>,2012010111</A><A>,2012121202</A><A>,2012050308</A>
<A>,2013010101</A><A>,2012050310</A><A>,2011030402</A><A>,2013032102</A>

 

2단계)

· 하지만 사번만으로는 누가 누군지 구분할 수 없다.

· 따라서, INSA 테이블에서 해당 사번에 해당하는 이름을 서브 쿼리로 다시 가져온다.

SELECT  (SELECT XMLAGG(XMLELEMENT(A, ',' || ( SELECT  INSA.NAME
		                                              FROM  INSA INSA
		                                              WHERE APPR.APPR_USER = INSA.SABUN	
		                                     ) 
		                          )
		               )
	      FROM  APPROVER APPR
	      WHERE AM.APPR_SEQ = APPR_SEQ    	          
	     ) AS 결재자	                 
	        	          
		FROM APPR_MST AM;
                     결재자
-----------------------------------------------------
<A>,유지연</A><A>,김주영</A><A>,이태경</A>
<A>,최민석</A><A>,김동우</A><A>,전지희</A><A>,남지영</A>

 

3단계)

· 사실상 필요 없는 XML 태그를'EXTRACT('//text()')' 함수로 제거한다.

· 운영 환경의 상황에 따라 한글이 깨질 수 있다는 것을 염두,'GETSTRINGVAL()'함수로 완벽하게 추출해 준다.

SELECT  (SELECT XMLAGG(XMLELEMENT(A, ',' || ( SELECT  INSA.NAME
		                                              FROM  INSA INSA
		                                              WHERE APPR.APPR_USER = INSA.SABUN	
		                                     ) 
		                         )
		               ).EXTRACT('//text()').GETSTRINGVAL()
	      FROM  APPROVER APPR
	      WHERE AM.APPR_SEQ = APPR_SEQ    	          
	     ) AS 결재자	                 
	        	          
		FROM APPR_MST AM;
          결재자
-------------------------
,유지연,김주영,이태경
,최민석,김동우,전지희,남지영

 

4단계)

가장 앞에 있는 콤마(,)를 'SUBSTR' 함수로 제거해 준다.

SELECT  SUBSTR((SELECT XMLAGG(XMLELEMENT(A, ',' || ( SELECT  INSA.NAME
		                                                FROM  INSA INSA
		                                                WHERE APPR.APPR_USER = INSA.SABUN	
		                                            ) 
		                                  )
		                        ).EXTRACT('//text()').GETSTRINGVAL()
	                    FROM  APPROVER APPR
	                    WHERE AM.APPR_SEQ = APPR_SEQ    	          
	            ),2) AS 결재자	                 
	        	          
		FROM APPR_MST AM;
        결재자
-------------------------
유지연,김주영,이태경
최민석,김동우,전지희,남지영

 

지금까지 오라클의 XMLELEMENT, XMLAGG 함수의 정의 및 활용에 대해 알아보았다.

 

긴 글 읽어주셔서 감사하며, 수정사항이나 개선이 필요한 부분이 있으면 언제든지 댓글 환영합니다 : )

'데이터베이스 > Oracle' 카테고리의 다른 글

[Oracle] 계층형 쿼리 - START WITH ~ CONNECT BY 구문  (1) 2020.03.08
[Oracle] Data Dictionary  (0) 2020.02.09
Comments