[MySQL,MariaDB]피벗(Pivot) 사용하기
- -
관계형 테이블은 행과 열로 이루어져 있습니다.
이번 글은 테이블의 행과 열의 위치를 바꾸는 피벗에 대한 포스팅입니다.
예제는 MariaDB(MySQL 가능)로 실습하였습니다.
오라클 11g부터는 피벗 기능을 자체적으로 제공하는데 거기에 대해서는 이후에 따로 포스팅할 예정입니다.
여기 피벗을 이용하려는 테이블 두 개가 있습니다.
PM_CALENDAR 테이블에는 2021년도까지의 날짜 데이터가 입력되어 있습니다.
- DATE : DATE 타입의 날짜 데이터
- DATECHAR : VARCHAR 타입의 날짜 데이터
PM_COMMUTE 테이블은 사람들의 출, 퇴근 기록이 담겨있습니다.
여기서 사용할 필드는 USERCD, WORKDAY, TODATE, FROMDATE 입니다.
- USERCD : 사람들의 고유번호
- WORKDAY : 근태날짜
- TODATE : 출근시간
- FROMDATE : 퇴근시간
이 두 테이블을 조인해서 데이터를 보고자 합니다.
출, 퇴근 기록이 없더라도 날짜는 찍혀야하기 때문에 Outer Join을 사용합니다.
SELECT DISTINCT
B.USERCD
, B.WORKDAY
, A.DATECHAR
, B.TODATE
, B.FROMDATE
FROM PM_CALENDAR A
LEFT OUTER JOIN PM_COMMUTE B
ON A.DATECHAR = B.WORKDAY
AND B.CMPCD = 'P0001'
WHERE A.DATECHAR BETWEEN '20200420' AND '20200430'
ORDER BY A.DATECHAR
PM_CALENDAR에 Left Outer Join을 걸었고, 날짜는 테스트 데이터가 있는 20200420 부터 20200430 까지만 조회하였습니다. 결과는 정상적으로 나왔습니다.
하지만 데이터를 보게 되면, 동일한 날짜로 N명의 사람들의 기록이 보이기 때문에 뭔가 조금 아쉽죠. 피벗을 사용한다면 데이터를 아래 그림과 같이 볼 수 있게 됩니다.
피벗을 위해 IF 함수와 집계 함수를 이용하여 쿼리를 작성하였습니다.
1) 피벗 1 - PM_CALENDAR
먼저, 날짜 데이터가 있는 PM_CALENDAR 데이터를 피벗시켰습니다.
SELECT
'' AS USERCD
, MAX(IF(B.ROWNUM = '20', B.WORKDAY, 0)) AS WORKDAY20
, MAX(IF(B.ROWNUM = '21', B.WORKDAY, 0)) AS WORKDAY21
, MAX(IF(B.ROWNUM = '22', B.WORKDAY, 0)) AS WORKDAY22
, MAX(IF(B.ROWNUM = '23', B.WORKDAY, 0)) AS WORKDAY23
, MAX(IF(B.ROWNUM = '24', B.WORKDAY, 0)) AS WORKDAY24
, MAX(IF(B.ROWNUM = '25', B.WORKDAY, 0)) AS WORKDAY25
, MAX(IF(B.ROWNUM = '26', B.WORKDAY, 0)) AS WORKDAY26
, MAX(IF(B.ROWNUM = '27', B.WORKDAY, 0)) AS WORKDAY27
, MAX(IF(B.ROWNUM = '28', B.WORKDAY, 0)) AS WORKDAY28
, MAX(IF(B.ROWNUM = '29', B.WORKDAY, 0)) AS WORKDAY29
, MAX(IF(B.ROWNUM = '30', B.WORKDAY, 0)) AS WORKDAY30
, MAX(IF(B.ROWNUM = '31', B.WORKDAY, 0)) AS WORKDAY31
FROM (
SELECT
CONCAT(SUBSTRING(A.DATECHAR,5,2),'.',SUBSTRING(A.DATECHAR,7,2)) WORKDAY
, SUBSTRING(A.DATECHAR,7,2) AS ROWNUM
FROM PM_CALENDAR A
WHERE A.DATECHAR BETWEEN '20200420' AND '20200430'
) B
IF 함수와 집계 함수를 이용해 피벗을 시킬 때, 키(Key)가 되는 것은 Rownum입니다.
여기 데이터를 볼 때, (PM_CALENDAR 테이블의) DATECHAR는 날짜(ex, 20200401, 20200402, ...)이고 이 날짜는 1부터 31 사이라는 규칙을 갖게 됩니다. 그래서 DATECHAR를 자르면 Rownum의 개념으로 사용할 수 있다. IF 함수를 적용하여 Rownum을 비교하면 한다면 피벗이 가능합니다.
2) 피벗 2 - PM_COMMUTE
다음으로 사람들의 출, 퇴근 데이터가 있는 PM_COMMUTE 데이터를 피벗시켰습니다. (두 데이터를 UNION ALL로 합칠 계획입니다)
(PM_COMMUTE 테이블의) WORKDAY도 날짜(ex, 20200401, 20204002, ...) 데이터가 들어있고 위 DATECHAR와 같은 동일한 규칙을 가지기 때문에 Rownum의 개념으로 사용이 가능합니다.
SELECT
A.USERCD
, MAX(IF(ROWNUM = '20', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY20
, MAX(IF(ROWNUM = '21', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY21
, MAX(IF(ROWNUM = '22', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY22
, MAX(IF(ROWNUM = '23', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY23
, MAX(IF(ROWNUM = '24', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY24
, MAX(IF(ROWNUM = '25', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY25
, MAX(IF(ROWNUM = '26', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY26
, MAX(IF(ROWNUM = '27', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY27
, MAX(IF(ROWNUM = '28', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY28
, MAX(IF(ROWNUM = '29', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY29
, MAX(IF(ROWNUM = '30', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY30
, MAX(IF(ROWNUM = '31', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY31
FROM (
SELECT
USERCD
, SUBSTRING(WORKDAY,7,2) AS ROWNUM
, IFNULL(DATE_FORMAT(TODATE, '%H:%i'),'') TODATE
, IFNULL(DATE_FORMAT(FROMDATE, '%H:%i'),'') FROMDATE
FROM PM_COMMUTE
WHERE WORKDAY BETWEEN '20200420' AND '20200430'
AND CMPCD = 'P0001'
) A
GROUP BY A.USERCD
IF 함수와 집계 함수를 이용해 피벗을 시켰습니다.
3) UNION ALL
두 피벗된 테이블을 UNION ALL로 합쳐 원하는 결과를 만들었습니다.
SELECT
'' AS USERCD
, MAX(IF(B.ROWNUM = '20', B.WORKDAY, 0)) AS WORKDAY20
, MAX(IF(B.ROWNUM = '21', B.WORKDAY, 0)) AS WORKDAY21
, MAX(IF(B.ROWNUM = '22', B.WORKDAY, 0)) AS WORKDAY22
, MAX(IF(B.ROWNUM = '23', B.WORKDAY, 0)) AS WORKDAY23
, MAX(IF(B.ROWNUM = '24', B.WORKDAY, 0)) AS WORKDAY24
, MAX(IF(B.ROWNUM = '25', B.WORKDAY, 0)) AS WORKDAY25
, MAX(IF(B.ROWNUM = '26', B.WORKDAY, 0)) AS WORKDAY26
, MAX(IF(B.ROWNUM = '27', B.WORKDAY, 0)) AS WORKDAY27
, MAX(IF(B.ROWNUM = '28', B.WORKDAY, 0)) AS WORKDAY28
, MAX(IF(B.ROWNUM = '29', B.WORKDAY, 0)) AS WORKDAY29
, MAX(IF(B.ROWNUM = '30', B.WORKDAY, 0)) AS WORKDAY30
, MAX(IF(B.ROWNUM = '31', B.WORKDAY, 0)) AS WORKDAY31
FROM (
SELECT
CONCAT(SUBSTRING(A.DATECHAR,5,2),'.',SUBSTRING(A.DATECHAR,7,2)) WORKDAY
, SUBSTRING(A.DATECHAR,7,2) AS ROWNUM
FROM PM_CALENDAR A
WHERE A.DATECHAR BETWEEN '20200420' AND '20200430'
) B
UNION ALL
SELECT
A.USERCD
, MAX(IF(ROWNUM = '20', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY20
, MAX(IF(ROWNUM = '21', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY21
, MAX(IF(ROWNUM = '22', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY22
, MAX(IF(ROWNUM = '23', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY23
, MAX(IF(ROWNUM = '24', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY24
, MAX(IF(ROWNUM = '25', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY25
, MAX(IF(ROWNUM = '26', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY26
, MAX(IF(ROWNUM = '27', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY27
, MAX(IF(ROWNUM = '28', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY28
, MAX(IF(ROWNUM = '29', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY29
, MAX(IF(ROWNUM = '30', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY30
, MAX(IF(ROWNUM = '31', CONCAT(A.TODATE, '<br>/', A.FROMDATE), '-')) AS WORKDAY31
FROM (
SELECT
USERCD
, SUBSTRING(WORKDAY,7,2) AS ROWNUM
, IFNULL(DATE_FORMAT(TODATE, '%H:%i'),'') TODATE
, IFNULL(DATE_FORMAT(FROMDATE, '%H:%i'),'') FROMDATE
FROM PM_COMMUTE
WHERE WORKDAY BETWEEN '20200420' AND '20200430'
AND CMPCD = 'P0001'
) A
GROUP BY A.USERCD
+ 피드백은 언제나 환영입니다 :)
'Database > MySQL,MariaDB' 카테고리의 다른 글
[MySQL,MariaDB]암호화, 복호화 (5) | 2021.07.30 |
---|---|
[MySQL,MariaDB]Outer Join 조건 주기 (2) | 2020.04.30 |
[MySQL,MariaDB]SQL 오류 (1093) (0) | 2020.04.29 |
[MySQL]현재 시간 입력 (0) | 2018.09.12 |
소중한 공감 감사합니다.