IT's Jenna

8. SELECT를 공략하자 본문

Study/관계형 데이터 베이스 실전 입문

8. SELECT를 공략하자

developer Jenna 2021. 6. 2. 19:03
SELECT의 역할
다양한 SELECT 활용법
관계형이 아닌 조작
들여 쓰기

1. SELECT의 역할

이 책에서 SELECT는 SQL의 심장부라고 설명한다. 백엔드 엔지니어로 근무하고 있는 나도 이 말에 전적으로 동의하는 바이다! SELECT는 RDB에서 데이터를 가져오는 유일한 수단이다.

 

SELECT의 기본 구조는 다음과 같다.

SELECT 칼럼의 목록
FROM 테이블의 목록
WHERE 검색 조건

 

2. 다양한 SELECT 활용법

SELECT가 단순히 데이터만 불러오는 것이라면 SQL의 심장부라고 할 수 없었을 것이다. SELECT에는 기본형 외에도 다양한 활용법이 존재한다.

 

a. 집계 함수

SELECT문에 집계 함수가 있으면 SELECT의 결과가 데이터 자체가 아니라 집계 결과가 된다. 여러 가지 집계 함수의 종류를 살펴보자.

Table. student

  • CONCAT

CONCAT 함수는 결과 문자열을 이어서 하나의 열로 데이터를 출력한다.

SELECT CONCAT(name, ':', department)
FROM sys.student
WHERE department = '컴공';

 

  • COUNT

COUNT 함수는 집계 결과의 개수를 하나의 행으로 출력한다.

SELECT COUNT(*)
FROM sys.student
WHERE department = '컴공';

COUNT 함수는 다른 집계 함수와 달리 반환 값에 NULL이 없다. 다른 집계 함수들은 아래 AVG와 같이 결과가 공집합인 경우 NULL을 반환한다.

SELECT AVG(age)
FROM sys.student
WHERE grade = 5;

하지만 COUNT 함수는 공집합의 결과로 0을 반환한다. 결과값으로 NULL을 받을 우려가 없기 때문에 가장 많이 사용되는 집계 함수이다.

SELECT AVG(age)
FROM sys.student
WHERE grade = 5;

 

  • GROUP BY

테이블 전체를 대상으로 하지 않고 특정 항목별로 집계하고 싶을 때 사용한다. 아래는 GROUP BY를 이용해서 학과별로 학생수를 집계한 결과이다.

SELECT department, COUNT(*)
FROM student
GROUP BY department;

  • HAVING

GROUP BY를 사용할 때 함께 사용할 수 있는 조건절 HAVING이 있다. 아래는 학생수가 2명 미만인 학과의 목록만 출력하는 쿼리문이다.

SELECT department, COUNT(*)
FROM student
GROUP BY department
HAVING COUNT(*) < 2;

그렇다면 WHERE과 HAVING의 차이점은 무엇일까? WHERE 절은 집계 전 기존의 행에 대해서 조건을 지정하고 HAVING은 집계 결과에 대해서 조건을 지정한다.

SELECT department, COUNT(*)
FROM student
WHERE grade IN (1)
GROUP BY department
HAVING COUNT(*) < 2;

  • ORDER BY

ORDER BY는 데이터의 정렬을 위한 함수이다. 기본적으로 오름차순이므로 ASC는 생략 가능하고 내림차순으로 정렬하기 위해선 DESC를 붙여준다.

 

GROUP BY, HAVING, ORDER BY 세 개의 절을 함께 쓸 때는 GROUP BY, HAVING, ORDER BY 순서대로 사용해야 한다. 즉, GROUP BY로 지정된 칼럼 별로 집게 하고 그 결과를 HAVING 조건으로 필터링한 다음 ORDER BY 순으로 정렬한다는 의미이다.

SELECT department, COUNT(*)
FROM sys.student
GROUP BY department
HAVING COUNT(*) < 2
ORDER BY department DESC;


b. 서브 쿼리

서브 쿼리는 형식상 SELECT문 내애서 SELECT문을 중첩하여 사용하는 것이다. 이때 서브쿼리는 반환 결과는 스칼라, 행, 테이블과 같이 자유롭게 변화할 수 있다. 서브쿼리의 결과 유형을 살펴보자.

 

  • 테이블 서브쿼리

테이블 서브쿼리는 쿼리의 결과가 테이블 형태로 나오는 것이다. IN, ANY, ALL 등을 예로 들 수 있다. 아래 테이블과 쿼리를 보자.

 

Table. courses
Table.course_registration
Table. student

아래는 IN절 안에 서브 쿼리를 달아서 WHERE절에 조건을 걸어준 경우이다.

SELECT *
FROM sys.course_registration
WHERE (department, course) IN (
SELECT department, course
FROM courses
WHERE minimum_grade >= 2)

집계 결과에 관해 또 다른 집계를 할 때는 FROM절 안에 서브 쿼리를 달아주는 것이 일반적이다. 아래는 각 학과별 학생수를 구한 후 전체 학과의 평균 학생수를 집계한 것이다.

SELECT AVG(t1.c) FROM (
SELECT COUNT(*) AS c
FROM sys.student
GROUP BY department) t1;

EXISTS 서브 쿼리는 서브 쿼리의 결과에 행이 하나라도 존재하는지 여부를 확인한다. 아래 예시는 수업을 하나도 이수하지 않은 학생을 찾는 쿼리문이다.

SELECT name, department
FROM sys.student
WHERE NOT EXISTS (
SELECT *
FROM sys.course_registration
WHERE student_name = sys.student.name);

 

  • 스칼라 서브쿼리

스칼라 서브 쿼리는 서브 쿼리의 결과가 스칼라 즉, 1행 1열 이어야 한다. 결과값이 스칼라가 아니면 쿼리에 오류가 발생한다.

 

WHERE절 안에 서브 쿼리가 스칼라 결과를 도출하는 예제를 살펴보자. 서브쿼리는 하나의 숫자인 스칼라를 반환하고 그 값을 조건으로 데이터를 불러왔다.

SELECT name, age
FROM sys.student s1
WHERE age = (
SELECT max(age)
FROM sys.student s2); 

아래는 HAVING절 안에서 서브 쿼리를 구하는 예제이다. 각 코스별로 COUNT가 1이기 때문에 평균값 1인 스칼라 값이 HAVING절 내에서 반환된다.

SELECT course, COUNT(*) AS COUNT
FROM sys.course_registration
GROUP BY course
HAVING COUNT(*) >= (
SELECT AVG(c)
FROM(
SELECT COUNT(*) AS c
FROM sys.course_registration
GROUP BY course) AS t);

 

  • 행 서브 쿼리

행 서브 쿼리는 서브쿼리의 결과가 1행이고 열이 여러개인 경우이다. 예를 들어 WHERE절에서는 여러개의 칼럼을 괄호로 묶어 동시에 비교할 수 있는데 이런 경우에 행 서브쿼리 결과를 받아서 사용하면 유용하다. 

WHERE (col1, col2) = (val1, val2)

 

3. 관계형이 아닌 조작

우리는 1장에서 릴레이션의 기본 연산에 대해서 배웠다. 이러한 기본 연산은 아래 표와 같이 모두 SELECT로 표현할 수 있다. 

그렇다면 표에 포함되지 않은 SELECT의 표현들은 무엇일까? SQL은 관계형 모델이 아닌 조작에 대해서도 대응할 수 있다. 앞서 배운 서브 쿼리를 사용하면 SQL 상에서 연산의 표현력을 비약적으로 증가시키며 그 모든 연산이 릴레이션 연산에 대응하지 않는다.

 

예를들어 ORDER BY 절을 이용한 집합의 정렬은 관계형 모델엔 존재하지 않는다. 수학적으로 각 집합의 요소에는 순서가 없기 때문이다. 

 

  • 관계형이 아닌 조작의 취급법

위에서 언급한 것처럼 SELECT는 관계형인 조작과 관계형이 아닌 조작의 복합체이다. 여기서 중요한 것은 관계형 조작이 늘 우선시되어야 한다는 것이다. 관계형 모델의 범위에서 연산이 가능하면 옵티마이저는 교환 법칙이나, 결합 법칙, 분배 법칙 등 집합 연산의 법칙을 실행할 수 있고 쿼리문 최적화가 더욱 용이해진다. 릴레이션이 아닌 연산의 요소가 쿼리에 포함되어 있으면 옵티마이저가 선택할 수 있는 효율적인 실행 계획이 현저히 줄어든다는 것을 유의하자!

 

4. 들여 쓰기

마지막으로 SELECT문을 사용할 때 필요한 것이 들여 쓰기 규칙이다. 들여쓰기는 복잡한 구조의 SELECT문을 파악하기 용이하게 만들어주며 디버깅에도 효과적이다. 들여쓰기 규칙은 다음과 같다.

  • SELECT 절은 들여쓰기하지 않는다.
  • UNION 절은 그다음 SELECT와 함께 작성한다.
  • 칼럼은 한 줄마다 작성한다.
  • 칼럼의 리스트는 네 개의 공백으로 들여 쓰기 한다.
  • FROM절, WHERE 절은 두 개의 공백으로 들여 쓰기 한다.
  • FROM절의 테이블 목록, WHERE 절의 검색 조건 목록은 한 줄씩 작성한다.
  • 서브 쿼리의 괄호는 각각 한 줄로 작성한다.
SELECT
	department,
    (
      SELECT
        COUNT(*)
	  FROM
        student
	  WHERE
        department = t1.department
	) AS COUNT
  FROM
    (
      SELECT
        DISTINCT department
	  FROM student
	)

이번장에서는 SELECT의 기본 구조와 활용법에 대해 알아보았다. SELECT는 데이터를 처리하는데 만능인만큼 훨씬 복잡해질 수 있다. 다양한 SELECT문을 이해하고 적용할 수 있다면 업무에 엄청난 도움이 될 것이다!

'Study > 관계형 데이터 베이스 실전 입문' 카테고리의 다른 글

10. 그래프에 맞서다  (0) 2021.06.30
9. 이력 데이터와 친해지기  (0) 2021.06.28
7. NULL과의 싸움  (0) 2021.06.01
6. 도메인 설계 전략  (0) 2021.05.28
5. 릴레이션의 직교성  (0) 2021.05.27
Comments