TigerCow.Door


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

이번 포스팅에서는 이중 not exists에 관해 예제를 다뤄보도록 하겠습니다.

이번에 다루게 되는 내용에 대해서는 기본적으로 not exists에 대해 동작 방식을 이해해야 수월하게 따라올 수 있습니다.

not exists에 대해 아직 헷갈린다면 아래 글을 먼저 확인해주세요.


[MS SQL Server] #11_ IN / EXISTS / NOT IN / NOT EXISTS 비교


특별히 이번 예제는 블로그를 통해서 연락주신 분에 의해서 다루게 되었습니다.



1. 테이블 정의 및 데이터 정의


먼저 예제를 소개하기에 앞서 사용될 테이블과 데이터를 정의합니다.

총 3개의 테이블(sailors, boats, reserved)을 사용하며 각 테이블에 있는 데이터는 아래 사진과 같습니다.


sailors tablesailors table


boats tableboats table


reserved tablereserved table



테이블에 대해 간략히 소개를 드리자면,

sailors 테이블은 5명의 사람데이터가 입력되어 있습니다. 각 사람에게는 sid라는 고유번호가 주어져 있으며 name값으로 이름이 주어집니다.

boats 테이블은 총 3개의 보트데이터가 입력되어 있습니다. 각 보트에는 bid라는 고유번호가 주어지며 boat_name값으로 이름이 주어집니다.

reserved 테이블은 어떤 사람이 어떤 보트를 예약했는지에 대한 정보를 가진 테이블입니다. 즉 sid에 해당하는 사람이 bid에 해당하는 보트를 예약한 것입니다.



2. 이중 not exists 예제


위와 같은 테이블과 데이터를 기반으로 우리의 목표는 boats 테이블에 있는 모든 보트를 예약한 사람을 구하는 것입니다. 물론 쉽게 알 수 있듯이 sid = 1인, sailor_1이 그 답이 되겠죠.

이 답을 찾기 위해 다음과 같은 쿼리를 사용할 수 있습니다.



위의 쿼리에서는 두번 중첩된 not exists 문, 즉 이중 not exists 문을 이용하여 답을 구하였습니다.


이번 포스팅에서는 위와 같은 쿼리가 어떤 과정으로 답을 도출해내는지 알아보려고 합니다.



3. 이중 not exists 분석


먼저 보다 쉽게 설명하기 위해서 위의 쿼리를 아래와 같이 3개로 나눠보도록 하겠습니다.



위의 그림과 같이 가장 바깥에 있는 쿼리 전체를 1번 쿼리, 그리고 1번 쿼리의 where 문에 있는 서브쿼리를 2번 쿼리, 마지막으로 2번 쿼리의 where 문에 있는 서브쿼리를 3번 쿼리라고 하겠습니다.


not exists문에 대해서 기본적인 내용을 이해한 상태라면 not exists문은 이하 서브쿼리에 값이 아무것도 존재하지 않아야 참이 됩니다.

그럼 데이터 하나씩 그 과정을 자세하게 살펴보도록 하겠습니다.

(아래 모든 그림들에서 3번째 테이블의 pid는 오타입니다. pid가 아닌 sid가 맞습니다.)



쿼리는 위와 같은 3개의 테이블에 대해서 하나씩 데이터를 꺼내 where문에 대해 비교를 실시합니다. 먼저 1번쿼리에 의해서 아래와 같이 sid=1, name=sailor_1인 데이터에 대해 where문 비교를 실시하겠죠.



1번 쿼리의 where문을 비교하기 위해 2번쿼리로 진입합니다. 2번쿼리에서 아래와 같이 bid=101, boat_name=boat_1인 데이터를 대해서 where문을 비교합니다.



이번에는 2번쿼리의 where문을 확인하기 위해 3번 쿼리로 진입합니다. 마지막 3번 쿼리에서는 where을 비교하기 위해 아래와 같이 bid=101, sid=1에 대한 데이터를 비교하기 시작합니다.



그런데 3번 쿼리에서 bid=101, sid=1인 데이터와 1번 쿼리에서 온 sid=1, name=sailor_1, 2번 쿼리에서온 bid=101, boat_name=boat_1인 데이터를 통해 where문을 확인하면 s.sid=r.sid와 b.bid=r.bid 가 모두 참이기 때문에 3번쿼리의 where문이 참이 됩니다. 따라서 3번쿼리에 따른 결과가 아래와 같이 출력됩니다.



이제 다시 2번쿼리로 올라가봅니다. 2번 쿼리의 where문을 보면 not exists(3번쿼리) 입니다. 헌데 방금 위에서 확인하였듯이 3번쿼리에 대한 결과 값이 존재하기 때문에 2번 쿼리의 where문은 거짓이 되죠. 따라서 2번쿼리에서 비교한 bid=101, boat_name=boat_1에 대해서는 2번 쿼리의 결과가 NULL입니다. 이번에는 2번쿼리에서 아래와 같이 bid=102, boat_name=boat_2에 대해서 동일하게 3번쿼리의 비교를 시작합니다.



이번 비교 또한 위와 같이 3번쿼리에서 where문이 참으로 3번쿼리에 대한 결과가 도출 됩니다. 따라서 2번 쿼리에서 비교한 bid=102, boat_name=boat_2에 대해서도 결과가 NULL입니다. 마지막으로 2번쿼리에서 bid=103, boat_name=boat_3에 대해서 비교를 해도 아래와 같이 결과는 같습니다.



즉 이를 통해 2번쿼리에 대해서 모든 데이터의 결과가 NULL입니다.

여기까지의 결과에 한정해서 현재 쿼리 상태를 보면 아래와 같습니다.


위의 그림에서 빨간색으로 나타난 글자와 같이 현재 위의 쿼리는 sailors 테이블의 sid=1, name=sailor_1인 데이터에 대해서 1번쿼리의 서브쿼리 결과를 보다 확인하기 쉽게 나타내주고 있습니다. 위의 쿼리에서 where not exists 이하가 모두 NULL이기 때문에 where문은 결국 참이 됩니다. 따라서, sid=1, name=sailor_1인 데이터는 1번 쿼리의 결과로써 출력이 되는 것 입니다.


동일한 방법으로 1번쿼리에서 두번째 데이터는 왜 출력이 되지 않는지 확인해보겠습니다.



우선 위에서 했던 것과 동일한 방식으로 1번 쿼리에서는 sid=2, name=sailor_2인 데이터에 대해 비교를 시작하며 where문의 서브쿼리인 2번 쿼리로 진입합니다. 2번 쿼리에서는 bid=101, boat_name=boat_1 데이터에 대해서 3번쿼리로 진입하고, 3번 쿼리의 where문에서 참이 되기 때문에 3번 쿼리에 대한 결과는 bid=101, sid=2가 나오게 됩니다. 이에 따라 bid=101, boat_name=boat_1 데이터에 대한 2번 쿼리의 결과는 NULL 입니다.


하지만 위와 같이 2번 쿼리에서 bid=102, boat_name=boat_2인 데이터에 대해서 확인하면 3번 쿼리에서 where문을 참으로 하는 3번째 테이블의 데이터가 없기 때문에 3번 테이블의 결과가 NULL이 됩니다. 이에 따라 2번 쿼리의 where문은 참이 되고, 2번 쿼리에서 bid=102, boat_name=boat_2인 데이터에 대한 결과는 이전과 달리 NULL이 아니라 해당 데이터가 출력되게 됩니다.



위와 같이 2번 쿼리에서 bid=3, boat_name=boat_3인 데이터에 대해 비교했을 때도 동일한 결과가 나와서 해당 데이터에 대한 2번 쿼리의 결과는 NULL이 아니라 bid=103, boat_name=boat_3인 데이터가 나오게 됩니다.

여기까지의 결과를 이전과 같이 한정적으로 쿼리로 살펴보면 다음과 같습니다.



물론 위의 쿼리는 sailors 테이블의 sid=2, name=sailor_2인 데이터에 대해 한정적인 쿼리입니다. 위의 쿼리를 보면 where not exists 이하에 NULL이 아닌 값이 존재합니다. 따라서 where문 이가 거짓이 되어 sid=2, name=sailor_2인 데이터는 출력되지 않습니다.


이후 sailors 테이블의 다른 데이터를 동일한 방법으로 비교하였을 때에도 방금과 같은 이유로 출력되지 않습니다.

즉 처음에 살펴본 sid=1, name=sailor_1인 데이터만 출력되는 것이죠.



이렇게 이번에는 특정 결과를 도출하기 위해 쿼리를 만드는 것을 목적으로 하기 보다 하나의 쿼리에 대해서 그 동작방식을 자세히 알아보았습니다. 특히 이번에 알아본 이중 not exists문에 대해서는 동작방식을 이해하고 살펴보면 쉽게 결과를 예측할 수 있습니다.

물론 특정 결과를 위해 쿼리를 만드는 학습도 중요하지만 이와 같이 특정 쿼리를 보다 자세하게 분석하는 학습 또한 중요합니다.

추가적으로 궁금한 사항이나 잘못된 점은 언제나 댓글 및 이메일로 연락주시면 감사하겠습니다.

블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

이번 포스팅에서는 조인의 개념에 대해서 알아보고 기초적인 이너조인(Inner JOIN), 아우터조인(Outer JOIN), 크로스조인(Cross JOIN), 셀프조인(Self JOIN)에 대해서 함께 알아보도록 하겠습니다.



1. 조인(JOIN)이란 무엇일까?


DB에서 자주 사용되는 조인이란 개념은 무엇일까요?

조인은 '어울리다'라는 의미를 가지고 있는데 이 말대로, 데이터베이스에서 테이블간의 결합(어울림)을 이야기합니다.

즉, 두개 이상의 테이블에 대해서 결합하여 나타낼 때 조인이라는 것을 이용합니다.

물론 셀프조인에서는 사실 하나의 테이블을 다루는 것입니다. 이에 대해서는 뒤에서 다시 이야기해보겠습니다.


예시데이터를 가지고 살펴보겠습니다.

예시데이터는 이전의 글들에서 사용된 employee 테이블과 department 테이블을 사용하며, employee테이블에 아래와 같이 하나의 데이터를 추가해서 진행해보도록 하겠습니다.


insert into employee (empNo, empName, job, manager, hireDate, salary, commission)

values (1015, '문정진', '대리', 1001, '2009-11-11', 350, 50);


select * from employee;


select * from department;



만약 우리가 사원의 이름과 그가 속한 부서를 알고 싶을 땐 어떻게 해야할까요?

기존의 방법으로 employee 테이블을 통해 조회한다면 다음과 같을 것 입니다.


select empName, deptNo from employee;



하지만 위와 같은 결과는 부서이름이 아니라 부서 코드를 넣어 주었기 때문에 사실 어떤 직원이 어떤 부서에 속했는지 한눈에 보기 힘듭니다.

그런데 어떤 부서코드가 어떤 부서인지에 대한 정보는 department 테이블에 있습니다.

즉, 아래와 같이 하나의 결과에서 employee 테이블과 departement 테이블을 매핑시켜준다면 직원의 이름과 그 직원이 속한 부서의 이름을 보다 쉽게 알 수 있겠죠?

이럴 때 사용되는 것이 바로 조인(JOIN)입니다.



조인의 종류에는 처음에 말씀드렸듯 크게4가지, 이너조인, 아우터조인, 크로스조인, 셀프조인이 있습니다.

그럼 각각의 조인에 대해서 간단하게 알아보겠습니다.



2. 이너 조인(INNER JOIN)


이너조인은 위와 같이 우리가 조인하고자 하는 두개의 테이블에서 공통된 요소들을 통해 결합하는 조인방식입니다. 즉 우리가 위에서 하고자 했던, 직원들의 이름과 부서명을 같이 출력하는데 있어서 사용되는 가장 일반적인 조인이죠. sql에서도 단순히 조인을 사용할때는 암묵적으로 이너조인을 뜻하게 됩니다.


기본적인 이너조인의 SQL형태는 다음과 같습니다.


SELECT table1.col1, table1.col2, ..., table2.col1, table2.col2, ...

FROM table1 [table1의 별칭]

JOIN table2 [table2의 별칭] ON table1.col1 = table2.col2


위의 쿼리에서 대괄호로 사용된 각 테이블의 별칭은 SELECT 절에서 컬럼이름 앞에 붙는 테이블명에서 사용될 수 있습니다.

조인시에 table1과 table2의 어떤 컬럼을 기준으로 할지는 ON 뒤에 작성합니다.

즉 위의 쿼리에서는 table1의 col1 컬럼과 table2의 col2 컬럼이 같은 행들에 대해서 조인을 실시합니다.


그럼 바로 이너조인을 통해서 직원의 이름과 부서명을 함께 출력해보도록 합시다.


select employee.empName, department.deptName

from employee

join department on employee.deptNo = department.deptNo



위와 같이 join문을 통해서 직원의 이름과 부서명을 함께 출력하였습니다.

employee 테이블의 deptNo과 department 테이블의 deptNo이 같은 것끼리 결합하여 직원이름과 부서명을 출력한 것이죠.



3. 아우터 조인(OUTER JOIN)



아우터 조인은 위의 그림과 같이 레프트 아우터 조인, 라이트 아우터 조인 그리고 그 두개를 합친 풀 아우터 조인 총3개가 있습니다.

아우터 조인은 그림과 같이 두 테이블의 공통영역을 포함해 한쪽 테이블의 다른 데이터를 포함하는 조인방식입니다.

아우터 조인을 보다 쉽게 이해하기 위해서 위에서 진행한 이너 조인의 결과를 한번 더 살펴보겠습니다.



위의 결과에서는 사실 누락된 데이터가 있습니다.

employee 테이블을 기준으로 보면 '문정진'이라는 이름의 사원데이터가 누락되어 있으며, department 테이블을 기준으로 보면 '전산부'라는 부서가 누락되어 있습니다.

해당 데이터들이 누락된 이유는 두 테이블간의 공통된 데이터가 없기 때문이죠.

다시말해서, '문정진'사원의 deptNo가 NULL이기 때문에 department 테이블과 공통된 점이 없으며, '전산부' 또한 employee 테이블에서 같은 부서코드를 가진 데이터가 없기 때문에 출력되지 않은 것 입니다.

하지만 이러한 데이터들도 함께 보고싶은 경우가 있고, 그런 경우에 우리는 아우터 조인을 사용합니다.


이때 left와 outer를 정하는 기준은 from절에 적어준 테이블이 left가 되고, join절에 적어준 테이블이 right가 됩니다. 그럼 먼저 부서코드가 NULL이거나 부서테이블의 deptNo과 일치하는 값이 없는 사원까지 출력하는 left 아우터 조인을 쿼리로 확인해보도록 합시다.


select employee.empName, department.deptName

from employee

left outer join department on employee.deptNo = department.deptNo



위의 결과를 보시면 이전에 실습했던 이너조인과 달리 '문정진'사원의 데이터도 함께 나오는 것을 확인할 수 있습니다.


그럼 이어서 right 아우터 조인과, full 아우터 조인도 확인해보겠습니다.


select employee.empName, department.deptName

from employee

right outer join department on employee.deptNo = department.deptNo



select employee.empName, department.deptName

from employee

full outer join department on employee.deptNo = department.deptNo



앞에서 설명한 바와 같이 right 아우터 조인은 '전산부' 데이터도 함께 출력되고 있으며, full 아우터 조인은 left 아우터 조인과 right 아우터 조인의 결과를 합친 것과 같습니다.



4. 크로스 조인(CROSS JOIN)


크로스 조인은 사실 앞에서 진행한 이너 조인 및 아우터 조인과 약간의 차이가 존재합니다.

이너 조인과 아우터 조인은 두 테이블간의 특정 기준에 의해 데이터 결합의 결과를 보여주는 방식이었다면, 크로스 조인은 특정 기준 없이, 두 테이블간 가능한 모든 경우의 수에 대한 결합을 결과로 보여주는 방식입니다.


쿼리를 작성할 때에도, 특정한 기준이 필요없으므로 on절이 없어지게 됩니다.

바로 쿼리로 확인해보도록 하겠습니다.


select employee.empName, department.deptName

from employee

cross join department



위와 같이 모든 경우에 수에 대한 결과가 출력되기 때문에 출력 데이터의 행수는 총 15(employee의 모든 행의 개수) * 4(department의 모든 행의 개수) 으로써 60개 입니다.



5. 셀프 조인(SELF JOIN)


셀프조인은 말 그대로 자기 스스로를 결합시키는 조인입니다. 셀프조인은 이너조인 및 아우터 조인, 크로스 조인과 동일한 방식으로 사용될 수 있지만 조인을 할 때에 기본 테이블 이외에 참조하는 테이블이 다른 테이블이 아닌 자기 자신이라는 점이 중요합니다.

셀프조인이 어떤식으로 사용되는지에 대해서 employee 테이블의 데이터를 한번 더 보면서 설명드리겠습니다.



위의 결과를 보면 사원들이 이름과 더불어 manager라는 항목이 존재합니다.

만약 우리가 사원들의 이름과 함께 그 사원의 manager의 이름도 함께 알고 싶다면 어떻게 할까요?

참조할 다른 테이블도 존재하지 않습니다. 이럴 때 우리는 셀프조인을 이용합니다. 즉 employee 테이블, 자기 자신을 조인하는 것 입니다.

이때 우리는 위에서 각 테이블에 대한 별칭을 선택적으로 사용했지만 셀프 조인시에는 별칭을 필수로 입력해주어야 합니다. 같은 테이블을 2개 또는 그 이상 사용하는데 별칭을 정해주지 않으면 혼동되기 때문이죠.


그럼 앞서 말씀드린 상황, 직원의 이름과 그 manager의 이름을 동시에 출력하는 쿼리를 셀프조인 방식으로 확인해보도록 하겠습니다.


select emp1.empNo 사원번호, emp1.empName 직원이름,

emp1.manager 매니저번호, emp2.empName 매니저이름

from employee emp1

join employee emp2 on emp1.manager = emp2.empNo



위와 같이 쿼리의 기본형태는 이너조인과 동일하지만 참조하는 테이블이 from절에 오는 기본 테이블과 동일하며, 별칭을 적어줌으로써 구별하는 모습을 볼 수 있습니다.



블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요



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

이번 포스팅에서는 IN, EXISTS, NOT IN, NOT EXISTS 에 대해서 보다 상세하게 알아보려고 합니다.

해당 내용은 꼭 SQL Server 뿐만 아니라 MySQL 등에서도 포괄적으로 적용되는 내용입니다.



0. 데이터 세팅


먼저 각 구문에 대해서 비교를 할 때 보다 쉽게 확인할 수 있도록 가상 데이터를 세팅해보도록 하겠습니다. 총 2개의 테이블을 생성하며 각 테이블의 이름과 데이터는 아래와 같습니다.


SELECT * FROM TB_FOOD;




SELECT * FROM TB_COLOR;




1. IN


SELECT * FROM TB_FOOD f

WHERE f.number IN (SELECT c.number FROM TB_COLOR c);


위와 같은 쿼리를 실행하면 어떤 결과가 나올까요?

먼저 결과를 살펴보면 다음과 같습니다.



이는 우리가 어느정도 예상할 수 있는 결과입니다. 하지만 실제로 IN 을 포함한 쿼리가 어떻게, 어떤 식으로 작동되는지 알아야 이후에 EXISTS 또는 NOT IN / NOT EXISTS와 헷갈리지 않습니다.


위의 쿼리에서는 제일먼저 TB_COLOR 테이블에 접근하게 됩니다.

즉, IN 뒤에 있는 괄호의 서브쿼리를 먼저 실행해서 그에 대한 요소를 가져오는 것이죠.

따라서 사실 IN뒤에 괄호안에는 서브쿼리 이외에도 직접 요소값을 적어줄 수 있습니다.


이후에는 TB_FOOD에서 하나의 레코드를 가져오며 그 레코드의 number 값이 앞에서 가져온 IN 이하의 요소들에 포함되어 있는지를 체크합니다. 그리고 IN 이하의 요소들 중 하나라도 일치한다면 그 레코드를 출력하게 되는 것이죠.


여기서 중요한 것은, 쿼리에서 TB_COLOR에 먼저 접근하여, number 값들을 가져와 리스트로 IN 이하에 뿌려주고, 그 이후에 TB_FOOD에서 하나의 레코드씩 IN 이하의 요소들과 일치하는지 비교한다는 것 입니다.



2. EXISTS


그럼 EXISTS는 어떻게 동작하는지 쿼리와 그 결과를 보도록 합시다.


SELECT * FROM TB_FOOD f

WHERE EXISTS (SELECT c.number FROM TB_COLOR c);



무언가 이상합니다. 우리가 기대했던 결과와는 달리 TB_FOOD 테이블이 그대로 출력되었습니다. 왜 이럴까요? 이는 EXISTS 구문에 대해서 정확히 알지 못하고 잘못 사용하였기 때문에 나온 결과입니다.


위의 쿼리를 기준으로 DB가 어떻게 동작하는지 한번 알아보겠습니다.

IN구문에서는 IN 이후에 나오는 소괄호 내부의 서브쿼리에 대해서 먼저 접근하였습니다. 하지만 EXISTS 구문에서는 다릅니다. 먼저 TB_FOOD에 접근하여 하나의 레코드를 가져오고 그 레코드에 대해서 EXISTS 이하의 서브쿼리를 실행하고 서브쿼리에 대한 결과가 '존재하는지'를 확인합니다.


예시를 들어 생각해보면, 제일 처음에 [ 1 / 치킨 ] 이라는 레코드를 가져왔을 것이고, 해당 레코드에 대해서 SELECT c.number FROM TB_COLOR c 쿼리를 통해 결과가 나오는지 확인합니다. 이때 서브쿼리에 대해 어떠한 결과라도 존재하기만 한다면 참이 되어서 [ 1 / 치킨 ] 레코드가 출력됩니다.

그런데 SELECT c.number FROM TB_COLOR c 쿼리는 사실 TB_FOOD의 어떠한 레코드하고도 연관이 없이 항상 결과값을 가지는 쿼리입니다. 따라서 TB_FOOD의 모든 레코드가 출력되는 것 이죠.


그럼 이를 우리가 기대하는 결과대로 출력하도록 하기 위해서는 다음과 같이 쿼리를 수정하면 됩니다.


SELECT * FROM TB_FOOD f

WHERE EXISTS (SELECT c.number FROM TB_COLOR c WHERE c.number = f.number);



이렇게 나온 결과는 사실 IN 구문과 같은 결과를 출력합니다. 하지만 내부적으로 쿼리가 동작하는 방식은 아예 다르다는 것에 주의하시길 바랍니다. 그러한 내부 로직에 따라서 성능차이도 크게 발생하기 때문입니다.



3. NOT IN


이번에는 NOT IN 구문입니다.

먼저 쿼리와 그 결과를 보고 함께 생각해보겠습니다.


SELECT * FROM TB_FOOD f

WHERE f.number NOT IN (SELECT c.number FROM TB_COLOR c);



위의 쿼리를 실행하니 위의 사진과 같이 아무런 결과도 출력되지 않았습니다. 왜 그럴까요?

우리가 처음에 알아본 IN의 방식에 대해서 알아봅시다. IN은 먼저 소괄호의 서브쿼리를 실행합니다. 그럼 SELECT c.number FROM TB_COLOR c 의 쿼리가 실행되고 그 결과로 다음의 리스트가 반환됩니다.


( 1, 2, 3, 4, 5, 6, NULL )


즉, 초기의 쿼리는 다음과 같은 쿼리인 것입니다.


SELECT * FROM TB_FOOD f

WHERE f.number NOT IN ( 1, 2, 3, 4, 5, 6, NULL );


그럼 이제 TB_FOOD에서 하나의 레코드씩 가져올 것이고 IN이 아니라 NOT IN 구문이기 때문에 소괄호의 요소들과 일치하지 않아야 결과로 반환됩니다.


TB_FOOD의 레코드들 중에서 [ 7 / 사탕 ] 레코드를 예로 들어서 생각해보면 해당 레코드의 number 값인 7이 NOT IN 이하의 소괄호에 있는지 확인하면 됩니다. 분명히 7이라는 요소는 존재하지 않습니다. 따라서 우리의 생각대로 라면 해당 레코드는 결과로 출력되어야 하는데 위에서 본 것 처럼 출력되지 않았습니다. 왜 일까요?


이는 DB 에서 해당 요소가 NOT IN 이하의 소괄호의 요소들에 대한 포함여부를 어떻게 판단하는지를 알면 쉽게 이해할수 있습니다. 사실 위의 쿼리는 아래의 쿼리와 같이 동작함으로써 NOT IN 이하의 소괄호에 대한 포함 여부를 판단하게 됩니다.


SELECT * FROM TB_FOOD f

WHERE f.number != 1

AND f.number != 2

AND f.number != 3

AND f.number != 4

AND f.number != 5

AND f.number != 6

AND f.number != NULL;

즉, NOT IN 구문은 TB_FOOD에서 가져온 레코드의 number 값이 소괄호의 모든 요소들과 일치하지 않는지를 체크하는 것입니다. 그런데 위에서는 number 값이 NULL과 연산을 진행하게 되는데, 이때 NULL과의 비교연산은 항상 UNKNOWN 값을 반환하게 됩니다. 따라서 WHERE 절 이하가 TRUE가 아니므로 해당 레코드가 출력되지 않게 되는 것이죠.


이렇게 NOT IN이 어떻게 동작하는지를 알고보니 결국 TB_COLOR에 존재하는 NULL 때문에 우리가 기대하던 결과가 나오지 않음을 알 수 있었습니다. 그럼 우리가 기대한 결과가 나오게 하려면 다음과 같이 쿼리를 수정하면 됩니다.


SELECT * FROM TB_FOOD f

WHERE f.number NOT IN (SELECT c.number FROM TB_COLOR c WHERE c.number IS NOT NULL);


 



4. NOT EXISTS


그럼 마지막으로 NOT EXISTS에 대해서 알아보도록 하겠습니다.

오히려 NOT EXISTS는 위에서 EXISTS에 대해서 이해했다면 크게 어려운 점이 없습니다. 하지만 그 결과가 NOT IN과 약간 다르죠. 쿼리와 결과를 먼저 보도록 하겠습니다.


SELECT * FROM TB_FOOD f

WHERE NOT EXISTS (SELECT c.number FROM TB_COLOR c WHERE c.number = f.number);



위에서 NOT IN을 사용했을 때에는 number 값이 NULL인 레코드는 출력되지 않았습니다. 그 이유를 다시 생각해보자면, IN 구문은 요소간에 비교 연산으로 레코드가 출력되는데 NULL 값에 대한 비교연산은 항상 UNKNOWN을 반환하기 때문이었습니다. 

하지만 앞에서 알아볼 때 EXISTS 구문은 다르게 동작했습니다. 위 쿼리를 기준으로 한다면 먼저 TB_FOOD에서 레코드를 가져오고 해당 레코드의 number를 NOT EXISTS 이하의 서브쿼리에 전달하여 해당 서브쿼리에서 값이 존재하는지를 확인합니다. EXISTS 구문이었다면 값이 존재할 때 해당 레코드를 출력하지만, NOT EXISTS 구문이기에 해당 서브쿼리의 값이 존재하지 않으면 해당 레코드를 출력합니다.


여기서 NULL이 출력하는 과정을 한번 더 자세하게 알아보자면, [ NULL / 타코 ] 레코드를 예로 들었을 떄, number = NULL 입니다. 따라서 NOT EXISTS 이하의 쿼리를 확인해보면 다음과 같을 것 입니다.


SELECT c.number FROM TB_COLOR c WHERE c.number = NULL;


이때 우리가 NOT IN에서 알아본 것과 같이 NULL에 대한 비교연산은 항상 UNKNOWN 값을 반환하므로 해당 쿼리의 결과가 존재하지 않게 되고, 이에 따라서 [ NULL / 타코 ] 레코드가 출력되는 것 입니다.



이렇게 IN, EXISTS, NOT IN, NOT EXISTS 에 대해서 알아보았습니다.

저 또한 많이 헷갈리던 개념들아었는데 다시 한번 정리하고 나니 확실히 개념이 잡히는 것 같습니다. IN과 EXISTS는 성능에 대해서도 많은 이슈를 가져오는 구문들이니 그 차이를 확실히 짚고 넘어가는 것을 추천드립니다.


혹시 내용이 잘못되었거나 더 궁금하신 점이 있다면 언제든지 말씀해주시면 감사하겠습니다.

블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

이번 글에서는 SQL Server에서의 순위함수들(RANK, DENSE_RANK, ROW_NUMBER, NTILE)에 대해서 알아보며 함께 PARTITION BY 절에 대해서도 함께 알아보도록 하겠습니다.



이번 실습 또한 과거 글에서 만들었던 SampleDB를 통해 진행하도록 하겠습니다.


SELECT * FROM employee;



먼저 RANK, DENSE_RANK, ROW_NUMBER 세가지 함수에 대해서 알아보겠습니다. 먼저 각각의 함수에 대해 간략한 설명과 함께 쿼리를 통해 결과를 확인해보고 세가지 함수의 차이에 대해서 함께 알아보도록 하겠습니다.



1. RANK


RANK 함수는 중복 값들에 대해서 동일 순위로 표시하고, 중복 순위 다음 값에 대해서는 중복 개수만큼 떨어진 순위로 출력하도록 하는 함수 입니다.


SELECT empNo, empName, salary,

RANK() OVER (ORDER BY salary DESC) RANK등수

FROM employee;




2. DENSE_RANK


DENSE_RANK 함수는 중복 값들에 대해서 동일 순위로 표시하고, 중복 순위 다음 값에 대해서는 중복 값 개수와 상관없이 순차적인 순위 값을 출력하도록 하는 함수 입니다.


SELECT empNo, empName, salary,

DENSE_RANK() OVER (ORDER BY salary DESC) DENSE_RANK등수

FROM employee;




3. ROW_NUMBER


ROW_NUMBER 함수는 중복 값들에 대해서도 순차적인 순위를 표시하도록 출력하는 함수 입니다.


SELECT empNo, empName, salary,

ROW_NUMBER() OVER (ORDER BY salary DESC) ROW_NUMBER등수

FROM employee;




4. RANK / DENSE_RANK / ROW_NUMBER


위에서 3가지 함수에 대해서 각각 알아보았습니다.

크게 어려운 내용들은 아니기에 각 함수에 대한 설명과 쿼리문 그리고 그에 대한 결과를 보여드렸습니다.

하지만 세가지 함수의 차이에 대해서는 약간 헷갈릴 수 있기에 이번에는 3가지 함수를 동시에 사용해서 차이를 비교해보도록 하겠습니다.


SELECT empNo, empName, salary,

RANK() OVER (ORDER BY salary DESC) RANK등수,

DENSE_RANK() OVER (ORDER BY salary DESC) DENSE_RANK등수,

ROW_NUMBER() OVER (ORDER BY salary DESC) ROW_NUMBER등수

FROM employee;



세가지 함수의 차이를 고려하려면 위 결과의 빨간색 박스를 보면 됩니다.

salary 값이 500으로 중복된 행이 총 3개 존재합니다.


먼저 RANK함수는

중복 값에 대해서 동일한 순위 그리고

중복 값 다음 값에 대해서 중복순위 + 중복값 개수 의 순위를 출력합니다.


DENSE_RANK함수는

중복 값에 대해서 동일한 순위 그리고

중복 값 다음 값에 대해서 중복순위 + 1 의 순위를 출력합니다.


ROW_NUMBER함수는

중복 값에 대해서 순차적인 순위 그리고

중복 값 다음 값에 대해서 또한 순차적인 순위를 출력합니다.



5. NTILE


NTILE 함수도 순위함수로 사용되지만 위에서 사용된 함수들과는 약간 다르게 느껴질 수 있습니다.

NTILE함수는 뒤에 함께 적어주는 숫자 만큼으로 등분을 하는 함수 입니다.

만약 직원들 데이터에 대해서 salary 순서를 기준으로 4등분을 하고자 한다면 다음과 같습니다.


SELECT empNo, empName, salary,

NTILE(4) OVER (ORDER BY salary DESC) NTILE등분

FROM employee;





6. PARTITION BY


PARTITION BY 절 또한 어렵지 않습니다.

만약 위와 같은 데이터에서 단순히 모든 사람의 salary를 순위 매기고 싶은 것이 아니라, 직급별 순위를 매기고 싶다면 어떻게 할까요?

직급 별로 구분을 해서 순위를 매기면 됩니다. 이렇게 특정 속성 별로 구분을 하고자 할 때 PARTITION BY절을 사용하면 됩니다.


SELECT empName, job, salary,

RANK() OVER (PARTITION BY job ORDER BY salary DESC) RANK등수

FROM employee;



블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요

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

오랜만에 파이썬으로 필요한 프로그램을 만들어 공유하려고 글 작성합니다.

소개해드리고자 하는 프로그램은


중고나라에서 키워드 검색후 게시글 크롤링 및 메일 발송

기능을 가진 프로그램입니다.


모든 소스코드는 아래의 github주소에 공개되어있습니다.

https://github.com/doorBW/joonggonara_crawl


간단하게 활용 스택 및 배운점 등을 공유하겠습니다.


1. 활용 스택


- Python 3.7.1

- pyenv 및 virtualenv를 통한 가상환경설정

- Selenium / BeautifulSoup / smtplib / dotenv 라이브러리 활용

- ubuntu crontab 활용


위와 같은 언어 및 라이브러리를 활용하였습니다.

crawl.py 에서 중고나라 사이트에 접속하여 사전에 설정해놓은 키워드로 검색을 하고

50개씩 보기를 선택한 후에 중복 글쓴이를 제외한 나머지 게시글을 리스트로 만들어서 메일로 발송합니다.

해당 파일은 aws의 개인서버에 두고 crontab을 활용하여 정해진 시각에 실행되도록 설정하였습니다.



2. 배운 점


과거에 selenium을 활용해본적은 있으나, 이번에 보다 많이 활용해본 것 같습니다.

네이버 카페에서 게시글이 나오는 쪽이 iframe으로 설정되어 있어 selenium에서 frame도 바꿔주어야 했고,

50개씩 보기를 선택하면서 보다 활용도가 높았던 것 같습니다.

활용할수록 매우 재미있는 라이브러리 같아서, 여름쯤 시간내서 한번 더 깊게 공부해보면 재미있을 것 같습니다.

관련되서 재밌는 프로젝트들에 대해서 아시는게 있다면 알려주세요 :)

이 외에 다른 라이브러리나 ubuntu crontab 활용에 대해서는 과거에 사용해봤던 것과 동일했기 때문에 따로 언급하지 않겠습니다.



3. 더 공부해볼 점


사실상 게시글 제목을 중심으로 크롤링을 진행했지만, 그러한 제목에는 상품의 가격이 제대로 나와있지 않습니다.

가격 또한 쉽게 보려면 게시글에 들어가서 내용을 검토해야 하는데, 그렇게 하지 않은 이유는 게시글 내용에서도 가격이 정확하게 나와 있지 않기 때문입니다.

중고나라에서 가격을 적는 칸이 있지만, 사실 대다수가 지키지 않고 있는 실정입니다.

이에 따라서 게시글 내용을 text로 반환 받아 그 내용 중 상품 가격을 추출할 수 있다면 매우 활용적일 수 있습니다.

인공지능 분야에서도 자연어처리 분야에 대해 지식이 필요할 것 같은데, 혹시 관련되서 내용을 아시거나, 다른 방법으로 해당 문제를 해결할 수 있는 방안에 대해서 아신다면 댓글 부탁드립니다!


블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

이번 포스팅에서는 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

댓글을 달아 주세요


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

이번 포스팅에서는 SUM, AVG, MIN, MAX와 같은 집계함수들에 대해서 알아보면서 GROUP BY절HAVING절에 대해서 다뤄보도록 하겠습니다.


1. 집계함수


집계함수는 값에 대해서 특정 연산을 수행하는 함수들을 이야기합니다.

합계를 구하는 SUM함수나 평균을 구하는 AVG함수, 개수를 세는 COUNT함수 등이 대표적입니다.

그럼 아래와 같이 이전 포스팅에서 사용했던 SampleDB를 사용하여 몇가지 집계함수에 대해서 함께 알아보도록 하겠습니다.




- SUM


SUM함수는 말 그대로 합계를 구하는 함수 입니다.

employee 테이블에서 연봉의 합계를 구하고자 할 때와 같은 경우에 아래와 같이 사용할 수 있습니다.


SELECT SUM(salary) FROM employee;




그럼 이번에는 employee 테이블에서 commission의 합계를 구해볼까요?

하지만 commission에는 NULL값이 포함되어 있습니다. 이전에는 NULL에 대한 연산을 진행할 때 그 결과가 NULL이 나오게 되어서 ISNULL이라는 함수를 이용했습니다.

그럼 집계함수에서는 어떨지 한번 확인해볼까요?


SELECT SUM(commission) FROM employee;



위의 결과에서 볼 수 있듯이 집계함수에서의 NULL은 무시가 됩니다.



- AVG


이어서 AVG함수에 대해서 알아봅시다.

AVG함수는 평균을 구하는 함수입니다. 그럼 월급의 평균은 어떠한지 확인해보도록 하겠습니다.


SELECT AVG(salary) FROM employee;



AVG함수 또한 NULL값이 존재할 때에는 NULL값은 무시하고 평균을 구하게 됩니다.



- MIN, MAX


MIN과 MAX는 최대값과 최소값을 구하는 함수 입니다.

다음과 같이 월급의 최소값과 최대값을 구해보도록 하겠습니다.


SELECT MIN(salary), MAX(salary) FROM employee;




- COUNT


마지막으로 알아볼 COUNT 함수는 개수를 세는 함수입니다.

이때 COUNT 또한 NULL값은 무시하게 됩니다. 즉, 값이 존재하는 데이터에 대한 개수만 세는 것이죠.

따라서 salary의 COUNT값과 commission의 COUNT값은 서로 다릅니다.


SELECT COUNT(salary), COUNT(commission) FROM employee;





2. GROUP BY


그런데 만약, 직급별 월급 평균, 월급 합계 등 특정 그룹별로 집계함수를 사용하고 싶을때는 어떻게 할까요?

테이블에서 특정 그룹을 만들 수 있도록 하는 것이 바로 GROUP BY 절 입니다.


즉, GROUP BY 'A' 라고 한다면 A칼럼에서 같은 것끼리 묶어서 하나의 GROUP를 만드는 것이죠.


그럼 바로 GROUP BY를 활용해보도록 하겠습니다.

위에서 이야기한 바와 같이 직급별 월급 평균은 다음과 같이 구할 수 있습니다.


SELECT job 직급, AVG(salary) 월급평균

FROM employee GROUP BY job;



위와 같이 직급별로 GROUP이 되어 AVG함수의 값이 나오는 것을 볼 수 있습니다.


하지만 우리가 다음과 같이 SELECT 문을 구성한다면 어떻게 될까요?


SELECT empName 이름, job 직급, AVG(salary) 월급평균

FROM employee GROUP BY job;


위의 구문을 실행시키면 아래와 같이 오류가 발생하게 됩니다.



오류메세지를 읽으며 한번 왜 오류가 발생하는지 생각해봅시다.

우리가 출력하고자 하는 empName 값이 집계함수에도 없고, GROUP BY 절에도 없어서 사용할 수 없다고 하네요.

그럼 empName이 집계함수나 GROUP BY 절에 있어야 한다는 의미인데, 왜 그럴까요?


우리가 결과를 출력하는 입장이라고 한번 생각해봅시다.

empName이 없을 때는 이전의 결과와 같이 직급과 월급평균을 잘 출력할 수 있었습니다.

예를 들어 과장의 월급평균은 483이란 결과를 가져왔죠.

그런데 이때 해당 레코드(행)에 empName을 출력해야 한다면, 어떠한 empName을 출력해야 할까요?

즉, 과장 직급의 483의 평균월급이라는 레코드는 '과장'이라는 값으로 여러개의 레코드가 그룹이 되어 있는 레코드 입니다. 즉 해당 레코드에는 여러개의 empName이 존재하는 것이죠.

따라서 SQL에서는 그 여러개 중에서 하나를 출력할 수 없기에 오류가 발생하는 것입니다.

만약 empName이 집계함수를 적용시키면 집계함수에 따른 값이 나오겠죠, 물론 문자열 값이기에 또 다른 오류가 발생하겠지만..

또한 empName을 GROUP BY절에 다시 써주면 아래와 같이 job과 empName이 동일한 레코드끼리 묶여서 나오기 때문에 사실상 현재 데이터에서는 모든 데이터를 출력하는 것과 같게 됩니다.


SELECT empName 이름, job 직급, AVG(salary) 월급평균

FROM employee GROUP BY job, empName;




3. HAVING


이번 포스팅에서 마지막으로 알아볼 것은 HAVING절 입니다.

HAVING은 간단하게 말해서, 우리가 위에서 배운 GROUP BY절을 통해 만들어진 GROUP에 대해서 조건을 거는 것 입니다.


그런데 우리는 이전에 조건을 거는 WHERE절에 대해서 학습하였습니다.


WHERE절과 HAVING절의 차이는 무엇일까요?


기본적으로 WHERE절은 모든 레코드(행)에 대해서 조건을 적용하게 됩니다.

하지만 HAVING절은 GROUP BY절을 통해 만들어진 GROUP 들에만 조건을 적용하는 것이죠.


이를 잘 이해하지 못하면 추후에 HAVING과 WHERE 사용에 있어 헷갈릴 수 있습니다.


그럼 바로 사용해보도록 하겠습니다.

각 직급에 대해 3명 이상인 직급과 그 인원수를 출력해보도록 하겠습니다.


SELECT job 직급, COUNT(job) '직급 수' FROM employee

GROUP BY job HAVING COUNT(job) >= 3;



위와 같이 사용됩니다.


그럼 만약,

"월급이 300만원 이상인 사람들에 대해서 직급별로 2명이상인 직급과 그 직급의 월급 평균을 구해라."

라면 어떻게 해야 할까요?

위와 같은 경우에 바로 WHERE절과 HAVING절이 같이 사용하게 됩니다.


SELECT job 직급, AVG(salary) 월급평균 FROM employee

WHERE salary >= 300

GROUP BY job HAVING COUNT(job) >= 2;



추후에 더 많은 데이터들을 대상으로 복잡한 쿼리문을 다루게 되면 충분히 헷갈릴 수 있는 점들이니 꼭 스스로 쿼리문을 작성해보면서 이해하기를 바랍니다.



이렇게 해서 집계함수와 GROUP BY, HAVING 절에 대해서 알아보았습니다.

궁금하거나 잘못된 점은 언제든 댓글이나 이메일, 카카오톡을 통해서 문의주시면 감사하겠습니다.


블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

이번 포스팅에서는 최근 한국에서도 판매를 시작한,

AirPods2에 대해서 솔직한 리뷰를 남기고자 합니다.

이전에 AirPods1을 사용하지 않았던 점을 고려해서 봐주시고

추가적으로 궁금한점이 있으시면 언제든지 댓글 남겨주세요! 

최대한 빠르고 정확하게 안내드리도록 하겠습니다 :)



# 너무 다양했던 루머들

본격적인 제품 언박싱이나 후기보다 처음 AirPods2를 만났을 때 솔직한 감정을 먼저 이야기 해보자면,

정말 너무나 많은 루머들이 있지 않았나 싶습니다.

색상을 비롯해서 추가적인 기능들, 예를 들면 AirPods1에서 제공되지 않은 볼륨 컨트롤등에 대해 이야기가 많았는데 정말 모~두 다 루머였습니다. :-(


그냥 좋고 나쁨을 떠나서 딱 체감되는 차이점으로 AirPods2를 설명하자면,

무선 충전과 시리 호출이 되는 에어팟

정도가 되겠습니다.

(물론 유선충전모델을 사면 시리호출만..)


그럼 본격적으로 제품 언박싱과 솔직 후기를 만나보시죠 :)



# 제품 언박싱



글 작성중인 4월19일 금요일!

몇일전 에어팟2가 판매시작한 것을 확인했지만 배송예상기간이 너무 길어 픽업풀리길 기대하고 있었는데 마침 오늘 당일 픽업 가능으로 제품이 풀려서 즉시 결제하고 퇴근하자마자 후다닥 애플스토어로 달려갔습니다.


입구에서부터 너무나 설레고 행복한 이곳...


항상 사람이 많은 곳이기에 금요일저녁이라 더 많을 줄 알았지만..

시간이 조금 늦은 8시30분경이어서 그런지 심각하지는 않아서 너무 다행이었네요.

픽업할 때 여쭤보니 7시쯤에는 사람이 더 많았다고 해요..


아무튼! 도착 후에 바로 직원분께 픽업을 안내드리니 줄을 서야 된다고 안내해 주시더라구요.

기다리면서 다른분들 이야기 하는 걸 들어보니까 에어팟2 픽업때문에 오신분들이 대다수 였습니다.


에어팟2를 수령하고 집에 얼른와서 후다닥 언박싱..!





제품 박스에서부터 에어팟1과 전혀 다르지 않은 모습..



박스를 여니 등장한 에어팟2 입니다.

여기서 첫번째로 에어팟1과 차이점을 볼 수 있습니다.

에어팟1과 같은 경우는 케이스 후면, 힌지가 있는 부분에 LED가 있지만 에어팟2와 같은 경우는 케이스 전면부에 LED가 있습니다.

이는 아마 무선충전을 할때 해당 LED로 확인할 수 있도록 하기 위함 같습니다.




사실상 제품의 겉모습은 에어팟1과 너무나 동일하기 때문에 많은 사진을 첨부하지 않겠습니다.

굳이 차이점은 위에서 말씀드린 LED위치정도..

케이스없이 에어팟만 봤을 때는 차이를 알기 어려우니 혹시나 추후에 중고를 구입하시려는 분들은 에어팟에 작은 글씨로 적혀있는 모델명을 잘 확인해보셔야 합니다!



# 일단은 너무 좋다.


일단 첫번째로, 이전에 에어팟1을 사용하지 않았던 유저로써.. 너무 좋습니다.

뭐 가격이나 이런거에 대해서는 밑에서 논하도록 하고.. 단순히 에어팟제품과 만족도만 이야기하자면 정말 만족스럽습니다. 물론 돌아다니면서 몇일 더 사용해봐야 확실하겠지만, 현재는 너무나 만족스럽네요.
블루투스 이어폰이다 보니 페어링이 끊기는 현상만 없다면 전혀 불만 없이 사용할 것 같습니다.

현재 맥북과 아이폰과의 호환도 너무나 부드럽고, 아이폰 -> 맥북, 맥북 -> 아이폰으로 넘어가는 과정도 단 몇초면 진행되기 때문에 너무나 편리합니다.


기능적으로 보면 에어팟1과 차이를 보이는 '시리 호출'기능이 생각보다 편리합니다.

시리를 호출하고 반응했는지 기다렸다가 이야기하는 것이 아니라, "시리야 ~~~알려줘" 처럼 그냥 자연스럽게 이야기하면 다 듣고 반응합니다.

개인적으로는 그냥 아이폰에 말하는 것보다 시리 호출이 잘 되는 것 같습니다.

그래서 이어폰 끼고 생각보다 다양한 작업을 할 수 있습니다.


루머중에 하나였던, 볼륨 컨트롤 또한 시리를 호출하면서, "시리야 볼륨 두칸 높여줘" 라고 이야기하면 되서 어느정도 해결된 부분이라고 생각합니다.


무선충전에 대해서는 사바사인 것 같습니다.

저는 예전부터 무선충전 패드를 사용해서 편리하다고 생각하는 편입니다.

이게 유선에 비해 엄청난 차이가 있다기 보다는, 유선 충전도 하면서 자기 전에 가볍게 올려두거나 할 때 무선충전을 이용하는데, 이럴 때는 오히려 선의 걸리적 거림이 없이 기기만 들었다 놨다하면 되기에 편하다고 생각합니다.



# 난 에어팟1이 있다면 ?


하지만 에어팟1이 있는 분들에게는 굳이 에어팟2를 추천드리지 않습니다.

내부 칩도 바뀌고 페어링 속도 등에 대한 차이도 있다고 하지만 그런걸 감안해도, 에어팟1대신 25만원을 지불하면서까지 에어팟2로 넘어올 정도는 아니라고 생각합니다.



# 25만원의 가성비 ?


이건..정말 어려운 질문 같습니다.

개인이 가치를 얼만큼 두느냐의 차이기 때문에..

일단 저는 가성비가 좋다고는 못하지만 크게 부족하다고도 생각하지 않습니다.

일단은 다양한 블루투스 이어폰 중에서 에어팟과 같은 디자인을 선호합니다. 갤럭시의 버즈와 같은 디자인보다는 콩나물 같지만 아래로 길게 빠진 디자인을 선호하기 때문에, 가성비가 정말 좋다는 QCY 녀석들도 잘 쳐다보지 않았습니다.

스타트업 제품들 중에서는 에어팟과 비슷한 디자인을 몇번 봤는데 어느정도 괜찮다고 생각한 제품들의 가격대가 대략15만원 가량 되었습니다.

이러한 가격대를 보았을 때, 애플 제품군 끼리의 호환, 페어링 속도와 빼놓을 수 없는 애플 감성..을 생각하면 그래도 요새 너무나 비싸지고 있는 다른 애플 제품들에 비하면 아직 에어팟2는 나쁘지 않다고 생각합니다.. :)



# 장단점 정리


장점은 편리함, 빠른 페어링, 터치기능, 시리호출, 디자인으로 생각할 수 있습니다.

추가적으로 애플제품을 다양하게 사용하시는 분들은 제품간 느낄 수 있는 좋은 호환성정도..!


단점으로는 25만원의 가격과 개인적으로는 커널형이 아닌 오픈형 이어폰인 점..

일단 가격대가 비싼편이긴 하기에 그 점이 제일 큰 단점이고 제일 중요하게 고민해보셔야 합니다.

스스로의 지갑상태를 고려하고, 주변에 사용하시는 분들의 다양한 의견도 들어보심을 적극 추천합니다.

개인적으로 생각한 추가적인 단점은 오픈형이어폰인 점인데, 사실 제가 걱정했던 것보다는 오픈형 이어폰임에도 불구하고 귀에서 잘 빠지지 않고 소리가 잘 들리는 것 같습니다. 커널형만 고집하다 보니 굳이 단점으로 넣었지만, 오픈형 이어폰임을 두고 본다면 오히려 장점으로도 볼 수 있습니다.



사실 에어팟1과 너무나 큰 차이가 없기 때문에, 기능적으로 크게 이야기하지는 않았습니다.

개인적으로 느끼고 생각한 점들을 위주로 적었는데 이 외에도 궁금하신 점들이 있다면 얼마든지 댓글을 남겨주세요!

최대한 빠르게 답변드리도록 하겠습니다 :)


블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

이번 포스팅에서도 SELECT 문을 기반으로 한 심화적인 내용에 대해서 알아보겠습니다.

LIKE 연산자와 ORDER BY, TOP(n) 등에 대해서 함께 알아보도록 하겠습니다.



1. LIKE


만약, 다음과 같은 조건에 대한 데이터를 조회하려면 어떻게 해야 할까요?


"이씨 성인 사람의 데이터를 조회하라."

"이름에 '범'이 들어가는 사람의 데이터를 조회하라"


우리가 이전에 배운 내용들로 쉽게 할 수 있을까요?

위와 같이 정확하게 어떤 값인지 모르지만 검색이 필요한 경우가 있을 것입니다.

우리는 이럴때 검색을 용이하게 하기 위해서 와일드카드를 사용합니다.

와일드카드로는 '%'와 '_' 두 개의 특수 문자가 존재하는데 각각의 의미하는 바는 다음과 같습니다.


% : 문자가 없거나, 한개이상의 어떠한 문자도 대치함. 즉 0~무한대의 글자가 될 수 있음.

_ : 어떠한 문자가 와도 되지만 단 한글자를 의미함.


설명이 대략적으로 이해가 가시나요?

아래 예제 쿼리들을 통해 확인해보도록 합시다.


먼저, 이름의 성이 '이'씨인 사람들을 찾아보도록 합니다.


SELECT * FROM employee

WHERE empName LIKE '이%';



위와 같이 이름의 첫글자가 '이'로 시작되는 데이터들이 조회되었습니다.

그럼 이번엔 이름에 '범'이 들어가는 데이터를 조회해보도록 합시다.


SELECT * FROM employee

WHERE empName LIKE '%범%';



위와 같이 이름에 '범'이 들어가는 모든 데이터가 조회되었습니다.

그런데 이름의 가운데 글자가 '범'인 사람의 데이터를 조회하고 싶을 때는 어떻게 할까요?

위에서 사용된 '%' 말고, '_'를 사용하면 됩니다.


SELECT * FROM employee

WHERE empName LIKE '_범_';



이렇게 언더바(_) 와일드카드는 무조건 그 자리에 한글자의 문자가 존재해야 합니다.

하지만 퍼센트(%) 와일드카드는 0 ~ 무한대 글자를 포함하기 때문에 와일드카드 위치에 문자가 없어도 되고, 여러 문자가 있어도 되는 것이죠.


언더바와 퍼센트 와일드 카드의 차이점을 한번 더 확인해보도록 하죠.


SELECT * FROM employee

WHERE empName LIKE '장%';



SELECT * FROM employee

WHERE empName LIKE '장_';



언더바와 퍼센트의 차이를 아시겠나요?


또한 LIKE 연산자는 NOT을 붙여서 반대의 의미로도 사용할 수 있습니다.


예를 들어, 이름에 '우'가 들어가지 않는 사람들의 데이터를 조회하고자 한다면 다음과 같이 할 수 있습니다.


SELECT * FROM employee

WHERE empName NOT LIKE '%우%';





2. IS NULL / IS NOT NULL


이번에는 NULL 체크시에 자주 사용되는 IS NULL / IS NOT NULL에 대해서 알아보겠습니다.

IS NULL / IS NOT NULL 은 간단하니 바로 예제 쿼리를 보고 확인해보도록 하겠습니다.


SELECT * FROM employee

WHERE manager IS NULL;



위와 같이 사용됩니다.

또한 주로는 아래와 같이 특정 칼럼값이 NULL이 아닌 데이터들만 조회하는데 주로 사용됩니다.


SELECT * FROM employee

WHERE commission IS NOT NULL;





3. ORDER BY


이번에 배워볼 구문은 ORDER BY 로써, 정렬을 하는데 사용하는 것 입니다.

만약 salary 값의 오름차순으로 데이터를 정렬해서 보여달라고 할 땐 어떻게 할까요?

아래 쿼리와 같이 ORDER BY 를 사용하고 어떤 컬럼을 기준으로 할지 함께 적어주면 됩니다.


SELECT * FROM employee

ORDER BY salary ASC;



이때 ASC는 오름차순을 뜻하며, 내림차순은 DESC 입니다.

만약 둘 중 아무것도 적어주지 않으면  기본적으로 오름차순 정렬이 됩니다.


그런데 결과를 보시면 첫번째와 두번째행의 salary가 250으로 같습니다.

추가적으로 이럴경우에는 empNo의 내림차순으로 정렬하고 싶어한다면 어떻게 할까요?

다음과 같이 이전의 쿼리에서 ORDER BY 뒤에 두번째로 정렬할 기준은 무엇인지 함께 적어주면 됩니다.


SELECT * FROM employee

ORDER BY salary ASC, empNo DESC;





4. TOP(n)


이번 포스팅에서 마지막으로 알아볼 것은 TOP(n) 입니다. 

TOP(n)은 결과 데이터의 상위 n개만 보여주라는 명령어입니다.

이는 MS SQL Server에서의 특징인데, 다른 데이터베이스에서는 LIMIT등으로 표현되고는 합니다.

TOP은 이전과 다르게 FROM 이후에 적어주는 것이 아니라 다음 쿼리와 같이 SELECT 뒤에 적어줍니다.


SELECT TOP 4 * FROM employee

ORDER BY salary;



위와 같이 TOP 뒤에 오는 숫자는 괄호로 묶지 않아도 괜찮습니다.


그런데 기존의 데이터를 보면 salary가 300인 사람은 2명입니다.

위의 쿼리와 같이 결과를 확인하면 salary가 300으로 동점인 2명 중 한명만 출력되고 있는 것인데, 만약 위와 같이 동점이 존재할 때, 모든 동점자를 함께 출력하려면 TOP n 뒤에 WITH TIES 옵션을 같이 붙여주면 됩니다.


SELECT TOP 4 WITH TIES * FROM employee

ORDER BY salary;



위와 같이 salary가 300으로 동점인 2명이 함께 출력되는 것을 확인하실 수 있습니다.

또한 TOP 구문에서는 단순히 정수를 사용하는 것이 아니라 전체 데이터의 퍼센트만큼 출력할 행의 수를 지정할 수 있습니다. 

예를 들어 전체 데이터의 10퍼센트에 해당하는 개수의 데이터만 조회하고 싶다면 다음과 같이 TOP n 뒤에 PERCENT 옵션을 붙여주면 됩니다.


SELECT TOP 10 PERCENT * FROM employee

ORDER BY empNo;




이렇게 해서 LIKE연산자를 비롯해 ORDER BY, TOP n 등에 대해서 알아보았습니다.

다음 포스팅에서는 집계함수(sum, max, min, avg 등)에 대해서 알아보며 동시에 GROUP BY, HAVING 절에 대해서도 함께 알아보도록 하겠습니다.

블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요


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

지난 글에서 기초적인 SELECT문을 통해 데이터를 조회하는 방법에 대해서 알아보았습니다.

이번 포스팅에서는 SELECT문에 WHERE절을 추가하여 특정 조건에 부합하는 데이터들만 조회하는 방법에 대해서 알아보도록 하겠습니다.


데이터베이스 및 테이블들은 지난 글에서 만든 샘플데이터를 그대로 이용합니다. 해당 샘플 데이터가 없는 분들은 아래 글에서 쿼리를 통해 샘플데이터를 생성하고 진행해주세요.


[MS SQL Server] #5_SELECT문 기초



1. WHERE문 기초


먼저 아래와 같은 기본 SELECT문을 통해서 employee 테이블의 모든 데이터를 조회해보도록 합시다.


SELECT * FROM employee;


그런데 만약 위와 같이 모든 정보를 얻고 싶은 것이 아니라, job의 값이 '사원'인 데이터에 대해서만 조회하고 싶을 때는 어떻게 할까요?

또는 salary가 500 이상인 데이터만 조회하고 싶을 때는 어떻게 해야 할까요?


이와 같이 테이블에서 특정 조건에 부합하는 데이터만 조회하고 싶을 때 사용하는 것이 WHERE절 입니다.


그럼 바로 WHERE절을 사용해보도록 합시다.

먼저 job이 사원인 데이터만 조회하는 쿼리는 다음과 같습니다.


SELECT * FROM employee

WHERE job = '사원';



위의 결과와 같이 job이 '사원'인 데이터만 조회되었습니다.


그럼 같은 방법으로 salary가 500 이상인 데이터도 조회해보도록 합시다.


SELECT * FROM employee

WHERE salary >= 500;



위와 같이 salary가 500이상인 데이터만 조회되었습니다.


WHERE 절에서 사용되는 기본적인 연산자는 다음과 같습니다.


대소를 비교할 때 사용되는 >, <, >=, <= 와 같은 것들이 있으며,

같음을 나타내는 = 와 서로 다름을 나타내는 != 또는 <>이 있습니다.


그런데 여러개의 조건을 함께 사용해야 하는 경우는 어떻게 해야할까요?



2. 논리연산자(and / or)의 사용


여러개의 조건을 함께 사용해야 한다는 것은 다음과 같은 상황일 것 입니다.


'사장' 직급이 아니면서, salary가 500 이상인 데이터에 대해서 조회하시오. 또는

commission을 포함한 연봉이 5500만원 이하이거나 '사원' 직급인 데이터를 조회하시오.


이러한 경우는 어떻게 할까요?

바로 여러개의 조건을 동시에 사용하면 되는데, 이럴 때 사용하는 것이 논리 연산자 입니다.

and 논리 연산자는 좌,우측의 조건을 동시에 만족해야 할 때 사용되며,

or 논리 연산자는 좌,우측의 조건중 하나만 만족하면 될 때 사용합니다.


그럼 바로 쿼리문으로 확인해보도록 하겠습니다.

'사장' 직급이 아니면서, salary가 500 이상인 데이터에 대해서 조회하는 쿼리는 다음과 같습니다.


SELECT * FROM employee

WHERE job != '사장'

AND salary >= 500;



위와 같이 우리가 기대한 결과가 잘 나온 것을 확인할 수 있습니다.


그럼 이번에는 OR연산자를 이용해보도록 합시다.

commission을 포함한 연봉이 5500만원 이하이거나 '사원' 직급인 데이터를 조회하는 쿼리는 다음과 같습니다.


SELECT * FROM employee

WHERE (salary * 12) + isnull(commission,0) <= 5500

OR job = '사원';



위의 쿼리에서는 지난 시간에 배운 isnull() 함수를 이용하여 연봉을 계산하고 해당 값을 WHERE절 에서 그대로 사용하였습니다.

위와 같이 WHERE절에서 연산도 가능하니 참고하시기를 바랍니다.



3. BETWEEN A AND B


이번에 배워볼 것은 위와 같이 WHERE절에서 사용되는 BETWEEN A AND B 구문입니다.

만약 salary가 300이상 500이하인 데이터에 대해서 조회하려면 어떻게 해야할까요?

우리가 위에서 배운 내용대로라면, 아래와 같은 쿼리가 될 것입니다.


SELECT * FROM employee

WHERE salary >= 300

AND salary <= 500;


하지만 BETWEEN A AND B 구문을 이용하면 다음과 같이 쿼리를 작성하면 됩니다.


SELECT * FROM employee

WHERE salary BETWEEN 300 AND 500;



실제로 같은 결과를 가져오지만 이전의 쿼리보다 보다 간결하고 직관적임을 알 수 있습니다.



4. IN


마지막으로 배워볼 구문은 IN입니다.

만약 salary가 300, 400, 500, 600인 데이터를 조회하려면 어떻게 해야할까요?

이전의 학습을 기반으로 쿼리를 작성해보면 다음과 같습니다.


SELECT * FROM employee

WHERE salary = 300

OR salary = 400

OR salary = 500

OR salary = 600;


하지만 IN 구문을 이용하면 다음과 같이 쿼리를 작성할 수 있습니다.


SELECT * FROM employee

WHERE salary IN (300,400,500,600);



IN구문을 활용함으로써 OR을 통한 이전의 쿼리보다 더 간결하고 직관적임을 알 수 있습니다.



이번 포스팅에서는 WHERE 절을 이용해서 조건에 부합하는 데이터 조회방법에 대해서 알아보았습니다.

사실 WHERE절 없이 SELECT를 활용하는 경우보다는 WHERE절을 통해 원하는 데이터만 조회할 때가 더 많다고 생각됩니다. 따라서 위에 나온 쿼리문들 이외에도 다양하게 시도해보고 쿼리문을 만들어보시길 바랍니다.


블로그 이미지

Tigercow.Door

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

댓글을 달아 주세요