PostgreSQL 고급 쿼리 작성

PostgreSQLBeginner
지금 연습하기

소개

이 랩에서는 고급 기술을 탐구하여 PostgreSQL 쿼리 작성 능력을 향상시킬 것입니다. 다른 쿼리의 결과를 기반으로 데이터를 필터링하기 위해 WHERE 절 내에서 서브쿼리를 사용하는 방법을 배우게 됩니다.

이 랩은 쿼리 가독성과 모듈성을 향상시키기 위해 CTE (Common Table Expressions) 를 정의하고 활용하는 과정을 안내합니다. 또한, ROW_NUMBER와 같은 윈도우 함수를 적용하여 행 집합 전체에서 계산을 수행합니다. 마지막으로, 데이터 세트에서 의미 있는 통찰력을 추출하기 위해 GROUP BYHAVING 절을 사용하여 데이터를 그룹화하고 필터링하는 방법을 숙달할 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 중급 레벨의 실험이며 완료율은 79%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

WHERE 절에서 하위 쿼리 작성

이 단계에서는 SQL 쿼리의 WHERE 절 내에서 서브쿼리를 사용하는 방법을 배우게 됩니다. 서브쿼리는 내부 쿼리 또는 중첩 쿼리라고도 하며, 다른 쿼리 내에 포함된 쿼리입니다. 서브쿼리는 메인 쿼리에서 검색할 데이터를 추가로 제한하는 조건으로 사용될 데이터를 반환하는 데 사용됩니다.

WHERE 절에서 서브쿼리 이해하기

WHERE 절의 서브쿼리는 일반적으로 열의 값을 서브쿼리의 결과와 비교하는 데 사용됩니다. 서브쿼리가 먼저 실행되고, 그 결과는 외부 쿼리에서 사용됩니다.

기본 구문:

SELECT column1, column2
FROM table_name
WHERE column_name IN (SELECT column_name FROM another_table WHERE condition);

여기서 서브쿼리 (SELECT column_name FROM another_table WHERE condition)는 값 집합을 반환합니다. 그런 다음 외부 쿼리는 table_name에서 column_name이 해당 집합에 있는 행을 선택합니다.

시나리오:

employeesdepartments의 두 테이블이 있다고 가정해 보겠습니다. employees 테이블에는 employee_id, employee_name, department_id를 포함한 직원 정보가 포함되어 있습니다. departments 테이블에는 department_iddepartment_name을 포함한 부서 정보가 포함되어 있습니다.

'Sales' 부서에서 근무하는 모든 직원을 찾고 싶습니다.

1 단계: 테이블 생성 및 데이터 삽입

먼저 postgres 사용자로 PostgreSQL 데이터베이스에 연결합니다.

sudo -u postgres psql

다음으로 departments 테이블을 생성합니다.

CREATE TABLE departments (
    department_id SERIAL PRIMARY KEY,
    department_name VARCHAR(50)
);

departments 테이블에 샘플 데이터를 삽입합니다.

INSERT INTO departments (department_name) VALUES
('Sales'),
('Marketing'),
('Engineering');

이제 employees 테이블을 생성합니다.

CREATE TABLE employees (
    employee_id SERIAL PRIMARY KEY,
    employee_name VARCHAR(50),
    department_id INTEGER REFERENCES departments(department_id)
);

employees 테이블에 샘플 데이터를 삽입합니다.

INSERT INTO employees (employee_name, department_id) VALUES
('Alice', 1),
('Bob', 2),
('Charlie', 1),
('David', 3),
('Eve', 2);

2 단계: 서브쿼리 작성

이제 WHERE 절에서 서브쿼리를 사용하여 'Sales' 부서에서 근무하는 모든 직원을 찾는 쿼리를 작성해 보겠습니다.

SELECT employee_name
FROM employees
WHERE department_id IN (SELECT department_id FROM departments WHERE department_name = 'Sales');

설명:

  • 서브쿼리 (SELECT department_id FROM departments WHERE department_name = 'Sales')department_name이 'Sales'인 departments 테이블에서 department_id를 선택합니다. 이 경우 1을 반환합니다.
  • 외부 쿼리는 서브쿼리에서 반환된 집합 (단지 1임) 에 department_id가 있는 employees 테이블에서 employee_name을 선택합니다.

3 단계: 쿼리 실행 및 결과 보기

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 employee_name
---------------
 Alice
 Charlie
(2 rows)

이는 Alice 와 Charlie 가 'Sales' 부서에서 근무하는 직원임을 보여줍니다.

4 단계: 서브쿼리와 함께 EXISTS 사용하기

WHERE 절에서 서브쿼리를 사용하는 또 다른 방법은 EXISTS 연산자를 사용하는 것입니다. EXISTS 연산자는 서브쿼리에 행이 있는지 테스트합니다. 서브쿼리가 행을 반환하면 true 를 반환하고, 그렇지 않으면 false 를 반환합니다.

EXISTS를 사용하여 동일한 결과를 얻는 예는 다음과 같습니다.

SELECT employee_name
FROM employees e
WHERE EXISTS (
    SELECT 1
    FROM departments d
    WHERE d.department_id = e.department_id
    AND d.department_name = 'Sales'
);

이 쿼리는 이전 쿼리와 동일한 결과를 얻지만 IN 대신 EXISTS 연산자를 사용합니다.

설명:

  • 서브쿼리 SELECT 1 FROM departments d WHERE d.department_id = e.department_id AND d.department_name = 'Sales'는 'Sales'라는 이름의 부서가 현재 직원의 department_id와 동일한 department_id를 가지고 있는지 확인합니다.
  • 서브쿼리가 행을 반환하면 (즉, 동일한 department_id를 가진 'Sales' 부서가 있는 경우) EXISTS 연산자는 true 를 반환하고 직원의 이름이 선택됩니다.

psql 터미널에서 쿼리를 실행합니다. 이전과 동일한 출력이 표시되어야 합니다.

 employee_name
---------------
 Alice
 Charlie
(2 rows)

이제 WHERE 절에서 서브쿼리를 사용하여 다른 테이블의 조건을 기반으로 데이터를 성공적으로 필터링했습니다. 또한 서브쿼리와 함께 EXISTS 연산자를 사용하는 방법을 배웠습니다.

CTE 정의 및 사용

이 단계에서는 PostgreSQL 에서 CTE (Common Table Expression) 를 정의하고 사용하는 방법을 배우게 됩니다. CTE 는 단일 SELECT, INSERT, UPDATE 또는 DELETE 문 내에서 참조할 수 있는 임시 명명된 결과 집합입니다. CTE 는 복잡한 쿼리를 더 간단하고 읽기 쉬운 부분으로 나누는 데 유용합니다.

CTE 이해하기

CTE 는 WITH 절을 사용하여 정의됩니다. CTE 는 쿼리 실행 기간 동안만 존재합니다.

기본 구문:

WITH cte_name AS (
    SELECT column1, column2
    FROM table_name
    WHERE condition
)
SELECT column1, column2
FROM cte_name
WHERE condition;

여기서 cte_name은 CTE 에 지정하는 이름입니다. 괄호 안의 SELECT 문은 CTE 의 결과 집합을 정의합니다. 그런 다음 외부 SELECT 문은 일반 테이블인 것처럼 CTE 를 쿼리합니다.

시나리오:

이전 단계의 employeesdepartments 테이블을 계속 사용하면서 CTE 를 사용하여 직원의 이름과 해당 부서 이름을 찾아보겠습니다.

1 단계: 테이블 및 데이터 확인

employeesdepartments 테이블이 존재하고 이전 단계의 데이터를 포함하는지 확인합니다. psql 터미널에서 다음 쿼리를 실행하여 확인할 수 있습니다.

SELECT * FROM departments;
SELECT * FROM employees;

테이블 또는 데이터가 누락된 경우 이전 단계를 참조하여 테이블을 생성하고 데이터를 삽입하십시오.

2 단계: CTE 정의

이제 EmployeeDepartments라는 CTE 를 정의하여 employeesdepartments 테이블을 조인하여 직원 이름과 해당 부서 이름을 검색해 보겠습니다.

WITH EmployeeDepartments AS (
    SELECT
        e.employee_name,
        d.department_name
    FROM
        employees e
    JOIN
        departments d ON e.department_id = d.department_id
)
SELECT employee_name, department_name
FROM EmployeeDepartments;

설명:

  • WITH EmployeeDepartments AS (...) 절은 EmployeeDepartments라는 CTE 를 정의합니다.
  • 괄호 안의 SELECT 문은 department_id 열에서 employees 테이블 (별칭 e) 을 departments 테이블 (별칭 d) 과 조인합니다.
  • 그런 다음 외부 SELECT 문은 EmployeeDepartments CTE 에서 employee_namedepartment_name을 검색합니다.

3 단계: 쿼리 실행 및 결과 보기

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 employee_name | department_name
---------------+-----------------
 Alice         | Sales
 Bob           | Marketing
 Charlie       | Sales
 David         | Engineering
 Eve           | Marketing
(5 rows)

이는 모든 직원의 이름과 해당 부서 이름을 보여줍니다.

4 단계: 필터링을 위한 CTE 사용

CTE 를 사용하여 데이터를 필터링할 수도 있습니다. 예를 들어, EmployeeDepartments CTE 를 사용하여 'Sales' 부서에서 근무하는 모든 직원을 찾아보겠습니다.

WITH EmployeeDepartments AS (
    SELECT
        e.employee_name,
        d.department_name
    FROM
        employees e
    JOIN
        departments d ON e.department_id = d.department_id
)
SELECT employee_name
FROM EmployeeDepartments
WHERE department_name = 'Sales';

설명:

  • 이 쿼리는 이전 쿼리와 유사하지만 외부 SELECT 문에 WHERE 절을 추가하여 'Sales' 부서에서 근무하는 직원만 결과에 포함하도록 필터링합니다.

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 employee_name
---------------
 Alice
 Charlie
(2 rows)

이는 Alice 와 Charlie 가 'Sales' 부서에서 근무하는 직원임을 보여줍니다.

이제 테이블을 조인하고 데이터를 필터링하기 위해 CTE 를 성공적으로 정의하고 사용했습니다. CTE 는 복잡한 SQL 쿼리의 가독성과 유지 관리성을 크게 향상시킬 수 있습니다.

윈도우 함수 적용 (예: ROW_NUMBER)

이 단계에서는 PostgreSQL 에서 윈도우 함수를 적용하는 방법을 배우게 됩니다. 윈도우 함수는 현재 행과 관련된 테이블 행 집합에 걸쳐 계산을 수행합니다. 집계 함수와 유사하지만, 집계 함수와 달리 윈도우 함수는 행을 단일 출력 행으로 그룹화하지 않습니다. 대신, 결과 집합의 각 행에 대한 값을 제공합니다.

윈도우 함수 이해하기

윈도우 함수는 OVER() 절을 사용하여 계산할 행의 윈도우를 정의합니다. OVER() 절에는 윈도우를 추가로 정의하기 위해 PARTITION BYORDER BY 절이 포함될 수 있습니다.

기본 구문:

SELECT
    column1,
    column2,
    WINDOW_FUNCTION(column3) OVER (
        [PARTITION BY column4]
        [ORDER BY column5]
    ) AS alias_name
FROM
    table_name;
  • WINDOW_FUNCTION: 윈도우 함수의 이름 (예: ROW_NUMBER, RANK, SUM, AVG).
  • OVER(): 함수가 작동하는 윈도우를 지정합니다.
  • PARTITION BY: 행을 파티션으로 나누고, 윈도우 함수는 각 파티션에 개별적으로 적용됩니다.
  • ORDER BY: 각 파티션 내에서 행의 순서를 정의합니다.
  • alias_name: 계산된 윈도우 함수 결과의 별칭입니다.

시나리오:

이전 단계의 employeesdepartments 테이블을 계속 사용하면서 ROW_NUMBER() 윈도우 함수를 사용하여 employee_name을 기준으로 각 직원의 해당 부서 내에서 고유한 순위를 할당해 보겠습니다.

1 단계: ROW_NUMBER() 윈도우 함수 적용

이제 ROW_NUMBER() 윈도우 함수를 사용하여 각 직원의 부서 내에서 순위를 할당하는 쿼리를 작성해 보겠습니다.

SELECT
    employee_name,
    department_id,
    ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name) AS employee_rank
FROM
    employees;

설명:

  • ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name): 이것이 윈도우 함수입니다.
    • ROW_NUMBER(): 윈도우 내의 각 행에 고유한 순차 정수를 할당합니다.
    • PARTITION BY department_id: department_id를 기준으로 행을 파티션으로 나눕니다. 즉, 각 부서에 대해 순위가 별도로 매겨집니다.
    • ORDER BY employee_name: 각 파티션 내에서 행이 정렬되는 순서를 지정합니다. 이 경우 직원은 employee_name을 기준으로 알파벳순으로 순위가 매겨집니다.
  • employee_rank: 이것은 윈도우 함수의 결과에 주어진 별칭입니다.

2 단계: 쿼리 실행 및 결과 보기

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 employee_name | department_id | employee_rank
---------------+---------------+---------------
 Alice         |             1 |             1
 Charlie       |             1 |             2
 Bob           |             2 |             1
 Eve           |             2 |             2
 David         |             3 |             1
(5 rows)

이는 각 직원의 이름, 부서 ID 및 부서 내 순위를 보여줍니다. 예를 들어 Alice 는 부서 1 에서 1 위이고 Charlie 는 부서 1 에서 2 위입니다.

3 단계: CTE 와 함께 윈도우 함수 사용하기

CTE 내에서 윈도우 함수를 사용하여 쿼리를 더 체계적으로 만들 수도 있습니다. CTE 를 사용하여 이전 쿼리를 다시 작성해 보겠습니다.

WITH RankedEmployees AS (
    SELECT
        employee_name,
        department_id,
        ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY employee_name) AS employee_rank
    FROM
        employees
)
SELECT
    employee_name,
    department_id,
    employee_rank
FROM
    RankedEmployees;

이 쿼리는 이전 쿼리와 동일한 결과를 생성하지만 CTE 를 사용하여 윈도우 함수 계산을 캡슐화합니다.

psql 터미널에서 쿼리를 실행합니다. 이전과 동일한 출력이 표시되어야 합니다.

GROUP BY 및 HAVING 을 사용한 그룹화 및 필터링

이 단계에서는 PostgreSQL 에서 GROUP BYHAVING 절을 사용하여 행을 그룹화하고 그룹화된 결과를 필터링하는 방법을 배우게 됩니다. GROUP BY 절은 지정된 열에서 동일한 값을 가진 행을 요약 행으로 그룹화합니다 (예: 각 부서의 직원 수 찾기). HAVING 절은 지정된 조건을 기반으로 이러한 그룹화된 행을 필터링하는 데 사용됩니다.

GROUP BY 및 HAVING 이해하기

  • GROUP BY: 이 절은 하나 이상의 열에서 동일한 값을 가진 행을 요약 행으로 그룹화합니다. 일반적으로 각 그룹에 대한 값을 계산하기 위해 집계 함수 (예: COUNT, SUM, AVG, MIN, MAX) 를 사용합니다.
  • HAVING: 이 절은 GROUP BY 절에 의해 생성된 그룹을 필터링합니다. WHERE 절과 유사하지만 개별 행이 아닌 그룹에 대해 작동합니다.

기본 구문:

SELECT
    column1,
    aggregate_function(column2)
FROM
    table_name
WHERE
    condition
GROUP BY
    column1
HAVING
    aggregate_function(column2) condition;
  • column1: 그룹화할 열입니다.
  • aggregate_function(column2): 각 그룹에 대해 column2에 적용된 집계 함수입니다.
  • WHERE: 그룹화 전에 행을 필터링합니다.
  • GROUP BY: column1의 값을 기반으로 행을 그룹화합니다.
  • HAVING: 집계 함수의 결과를 기반으로 그룹화 그룹을 필터링합니다.

시나리오:

employeesdepartments 테이블을 계속 사용하면서 GROUP BYHAVING을 사용하여 직원이 2 명 이상인 부서를 찾아보겠습니다.

1 단계: 부서별 그룹화 및 직원 수 계산

먼저, 부서별로 직원을 그룹화하고 각 부서의 직원 수를 계산하는 쿼리를 작성해 보겠습니다.

SELECT
    d.department_name,
    COUNT(e.employee_id) AS employee_count
FROM
    employees e
JOIN
    departments d ON e.department_id = d.department_id
GROUP BY
    d.department_name;

설명:

  • SELECT d.department_name, COUNT(e.employee_id) AS employee_count: 각 부서의 부서 이름과 직원 ID 수를 선택합니다.
  • FROM employees e JOIN departments d ON e.department_id = d.department_id: employeesdepartments 테이블을 department_id 열에서 조인합니다.
  • GROUP BY d.department_name: 부서 이름별로 행을 그룹화하므로 COUNT() 함수는 각 부서의 직원 수를 계산합니다.

2 단계: 쿼리 실행 및 결과 보기

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 department_name | employee_count
-----------------+----------------
 Engineering     |              1
 Marketing       |              2
 Sales           |              2
(3 rows)

이는 각 부서의 직원 수를 보여줍니다.

3 단계: HAVING 으로 필터링

이제 HAVING 절을 추가하여 직원이 2 명 이상인 부서만 결과에 포함하도록 필터링해 보겠습니다.

SELECT
    d.department_name,
    COUNT(e.employee_id) AS employee_count
FROM
    employees e
JOIN
    departments d ON e.department_id = d.department_id
GROUP BY
    d.department_name
HAVING
    COUNT(e.employee_id) > 1;

설명:

  • 이 쿼리는 이전 쿼리와 동일하지만 HAVING 절을 추가합니다.
    • HAVING COUNT(e.employee_id) > 1: 직원 ID 수가 1 보다 큰 부서만 그룹에 포함하도록 필터링합니다.

4 단계: 쿼리 실행 및 결과 보기

psql 터미널에서 쿼리를 실행합니다. 다음 출력이 표시되어야 합니다.

 department_name | employee_count
-----------------+----------------
 Marketing       |              2
 Sales           |              2
(2 rows)

이는 직원이 2 명 이상인 부서 (마케팅 및 판매) 만 보여줍니다.

이제 GROUP BYHAVING 절을 사용하여 행을 그룹화하고 조건을 기반으로 그룹화된 결과를 성공적으로 필터링했습니다.

요약

이 Lab 에서는 고급 PostgreSQL 쿼리를 작성하는 방법을 배웠습니다. 먼저 다른 쿼리의 결과를 기반으로 데이터를 필터링하기 위해 WHERE 절 내에서 하위 쿼리를 사용하는 것으로 시작했습니다. 여기에는 내부 쿼리에서 반환된 값 집합과 열 값을 비교하기 위한 하위 쿼리의 구문 및 적용을 이해하는 것이 포함되었습니다.

그런 다음 쿼리 가독성과 모듈성을 향상시키기 위해 Common Table Expression (CTE) 을 정의하고 활용하는 것으로 넘어갔습니다. CTE 를 사용하면 복잡한 쿼리를 더 간단하고 관리하기 쉬운 부분으로 나눌 수 있습니다.

다음으로, ROW_NUMBER와 같은 윈도우 함수를 적용하여 행 집합에 걸쳐 계산을 수행했습니다. 윈도우 함수는 집계 함수와 유사하지만, 행을 단일 출력 행으로 그룹화하는 대신 결과 집합의 각 행에 대한 값을 제공합니다.

마지막으로, GROUP BYHAVING 절을 사용하여 데이터를 그룹화하고 필터링하여 데이터 세트에서 의미 있는 통찰력을 추출하는 방법을 익혔습니다. GROUP BY 절은 지정된 열에서 동일한 값을 가진 행을 요약 행으로 그룹화하는 반면, HAVING 절은 지정된 조건을 기반으로 이러한 그룹화된 행을 필터링합니다.