TigerCow.Door

'MS SQL Server'에 해당되는 글 2건


안녕하세요. 문범우입니다.

이번 포스팅에서는 rollup, cube, case, pivot에 대해서 함께 알아보도록 하겠습니다.



먼저 이전의 글에서 만들었던 sample데이터를 통해 실습을 진행해보도록 하겠습니다.


SELECT * FROM employee;




1. ROLLUP


먼저 알아볼 함수는 ROLLUP 입니다.

ROLLUP은 GROUP BY문과 함께 사용되는 함수인데, GROUP BY로 GROUPING된 행들 데이터 총합을 나타는데 사용합니다.

즉, employee테이블에서 부서별 그리고 직급 별 급여 합과 총합을 구하기 위해서 ROLLUP이 사용될 수 있습니다.


SELECT deptNo, job, SUM(salary) 급여총합

FROM employee

GROUP BY deptNo, job with ROLLUP;



사실상 부서별 그리고 직급별 급여의 합을 구하는 것은 SUM함수를 통해서 할 수 있었습니다.

하지만 위의 결과에서 빨간색으로 음영처리해놓은 결과는 이전의 SUM함수를 통해서 얻을 수 없었습니다.

첫번째 빨간색 음영 박스가 의미하는 것은 deptNO이 10인 부서에서 모든 직급(모든 job)의 급여총합을 의미합니다.

즉, deptNO이 10인 행들끼리 job을 모두 감았다(roll up)는 의미로 생각하시면 될 것 같습니다.

그리고 우리가 GROUP BY를 deptNo, job 로 GROUPING하였기 때문에, job으로 ROLLUP된 행 3개와 deptNO으로 ROLLUP 된 마지막 행(15번째 행)까지 총 4개의 추가 행이 결과로 나오게 됩니다.



2. CUBE


이번에는 CUBE에 대해서 알아봅니다.

쉽게 생각하면 CUBE는 ROLLUP 함수의 확장 형태라고 볼 수 있습니다.

우리가 위의 예에서 deptNo, job으로 ROLLUP을 진행하니, deptNo 끼리 job으로 묶인 salary의 총합(3개 행)과 deptNo와 job으로 묶인 salary의 총합(1개행)의 추가 데이터를 얻을 수 있었습니다.

만약 ROLLUP을 job, deptNo으로 진행했다면 어떨까요?

job끼리 deptNo으로 묶인 salary의 총합(6개 행 = job의 종류가 6개)과 job과 deptNo로 묶인 salary의 총합(1개행)의 추가 데이터를 얻을 수 있습니다.

CUBE는 이렇게 GROUPING 할 때 언급한 열의 순서에 따라 나타날 수 있는 ROLLUP의 경우의 수를 모두 나타내주게 됩니다. 그럼 바로 확인해보도록 하겠습니다.


SELECT deptNo, job, SUM(salary) 급여총합

FROM employee

GROUP BY deptNo, job with CUBE;



위의 결과를 보시면 먼저 3행은 과장 직급의 급여총합을 구한 것이며, 동일하게 5행, 9행, 13행, 15행, 17행은 각 직급별 급여총합을 나타내고 있습니다.

그리고 18행은 모든 deptNo와 job의 급여 총합을 나타내주고 있으며, 19행, 20행, 21행은 deptNo별 급여총합을 나타내고 있습니다.

위의 쿼리문을 아래와 같이 하면 행의 순서는 바뀔 수 있지만 결과는 동일합니다


SELECT deptNo, job, SUM(salary) 급여총합

FROM employee

GROUP BY job, deptNo with CUBE;



3. CASE


이번에 알아볼 CASE문은 C언어나 Java와 같은 프로그래밍 언어에서 사용되는 if ~ else ~ 문 또는 switch 문과 개념적으로 동일합니다.

기본적인 형식은 아래와 같습니다.


CASE when 조건식1 then 결과 1

          when 조건식1 then 결과2

          ...

          [ELSE 결과]

END [AS 칼럼 명]


여기서 대괄호로 표시된 것들은 생략할 수 있습니다.


CASE문은, 우리가 employee 테이블에서 부서별로 부서명을 직접 나타내고 싶을 때와 같은 경우에 사용할 수 있습니다. 부서번호가 10인 경우 전산부, 20인 경우 인사부, 30인 경우 경리부, 40인 경우 영업부로 나타내고 이 외에는 미정이라고 나타내보도록 합시다.


SELECT empName, deptNo,

CASE WHEN dpetNo = 10 THEN '전산부'

WHEN deptNo = 20 THEN '인사부'

WHEN deptNo = 30 THEN '경리부'

WHEN deptNo = 40 THEN '영업부'

ELSE '미정'

END AS 부서명

FROM employee;



위와 같이 우리가 조건을 설정한 대로 부서명이 올바르게 출력되었습니다.

조건식에는 범위에 대한 조건도 설정할 수 있습니다.


만약 부서번호가 20이하인 경우는 부서위치가 1층, 20초과인 경우에는 부서위치가 2층인 것으로 나타내고자 하면 다음과 같이 할 수 있습니다.


SELECT empName, deptNo,

CASE WHEN deptNo <= 20 THEN '1층'

WHEN deptNo > 20 THEN '2층'

END AS 부서위치

FROM employee;




4. PIVOT / UNPIVOT


마지막으로 알아볼 것은 PIVOT과 UNPIVOT입니다.

먼저 PIVOT에 대해서 알아보도록 하겠습니다.

PIVOT은 행으로 되어 있는 데이터를 열로 변환해서 표현하는 함수입니다.


우리가 가지고 있는 employee 테이블에서 열을 deptNo과 각 직급으로 나타내어 그 값을 표현하려면 어떻게 해야 할까요? 기존의 직급 데이터는 'job'이라는 속성에 행으로써 존재 했습니다. 하지만 사원, 부장 과 같이 행으로 되어있던 데이터를 열로 나타내려면 PIVOT을 사용하면 됩니다.

PIVOT문의 기본 형태는 다음과 같습니다.


SELECT [PIVOT열1], [PIVOT열2], ...

FROM 테이블명

PIVOT(나타내고자하는 값, FOR 행->열로 바꿀 열 IN ([PIVOT열1], [PIVOT열2], ...)) as 별칭


PIVOT문은 조금 어려울 수 있기 때문에 바로 실제 쿼리를 보면서 이해하시는 것을 추천드립니다.

위에서 이야기한 바를 쿼리로 나타내면 다음과 같습니다.


SELECT deptNo, [사원], [대리], [과장], [부장], [차장], [사장]

FROM employee

PIVOT(SUM(salary) FOR job In([사원], [대리], [과장], [부장], [차장], [사장])) AS job

ORDER BY deptNo;



위와 같이 행으로 되어있던 값을 열로 나타내어 테이블의 형태를 재구축 할 수 있습니다.

UNPIVOT과 같은 경우도 PIVOT과 활용법이 동일하지만 PIVOT이 행->열의 방식이었다면 UNPIVOT은 열->행의 방식인 것만 고려하면 됩니다.


블로그 이미지

Tigercow.Door

Web Programming / Back-end / Database / AI / Algorithm / DeepLearning / etc


안녕하세요. 문범우입니다.

이번 포스팅에서는 먼저 샘플 데이터세트를 구성해보고, 이를 바탕으로 select 문에 대해서 보다 자세히 다뤄보도록 하겠습니다.



1. 샘플 데이터세트 구성하기


아래의 쿼리를 실행시킴으로써 샘플 데이터베이스 생성 부터 데이터 입력까지 진행합니다.

단순 CREATE 및 INSERT 문이기 때문에 복사 붙여넣기를 하셔도 되지만

복습 및 익숙해짐을 위해서 직접 타이핑 해보시는 것도 좋을 것 같습니다.


-- CREATE SAMPLE_DB

CREATE DATABASE SampleDB;


USE SampleDB;


-- CREATE TABLE

CREATE TABLE department(

deptNo INT PRIMARY KEY,

deptName NVARCHAR(20),

location NCHAR(20)

);


CREATE TABLE employee(

empNo INT PRIMARY KEY,

empName NVARCHAR(20),

job NVARCHAR(20),

manager INT,

hireDate DATETIME,

salary INT,

commission INT,

deptNo INT FOREIGN KEY REFERENCES DEPARTMENT(deptNo)

);


-- INSERT DATA

INSERT INTO department (deptNo, deptName, location)

VALUES (10, '경리부', '서울');

INSERT INTO department (deptNo, deptName, location)

VALUES (20, '인사부', '인천');

INSERT INTO department (deptNo, deptName, location)

VALUES (30, '영업부', '대전');

INSERT INTO department (deptNo, deptName, location)

VALUES (40, '전산부', '부천');


INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1001, '최범우', '사원', 1013, '2007-03-21', 300, 20);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, commission, deptNo)

VALUES (1002, '김범수', '대리', 1005, '2007-04-11', 250, 80, 30);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, commission, deptNo)

VALUES (1003, '장태훈', '과장', 1005, '2005-02-01', 500, 100, 30);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1004, '최상우', '부장', 1008, '2003-09-01', 600, 20);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, commission, deptNo)

VALUES (1005, '변봉중', '과장', 1008, '2003-10-02', 450, 200, 30);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1006, '송원철', '부장', 1005, '2003-08-02', 480, 30);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1007, '문서연', '부장', 1008, '2004-03-08', 520, 10);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, commission, deptNo)

VALUES (1008, '장웅', '차장', 1003, '2004-03-01', 500, 0, 30);

INSERT INTO employee (empNo, empName, job, hireDate, salary, deptNo)

VALUES (1009, '최상범', '사장', '1996-10-04', 1000, 20);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1010, '이명근', '과장', 1003, '2005-04-01', 500, 10);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1011, '서은혜', '사원', 1007, '2007-02-03', 280, 30);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1012, '이철진', '사원', 1006, '2007-08-06', 300, 20);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1013, '한태범', '부장', 1003, '2002-10-09', 560, 20);

INSERT INTO employee (empNo, empName, job, manager, hireDate, salary, deptNo)

VALUES (1014, '김광우', '사원', 1006, '2007-11-09', 250, 10);


위의 쿼리들을 이용해서 데이터세트를 구성하고 나서 아래의 기본 SELECT문을 이용하여 각각의 테이블을 조회하면 아래 사진과 같은 결과가 나와야 합니다.


-- SELECT TABLE

SELECT * FROM department;




SELECT * FROM employee;



그럼 위의 데이터들을 바탕으로 SELECT 문에 대해서 학습해보도록 하겠습니다.



2. SELECT


기본적으로 SELECT 문은 아래와 같은 형태를 띄고 있습니다.


SELECT <COLUMN NAME1, COLUMN NAME2, ...>

FROM <TABLE NAME>;


물론 해당 SELECT 문에 WHERE 문이라는, 조건절이 추가적으로 붙어서 사용되는 경우가 많지만 이번 포스팅에서 해당 내용은 제외하고 심플하게 SELECT문에 대해서만 알아보도록 하겠습니다.


SELECT문 에서는 특정 테이블에서 내가 원하는 컬럼에 대한 데이터만 조회할 수 있습니다.

이때 조회하고자 하는 테이블이름과 조회하고자 하는 컬럼명을 적어주어야 합니다.

그럼 employee 테이블에서 empName과 job, salary를 조회해보도록 하겠습니다.


SELECT empName, job, salary

FROM employee;




* SELECT문에서 연산자 이용하기


이번에는 SELECT 문에서 연산자를 이용해보는 실습을 진행해보겠습니다.

현재 우리는 salary에 대한 데이터를 가지고 있는데, 만약 연봉값을 알고 싶을 땐 어떻게 해야할까요?

간단하게 생각해보면, salary는 월급이므로 salary에 12를 곱하면 연봉이 될 것입니다.

SELECT 문에서는 연산자 이용이 가능합니다. 따라서 연봉을 구하기 위해 salary에 12를 곱해주면 되는 것이죠.


아래와 같이 쿼리문을 작성함으로써 employee들의 연봉 값을 같이 구할 수 있습니다.


SELECT empName, job, salary, salary*12

FROM employee;




위의 결과를 보면 salary에 12를 곱한 값들도 함께 출력되는 것을 확인할 수 있습니다.

하지만 해당 열이름을 보면 (열 이름 없음) 이라는 값으로 나와서 그것이 연봉인지는 모두가 알기 쉽지 않을 것 입니다.

그럼 우리가 원하는 열 이름을 지정해줄 수 있다면 어떨까요?

당연히 해당 기능을 지원하고 많이 이용되고 있습니다.



* as 를 이용한 열 이름 별칭 사용하기


해당 기능을 열 이름에 별칭을 사용한다고 표현합니다.

별칭이라는 말 그대로, 원래 이름대신 별명을 이용하는 것이라고 생각하시면 됩니다.

이는 위에서 본 (열 이름 없이)와 같이 새롭게 생성된 열 이외에도, 기존의 열에도 사용 가능합니다.

아래의 쿼리와 같이 작성함으로써 각각의 열에 대해서 우리가 원하는 이름을 지정할 수 있습니다.

SELECT empName as 사원이름, job as 직급, salary as 월급, salary*12 as 연봉

FROM employee;



위와 같이 as 뒤에 설정한 이름이 출력되는 열의 이름이 된 것을 확인할 수 있습니다.


추가로, 아래와 같이 as 라는 예약어는 생략가능하며, 만약 우리가 지어주는 열 이름에 띄어쓰기를 넣고 싶다면 아래와 같이 별칭 값을 홀 따옴표로 묶어줘야 합니다.




그런데 사실 연봉에는 commission 값도 포함되어야 합니다.

그럼 연봉에 해당 값을 같이 더해서 출력해보겠습니다.

우리가 * 연산을 활용한 것과 같이 그대로 + 연산을 활용하면 됩니다.


SELECT empName 사원이름, job 직급, salary 월급, (salary*12) + commission '연  봉'

FROM employee;



하지만 결과를 보면, 우리가 생각하지 못했던 NULL 값이 출력되고 있습니다.

왜 이런 결과가 나오는 것 일까요?



* NULL 값


먼저 그 이유를 알아보자면, NULL과 함께 연산했기 때문에 그 결과가 NULL로 나오는 것 입니다.


기존의 데이터를 살펴보면 commission 값을 NULL로 설정한 행들이 있습니다.

그리고 위의 결과와 대조해 보았을 때 commission 값이 NULL인 행들의 연봉이 NULL로 나온 것을 볼 수 있습니다.

즉, 덧셈 연산을 통해 특정 값에 NULL을 더했기 때문에 그 결과도 NULL이 나오는 것 입니다.

도대체 NULL이 정확이 무엇이길래, 그러한 결과가 될까요?


NULL은 0이라는 값이나, 문자열에서의 공백을 의미하는 것이 아닙니다.

그렇다고 해서 값이 저장되는 공간을 의미하는 것도 아닙니다.

NULL은 정말 단순하게, 인식되지 않는 값, 미확정 값을 의미합니다.


즉 NULL 값이란 어떠한 값이 존재하는 것이지만 어떠한 값인지 우리가 알 수 없는 것 입니다.

NULL 값이 단순하게 하나로 정의되기 보다는 우리가 인식하지 못하는, 어떠한 값이라고 이야기할 수 없는 모든 값이 NULL로 표현됩니다.

이해를 위해 특정 NULL의 값이 3a937f8b13 이라고 해봅시다.

그런데 만약 해당 값에 5000 이라는 값을 더하면, 우리가 인식할 수 있는 값이 될까요?

그렇지 않습니다. 우리가 5000 이라는 인식할 수 있는 값이 있었지만 해당 값과 인식하지 못하는 값을 더했기 때문에 결국 그 결과도 인식할 수 없는 결과가 되어 버립니다.


따라서 연산에서도, NULL을 이용한 연산을 한다면 그 결과 또한 항상 NULL이 되어버립니다.


그럼 이렇게 NULL이 포함된 값에 대해서 연산을 수행할 때, 그 결과가 NULL이 나오지 않게 하려면 어떻게 해야 할까요?



* isnull 함수 사용하기


바로, isnull() 이라는 내장함수를 사용하면 됩니다.

내장함수라는 것은 말 그대로, 외부에서 호출(import)을 하지 않아도 내부에 저장되어 있어 즉시에 사용할 수 있는 함수를 이야기합니다.

isnull함수는 다음과 같이 사용될 수 있습니다.


isnull(field, val)


위와 같이 사용되는 isnull함수는 field의 값이 null일때 val값으로 대치(변경)되어 처리됩니다.


말이 조금 어렵다면 바로 아래와 같이 쿼리를 작성하여 그 결과를 보고 이해해보도록 합시다.


SELECT empName 사원이름, job 직급, salary 월급, isnull(commission, 0) 보너스, (salary*12) + isnull(commission, 0) '연  봉' FROM employee;



아까와 달리 NULL이라는 결과가 나오지 않고, 제대로 결과가 출력됨을 볼 수 있습니다.



* distinct


그럼 이번에는 회사에 있는 직급들에 대해서만 한번 출력해보도록 하겠습니다.


SELECT job as 직급 FROM employee;


위와 같이 회사내의 직원들의 직급이 전부 출력되었습니다.

하지만 위의 결과에서는 직급에 대해 중복되는 결과들이 있기에 실제로 회사에 어떤 직급들이 존재하는지 보기가 편리한 결과는 아닙니다.

즉, 중복되는 값들을 출력되지 않게 한다면 회사 내에 존재하는 직급들을 확인하는데 보다 편리할 것 입니다.

이렇게, 중복되는 값들은 출력되지 않도록 하는 것이 distinct 입니다.

아래의 쿼리를 통해 확인해보도록 하겠습니다.


SELECT DISTINCT job as 직급 FROM employee;


그럼 아래와 같은 쿼리는 어떨까요?


SELECT DISTINCT job as 직급, empName as 사원이름 FROM employee;


이전의 쿼리와 달리, 직급의 중복 데이터가 존재합니다.

즉 DISTINCT는 특정 칼럼에 대해서만 중복을 확인하는 것이 아니라, 출력되는 각 행(레코드)의 중복을 체크하는 것입니다.

다시 말해서, 출력된 결과의 1번과 2번의 직급은 과장으로 동일하지만 사원이름이 서로 다르기 때문에 중복된 행이 아닙니다. 따라서 DISTINCT에 의해 생략되지 않고 둘 다 출력되는 것 입니다.



* 애스터리스크(*)


마지막으로는 애스터리스크라고도 불리는, 별표(*)에 대해서 알아보고 오늘 글을 마무리하려 합니다.

사실 이전에도 몇번 만나보았고 간단하게 설명드렸던 내용입니다.

이전에 우리가 아래와 같은 쿼리를 작성했던 것 기억하시나요?


SELECT * FROM employee;

위의 쿼리에서 *를 애스터리스트, 별표라고 합니다.

그리고 그 의미는 모든 값을 의미하는 것이죠.

즉, employee에 있는 모든 칼럼을 의미하기 때문에 해당 쿼리의 결과는 아래 사진과 같이 employee 테이블의 모든 칼럼에 대한 값을 출력하게 됩니다.




이렇게 해서 SELECT 문에 대한 글을 마무리합니다.


물론 추후에 WHERE절이 붙거나 JOIN, 서브쿼리를 이용하는 등의 작업에 있어서 SELECT 문에 대해서도 더 알아보아야 할 내용들이 많겠지만, 간단한 내용부터 하나씩 알아갈 예정입니다.

처음 SQL을 공부하시는 분들은 꼭 위의 내용에 대해서 이해해주셔야 추후에 더 복잡한 쿼리문에 있어서 어렵지 않게 공부하실 수 있을 것이라 생각됩니다.

더 궁금한 점이나 잘못된 부분에 대해서는 언제든지 거침없이 말씀해주시면 감사하겠습니다 :)


블로그 이미지

Tigercow.Door

Web Programming / Back-end / Database / AI / Algorithm / DeepLearning / etc