* 쿼리 사용하는 케이스 가정
Apparel ERP시스템에서 아래의 테이블들을 가지고
각 주문의 스타일/컬러별 사이즈를 행이 아닌 열의 형태로
동적으로 표현하려고 한다.
1) 발주확정 전 샘플스타일 테이블
2) 발주확정 후 스타일 테이블
3) 스타일별 사이즈 정보 테이블
4) 주문테이블
(주문seq에 따라 스타일, 컬러, 사이즈 컬럼을 가진 테이블)
원하는 결과 구조
주문정보 | 샘플스타일 | 스타일 | 사이즈1 | 사이즈2 | ... | 사이즈N |
스타일1 | (사이즈1의 수량) | (사이즈N의 수량) | ||||
샘플스타일1 | (사이즈2의 수량) | (사이즈N의 수량) | ||||
스타일2 | (사이즈1의 수량) | (사이즈2의 수량) |
(방법 1) PIVOT 사용
<select id=“~~” resultType=“HashMap”>
DECLARE @COLUMNS NVARCHAR(MAX)
DECLARE @SQL NVARCHAR(MAX)
SET @COLUMNS = ‘’
/* 스타일별 사이즈를 뽑아내어 PIVOT항목으로 사용하기 위한 쿼리 */
SELECT @COLUMNS = @COLUMNS + ‘[‘ + SIZECD + ‘],’
FROM (
SELECT DISTINCT A.SIZE_CD
FROM 사이즈테이블 A
JOIN 스타일테이블 B
ON (join 조건)
WHERE B.STYLE_CD IN (java단에서 넘긴 스타일 목록)
UNION
SELECT DISTINCT A.SIZE_CD
FROM 사이즈테이블 A
JOIN 샘플스타일테이블 B
ON (join 조건)
WHERE B.SMPL_STYLE_CD IN (java단에서 넘긴 샘플스타일 목록)
) AS SIZES
ORDER BY SIZE_CD
/* 1) 만약 사이즈 목록이 비어있다면 PIVOT쿼리에서 오류가 발생하므로
사이즈 목록이 없는 경우에 대해 바로 종료할 수 있는 방어코드
2) 사이즈 목록이 존재한다면 마지막 , 문자를 삭제하는 LEFT함수 처리 */
SELECT @COLUMNS = CASE WHEN @COLUMNS = ‘’ THEN ‘NaN’
ELSE LEFT(@COLUMNS, LEN(@COLUMNS)-1) END
SET @SQL = ‘SELECT A.*
FROM (
SELECT M.주문정보(주문차수 등)
, M.SMPL_STYLE_CD
, M.STYLE_CD
, M.SIZE_CD AS sizeCd
, M.REQ_QTY AS reqQty
FROM 주문테이블 M
WHERE M.BRND_CD = ‘’’ + #{변수} + ‘’‘
AND M.ORDER_DT = REPLACE(‘ + #{주문일자} + ‘,’’-‘’, ‘’’’)
AND M.ORDER_SN = ‘ + #{주문차수} + ’
) AS RESULT
PIVOT (
SUM(reqQty) FOR sizeCd IN (‘ + @COLUMNS +’)
) AS PIVOT_RESULT’
EXEC(@SQL)
</select>
쿼리 중간중간에 코멘트를 달았지만
추가로 더 자세하게 작성하면
PIVOT의 경우 사용할 컬럼명을 [사이즈1], [사이즈2]…의 형태로 쿼리에 넣어주어야 하므로
이를 동적으로 만들기 위해
DECLARE문으로
@COLUMNS : ‘[사이즈1], [사이즈2]…’ 의 형태로 텍스트로 만들 변수
@SQL : EXEC함수에 넣어 동적으로 실행할 PIVOT쿼리가 저장되는 변수
@COLUMNS 변수에
스타일/샘플스타일들의 모든 사이즈 목록을
‘[사이즈1], [사이즈2]…’ 이 형태로 추출하여 저장한다
만약 데이터 정합성 문제로
사이즈 정보가 전혀 없는 스타일이라면
맨아래 PIVOT 쿼리실행시 오류가 발생하기 때문에
방어로직으로 @COLUMNS이
초기 선언값인 ‘’(empty string)과 동일하다면
예외케이스로 사이즈정보가 아닐만한 텍스트(나는 NaN으로)로 임시치환하여
PIVOT 쿼리에서 오류가 발생하지 않도록 처리해준다
아래 PIVOT 쿼리에서
AND ORDER_DT = REPLACE(‘ + #{주문일자} + ‘,’’-‘’, ‘’’’)
코드블럭과 같이
single quotation이 많이 있는 이유는
@SQL 변수에 쿼리를 텍스트로 만드는 것이기 때문에
변수삽입을 위한 single quotation으로 생각해야한다
ex) 일반적인 쿼리 실행때는 이렇게 사용
AND ORDER_DT = REPLACE(#{주문일자}, ‘-‘, ‘’)
(방법 2) java단에서 사이즈 목록을 parameter에 직접 전달해준다면
WITH SIZES AS (
SELECT M.주문정보
, M.SMPL_STYLE_CD
, M.STYLE_CD
, M.SIZE_CD
, M.REQ_QTY
FROM 주문테이블 M
WHERE M.BRND_CD = #{brndCd}
AND M.ORDER_DT = REPLACE(#{주문일자}, ‘-’, ‘’)
AND M.ORDER_SN = #{주문차수}
)
SELECT A.*
FROM (
SELECT M.주문정보
, M.SMPL_STYLE_CD
, M.STYLE_CD
, M.SIZE_CD
<foreach item=“item” index=“index” collection=“sizeList”>
, SUM(M.S${index}) AS ‘${item}’
</foreach>
FROM (
SELECT M1.주문정보
, M1.SMPL_STYLE_CD
, M1.STYLE_CD
<foreach item=“i” index=“index” collection”sizeList”>
, CASE WHEN SZ.SIZE_CD = #{i} THEN SZ.REQ_QTY END AS ‘S${index}’
</foreach>
FROM 주문테이블 M1
LEFT JOIN SIZES SZ /*주문 수량이 존재하는 스타일별 사이즈 목록 */
ON (join 조건)
WHERE M1.BRND_CD = #{brndCd}
AND M1.ORDER_DT = REPLACE(#{주문일자}, ‘-’, ‘’)
AND M1.ORDER_SN = #{주문차수}
) M
GROUP BY M.주문정보, M.SMPL_STYLE_CD, M.STYLE_CD
) A
사이즈목록이 숫자, 특수문자(ex 01(XS), 02(S), 03(M) ~ ) 등의 여러 케이스가 있기때문에
S를 붙여서 텍스트로 만들어주거나
리스트에서 항목값 자체를 텍스트로 사용하는
${item} 등을 사용하였다.
PIVOT 처럼 행을 열로 변경하기 위해
SizeList 를 foreach 문으로 반복하면서
CASE 문을 사용하면 PIVOT 과 동일한 형태의 결과를 얻을 수 있다.
나는 내가 사용할 컬럼리스트를 만든다는 관점에서 PIVOT이 더 직관적으로 느껴졌으나,
확실히 동적쿼리를 사용하니
유지보수와 변수처리에서 한계가 많이 느껴졌다
ex) 조회 컬럼이 늘어나거나, 사이즈값에서 예외케이스가 발생하거나, 조회조건을 추가할 때
MSSQL에서는 어떤 부분에서 에러가 났는지 정확히 파악이 어려웠다,,,,
처음에 사이즈가 단순히 신발 사이즈처럼
220, 225, 230~ 이렇게 있다고 생각했는데
중간에 발생했던 특수문자나
해외 사이즈인 소수점 사이즈(7.5, 8, 9.5 ~)
에서는 DECLARE, EXEC를 활용한 쿼리는
에러 처리로직이 많이 필요하기때문에
결국 방법2로 변경하게 되었다
물론 방법2에서도 소수점 이슈가 발생해서 ㅎ
위 코드블록에서는 생략했지만
Java단에서 sizeList를 넘길때
REPLACE(사이즈값, ‘.’, ‘@‘) 처럼 문자를 치환해서 넘겨야하고
방법2쿼리에서도 REPLACE(사이즈값, ‘.’, ‘@‘) 처리하여 비교하는 로직이 필요했지만
일단 행과 열을 변형하는 포스팅이라 생략했다
'개발자 :-) > SQL' 카테고리의 다른 글
MSSQL PL/SQL 시스템 함수 정리(@@TRANCOUNT, @@ROWCOUNT 등) (0) | 2025.02.03 |
---|---|
[ORACLE] PIVOT 동적 컬럼 사용하기(입력받은 날짜를 열로 표현) (0) | 2023.01.30 |
[SQL] 오라클 LAG, LEAD 함수 사용법(이전 행값, 다음 행값 가져오기),전일대비 구하기 (1) | 2023.01.05 |
[MySQL] 스토어드 프로시저(2) (0) | 2022.10.02 |
[MySQL] 스토어드 프로시저 (0) | 2022.10.01 |
댓글