2025-08-16 13:14

Tags:

SQL SELECT 완벽 정복 데이터 추출의 첫걸음부터 전문가까지 (핸드북)

데이터가 ‘21세기의 원유’라고 불리는 시대입니다. 하지만 원유가 정제되지 않으면 그 가치를 발휘할 수 없듯, 데이터 역시 잘 추출하고 가공해야만 비로소 의미 있는 ‘정보’와 ‘인사이트’가 됩니다. 이때, 거대한 데이터 저장소(데이터베이스)에서 원하는 원유(데이터)를 정확하게 뽑아내는 시추 장비 역할이 바로 SELECT입니다.

SELECT는 SQL(Structured Query Language)에서 가장 먼저 배우고, 가장 많이 사용되는 명령어입니다. 단순히 데이터를 조회하는 것을 넘어, 우리가 원하는 형태로 필터링하고, 그룹화하고, 정렬하고, 여러 테이블의 정보를 엮어내는 등 무궁무진한 활용성을 가지고 있습니다. 이 핸드북을 통해 SELECT 문의 탄생 배경부터 기본 구조, 실전 활용법, 그리고 전문가 수준의 심화 내용까지 차근차근 정복해 보겠습니다.

1. SELECT의 탄생: 왜 필요할까요?

SELECT 문이 왜 필요한지 이해하기 위해 거대한 도서관을 상상해 봅시다. 이 도서관에는 수백만 권의 책(데이터)이 빼곡히 꽂혀 있습니다. 당신은 ‘19세기 영국 작가가 쓴, 500페이지 미만의 소설’을 찾고 싶습니다. 어떻게 해야 할까요?

만약 SELECT 문이 없다면, 당신은 도서관의 모든 서가를 처음부터 끝까지 뒤지며 책 한 권 한 권의 저자, 국적, 출판 연도, 페이지 수를 확인해야 할 겁니다. 상상만 해도 끔찍한 작업이죠.

이때, 사서에게 정확한 요청서를 작성해서 전달하는 것이 바로 SELECT 문을 사용하는 것과 같습니다.

  • SELECT: “책의 제목과 저자를 알려주세요.” (어떤 정보를 원하는가)

  • FROM: “소설 코너에서 찾아주세요.” (어디에서 찾을 것인가)

  • WHERE: “단, 저자의 국적은 ‘영국’이고, 출판 시대는 ‘19세기’이며, 페이지 수는 ‘500 미만’인 책만요.” (어떤 조건에 맞는 것을 찾을 것인가)

이처럼 SELECT 문은 방대한 데이터 속에서 헤매지 않고, 명확한 규칙과 조건으로 원하는 데이터에 정확하게 접근하기 위해 탄생했습니다. 데이터를 ‘탐색’하는 비효율을 ‘질의’하는 효율로 바꿔준 혁신적인 도구인 셈입니다.

2. SELECT 문의 해부: 기본 구조와 문법

SELECT 문은 여러 개의 ‘절(Clause)‘이 모여 하나의 완성된 문장을 이룹니다. 각 절은 저마다의 역할이 있으며, 정해진 순서에 따라 작성해야 합니다.

가장 기본적이고 완전한 형태의 SELECT 문 구조는 다음과 같습니다.

SELECT   -- 1. 어떤 '컬럼(열)'의 데이터를 가져올지 지정합니다.
FROM     -- 2. 어떤 '테이블'에서 데이터를 가져올지 지정합니다.
WHERE    -- 3. 어떤 '조건'에 맞는 데이터(행)만 필터링할지 지정합니다.
GROUP BY -- 4. 특정 컬럼을 기준으로 데이터를 '그룹화'합니다.
HAVING   -- 5. 그룹화된 결과에 대해 '조건'을 적용하여 필터링합니다.
ORDER BY -- 6. 최종 결과를 어떤 순서로 '정렬'할지 지정합니다.
LIMIT    -- 7. 최종 결과 중 몇 개의 '행'만 가져올지 지정합니다.

물론 이 모든 절을 항상 다 사용해야 하는 것은 아닙니다. 가장 기본적인 SELECT 문은 SELECTFROM 절만으로 구성됩니다. 필요에 따라 다른 절들을 추가하여 점점 더 정교한 요청을 만들어가는 방식입니다.

3. SELECT 실전 활용: 절(Clause)별 사용법 마스터하기

이제 각 절이 실제로 어떻게 사용되는지 구체적인 예시와 함께 살펴보겠습니다. employees라는 이름의 직원 정보 테이블이 있다고 가정해 봅시다.

employees 테이블 예시: | id | name | department | salary | hire_date | |----|------|------------|--------|-----------| | 1 | 김철수 | 개발팀 | 7000 | 2020-01-15| | 2 | 박영희 | 기획팀 | 7500 | 2019-03-10| | 3 | 이민준 | 개발팀 | 9000 | 2018-05-20| | 4 | 최지우 | 디자인팀 | 6200 | 2021-07-30| | 5 | 정다솜 | 개발팀 | 5800 | 2022-11-01|

SELECTFROM: 기본 중의 기본

  • 모든 컬럼 조회 (*): 테이블의 모든 정보를 보고 싶을 때 사용합니다.

    SELECT * FROM employees;
    
  • 특정 컬럼만 조회: 원하는 컬럼만 지정하여 불필요한 데이터 조회를 줄일 수 있습니다. 이는 성능 향상에 매우 중요합니다.

    SELECT name, department FROM employees;
    
  • 별칭(Alias) 사용 (AS): 컬럼이나 테이블 이름을 임시로 바꿔 부를 때 사용합니다. 결과를 더 명확하게 만들거나, 쿼리가 복잡해질 때 가독성을 높여줍니다. AS는 생략 가능합니다.

    SELECT name AS "직원 이름", salary AS "연봉" FROM employees;
    

WHERE: 내가 원하는 데이터만 콕 집어내기

WHERE 절은 가장 강력하고 자주 사용되는 필터입니다. 다양한 연산자를 조합하여 복잡한 조건도 만들어낼 수 있습니다.

  • 비교 연산자: = (같다), > (크다), < (작다), >= (크거나 같다), <= (작거나 같다), != 또는 <> (다르다)

    -- 연봉이 7000 이상인 직원 조회
    SELECT * FROM employees WHERE salary >= 7000;
    
  • 논리 연산자: AND (두 조건 모두 만족), OR (둘 중 하나라도 만족), NOT (조건을 만족하지 않음)

    -- 개발팀 소속이면서 연봉이 6000 이상인 직원
    SELECT * FROM employees WHERE department = '개발팀' AND salary >= 6000;
    
  • 기타 유용한 연산자:

    • IN: 여러 값 중 하나와 일치하는 경우

      -- 기획팀 또는 디자인팀 소속 직원
      SELECT * FROM employees WHERE department IN ('기획팀', '디자인팀');
      
    • BETWEEN a AND b: a와 b 사이의 값인 경우 (경계값 포함)

      -- 연봉이 6000에서 8000 사이인 직원
      SELECT * FROM employees WHERE salary BETWEEN 6000 AND 8000;
      
    • LIKE: 문자열 패턴이 일치하는 경우 (%: 여러 문자, _: 한 문자)

      -- '김'씨 성을 가진 직원
      SELECT * FROM employees WHERE name LIKE '김%';
      
    • IS NULL / IS NOT NULL: 값이 없는(NULL) 경우 또는 없는 게 아닌 경우

      -- 부서가 지정되지 않은 직원 (이 예시에는 없지만)
      SELECT * FROM employees WHERE department IS NULL;
      

GROUP BY와 집계 함수: 데이터 요약 및 통계의 기술

GROUP BY는 특정 컬럼의 값이 같은 행들을 하나의 그룹으로 묶어주는 역할을 합니다. 주로 집계 함수와 함께 사용되어 그룹별 통계를 낼 때 강력한 힘을 발휘합니다.

  • 주요 집계 함수:

    • COUNT(): 행의 개수를 셉니다.

    • SUM(): 숫자 값의 합계를 구합니다.

    • AVG(): 숫자 값의 평균을 구합니다.

    • MAX() / MIN(): 최대값 / 최소값을 구합니다.

  • 사용 예시:

    -- 각 부서별 직원 수와 평균 연봉 계산
    SELECT
        department,
        COUNT(*) AS employee_count, -- 부서별 직원 수
        AVG(salary) AS avg_salary    -- 부서별 평균 연봉
    FROM
        employees
    GROUP BY
        department;
    

    결과: | department | employee_count | avg_salary | |------------|----------------|------------| | 개발팀 | 3 | 7266.67 | | 기획팀 | 1 | 7500.00 | | 디자인팀 | 1 | 6200.00 |

    GROUP BY의 핵심 규칙!: GROUP BY를 사용할 때 SELECT 절에는 GROUP BY에 사용된 컬럼과 집계 함수만 올 수 있습니다. ‘어떤 그룹’인지 명시하거나, ‘그룹의 통계값’을 보여주는 것만 가능하기 때문입니다.

HAVING: 그룹에 대한 필터링

WHEREHAVING은 둘 다 ‘조건’을 건다는 점에서 헷갈리기 쉽습니다. 하지만 명확한 차이가 있습니다.

  • WHERE: 그룹화하기 , 즉 원본 테이블의 개별 행들을 필터링합니다.

  • HAVING: 그룹화가 끝난 후, 집계 함수를 통해 나온 결과 그룹들을 필터링합니다.

-- 직원 수가 2명 이상인 부서의 평균 연봉을 조회
SELECT
    department,
    COUNT(*) AS employee_count,
    AVG(salary) AS avg_salary
FROM
    employees
GROUP BY
    department
HAVING
    COUNT(*) >= 2; -- 그룹화 결과(직원 수)에 대한 조건

위 쿼리에서 WHERE COUNT(*) >= 2는 문법 오류입니다. WHERE 절은 아직 그룹이 만들어지기 전에 실행되므로 COUNT(*)라는 그룹 통계치를 알 수 없기 때문입니다.

ORDER BY: 결과 보기 좋게 정렬하기

최종 결과를 특정 컬럼 기준으로 정렬합니다.

  • ASC (Ascending): 오름차순 (기본값, 생략 가능)

  • DESC (Descending): 내림차순

-- 연봉이 높은 순으로 직원들을 정렬하고, 연봉이 같다면 입사일이 빠른 순으로 정렬
SELECT name, salary, hire_date
FROM employees
ORDER BY salary DESC, hire_date ASC;

4. 논리적 처리 순서의 비밀: SQL은 어떻게 생각할까?

우리가 SELECT 문을 작성하는 순서와, 데이터베이스가 실제로 쿼리를 이해하고 실행하는 순서는 다릅니다. 이 논리적 처리 순서를 이해하면 왜 어떤 쿼리는 되고 어떤 쿼리는 안 되는지 명확하게 알 수 있습니다.

SQL의 실제 생각 순서 (논리적 처리 순서):

  1. FROM: 어떤 테이블을 사용할지 결정합니다.

  2. WHERE: 테이블에서 어떤 행을 가져올지 필터링합니다.

  3. GROUP BY: 필터링된 행들을 그룹으로 묶습니다.

  4. HAVING: 생성된 그룹들 중 어떤 그룹을 남길지 필터링합니다.

  5. SELECT: 최종적으로 어떤 컬럼을 결과로 보여줄지 결정합니다.

  6. ORDER BY: 최종 결과를 정렬합니다.

  7. LIMIT: 정렬된 결과 중 일부만 보여줍니다.

이 순서 때문에 발생하는 대표적인 사례가 바로 SELECT 절에서 만든 별칭(Alias)을 WHERE 절에서 사용할 수 없는 이유입니다.

-- 잘못된 쿼리 예시
SELECT salary * 1.1 AS increased_salary
FROM employees
WHERE increased_salary > 8000; -- 에러 발생!

위 쿼리는 에러가 발생합니다. 왜냐하면 SQL은 2번 WHERE 절을 먼저 처리하는데, 이때는 아직 5번 SELECT 절이 실행되지 않아 increased_salary라는 별칭이 무엇인지 모르기 때문입니다.

반면, ORDER BY 절에서는 별칭을 사용할 수 있습니다. ORDER BYSELECT 절이 실행된 이후에 처리되기 때문이죠.

-- 올바른 쿼리 예시
SELECT salary * 1.1 AS increased_salary
FROM employees
ORDER BY increased_salary DESC; -- 정상 작동!

5. SELECT 심화: 전문가로 가는 길

기본기를 다졌다면, 이제 SELECT 문을 한 차원 높여줄 심화 기법들을 알아볼 시간입니다.

JOIN: 흩어진 데이터 조각을 하나로

실제 데이터는 여러 테이블에 나뉘어 저장되는 경우가 대부분입니다. 예를 들어 employees 테이블에는 직원 정보가, departments 테이블에는 부서 정보(부서 위치 등)가 따로 저장될 수 있습니다. JOIN은 이처럼 관련된 테이블들을 특정 기준(키)으로 연결하여 하나의 큰 테이블처럼 조회하게 해주는 강력한 기능입니다.

  • INNER JOIN: 두 테이블에 공통으로 존재하는 데이터만 연결합니다.

  • LEFT JOIN: 왼쪽 테이블의 모든 데이터를 기준으로, 오른쪽 테이블에 일치하는 데이터가 있으면 연결하고 없으면 NULL로 표시합니다.

② 서브쿼리(Subquery): 쿼리 속의 또 다른 쿼리

SELECT, FROM, WHERE 등 다른 쿼리 절 안에 포함된 SELECT 문을 의미합니다. 복잡한 조건을 걸거나, 다른 쿼리의 결과를 기반으로 새로운 쿼리를 작성할 때 유용합니다.

-- 평균 연봉보다 많은 연봉을 받는 직원 조회
SELECT name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees); -- WHERE 절에 사용된 서브쿼리

③ 공통 테이블 표현식 (CTE, Common Table Expression): WITH

서브쿼리가 너무 많아지거나 복잡해지면 쿼리의 가독성이 급격히 떨어집니다. CTE는 WITH 키워드를 사용하여 복잡한 서브쿼리를 마치 임시 테이블처럼 정의하고, 이후 쿼리에서 해당 이름을 참조하여 사용할 수 있게 해주는 기능입니다. 쿼리를 논리적인 단계로 나눌 수 있어 훨씬 깔끔하고 이해하기 쉬운 코드를 작성할 수 있습니다.

-- CTE를 사용하여 부서별 평균 연봉을 구하고,
-- 그 평균 연봉보다 많이 받는 직원들을 조회
WITH DepartmentAvgSalary AS (
    SELECT department, AVG(salary) as avg_sal
    FROM employees
    GROUP BY department
)
SELECT e.name, e.department, e.salary
FROM employees e
JOIN DepartmentAvgSalary das ON e.department = das.department
WHERE e.salary > das.avg_sal;

결론: SELECT는 데이터와의 대화법입니다.

지금까지 우리는 SELECT 문이라는 강력한 도구를 사용하는 방법을 아주 기초적인 부분부터 전문가 수준의 내용까지 폭넓게 살펴보았습니다. SELECT 문은 단순히 데이터를 꺼내보는 행위를 넘어, 데이터를 내가 원하는 관점대로 분석하고, 요약하고, 재구성하는 데이터와의 대화법입니다.

이 핸드북에서 다룬 내용들을 꾸준히 연습하고 실제 데이터에 적용해 보세요. 처음에는 문법이 낯설고 어떤 절을 써야 할지 막막할 수 있지만, “어떤 데이터를, 어디서, 어떤 조건으로 가져오고 싶은가?”를 차분히 생각하며 문장을 만들어나가다 보면 어느새 데이터베이스를 자유자재로 다루는 자신의 모습을 발견하게 될 것입니다.

데이터의 잠재력을 깨우는 열쇠, SELECT 문을 이제 여러분의 손에 쥐여드렸습니다. 마음껏 데이터를 탐험하고 새로운 인사이트를 발견하는 즐거움을 누리시길 바랍니다!

레퍼런스(References)

SELECT