본 시리즈에서는 데이터베이스의 개념을 정리하고 필요시 실습을 진행합니다.
Ubuntu-24.04
)10.11.8-MariaDB
)2024.2.2
)correto-21
)Ultimate Edition
)world database
데이터베이스에서 로직 구현은 단순히 데이터를 저장하고 조회하는 것 이상의 역할을 수행하며, 데이터의 정합성과 무결성을 유지하는 데 중요한 역할을 합니다.
SQL
을 통해 로직을 구현하는 다양한 방식들이 존재하며, 이러한 요소들은 주로 조건 처리, 함수, 프로시저, 트리거를 사용하여 데이터베이스의 비즈니스 로직과 자동화된 데이터 처리를 지원합니다.SELECT
구문에서 동적인 값 설정이나 가독성을 위한 데이터 변환 목적으로 활용됩니다.함수
와 달리 여러 개의 값을 반환하거나 단순한 데이터 조작을 수행하는 데 유용하며, 입력 및 출력 매개변수를 활용하여 복잡한 데이터 로직을 처리할 수 있습니다.SQL의 다양한 로직 구현 도구를 통해 데이터베이스에서 복잡한 비즈니스 로직을 처리하고, 데이터 조작 자동화와 데이터 무결성 유지가 가능해집니다.
CASE
문은 SQL에서 조건에 따라 서로 다른 결과를 반환할 때 사용하는 조건문입니다.
SELECT
column1,
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE default_result
END AS alias_name
FROM table_name;
WHEN
: 조건을 지정합니다. WHEN
에 지정된 조건이 참일 때 그에 해당하는 result
가 반환됩니다.THEN
: 조건이 참일 때 반환할 값을 지정합니다.ELSE
: 모든 조건이 거짓일 때 반환할 기본값을 지정합니다. ELSE
는 선택사항입니다.END
: CASE
문을 종료합니다.AS alias_name
: 결과 열에 별칭(alias)을 부여합니다.country
테이블에서 인구(Population
)에 따라 국가를 "대국", "중간국", "소국"
으로 분류하고자 한다면 다음과 같이 작성할 수 있습니다:SELECT Name AS Country,
Population,
CASE
WHEN Population >= 100000000 THEN '대국'
WHEN Population >= 10000000 THEN '중간국'
ELSE '소국'
END AS SizeCategory
FROM country;
Population
값에 따라 각 국가가 속하는 범주를 SizeCategory
라는 별칭으로 반환합니다.CASE
문은 AND
로 복수의 조건을 통해 좀 더 복잡한 논리 처리가 가능합니다. city
테이블에서 도시의 인구가 1,000,000
이상이고 CountryCode
가 'USA'
인 경우 "미국 대도시"
로, 그렇지 않으면 "일반 도시"
로 분류할 수 있습니다.SELECT Name AS City,
Population,
CASE
WHEN Population >= 1000000 AND CountryCode = 'USA' THEN '미국 대도시'
ELSE '일반 도시'
END AS CityCategory
FROM city
WHERE CountryCode = 'USA';
Population
과 CountryCode
의 조합 조건을 활용하여 도시를 분류합니다.CASE
문은 집계 함수와 함께 사용하여 조건에 따라 집계 결과를 구할 수 있습니다. country
테이블에서 대륙(Continent
)별로 인구가 50,000,000
이상인 국가의 수를 구하려면 다음과 같이 작성할 수 있습니다.SELECT Continent,
COUNT(CASE WHEN Population >= 50000000 THEN 1 END) AS HighPopulationCountryCount
FROM country
GROUP BY Continent;
Continent
별로 인구가 50,000,000
이상인 국가의 개수를 집계하여 반환합니다.CASE
문을 ORDER BY
절에서도 사용할 수 있습니다. country
테이블에서 Region
이 "Western Europe"
인 국가를 우선으로 정렬하고, 그 외의 국가
를 인구 순으로 정렬하고자 할 때 다음과 같이 작성할 수 있습니다.SELECT Name, Region, Population
FROM country
ORDER BY
CASE WHEN Region = 'Western Europe' THEN 1 ELSE 2 END,
Population DESC;
Western Europe
에 속한 국가가 먼저 나오고, 그 외 국가들은 인구 내림차순으로 정렬됩니다.참고:
CASE
문은 SQL 쿼리의WHERE
절에서는 사용할 수 없으며,SELECT
,ORDER BY
,GROUP BY
등에서 주로 사용됩니다.
사용자 정의 함수(User-Defined Function, UDF)는 사용자가 정의한 논리를 SQL에서 재사용할 수 있는 함수입니다.
트랜잭션
내에서 사용되거나 여러 쿼리에서 재사용할 수 있어 코드 재사용성과 유지보수성을 향상시킵니다.CREATE FUNCTION function_name(parameter1 datatype, parameter2 datatype, ...)
RETURNS return_datatype
BEGIN
DECLARE variable_name datatype;
-- 함수 로직 (SQL 구문)
RETURN result;
END;
CREATE FUNCTION
: 함수를 정의하여 생성합니다. (DB의 routines 항목에 저장됩니다)function_name
: 함수 이름을 지정합니다.parameter
: 입력 매개변수로, 함수에 전달되는 값을 지정합니다.RETURNS
: 반환 타입을 지정합니다.BEGIN ... END
: 함수 본체를 정의하는 구간으로, 내부에서 SQL 구문을 작성합니다.DECLARE
: 함수 내부에서 사용할 변수를 선언합니다. (일종의 지역변수 입니다)참고로 생성한 함수를 지우려면
DROP FUNCTION function_name;
구문을 사용하시면 됩니다.
CREATE FUNCTION add_numbers(a INT, b INT)
RETURNS INT
BEGIN
RETURN a + b;
END;
SELECT add_numbers(10, 20) -- 결과: 30
FROM dual;
city
테이블에서 도시 이름
과 국가 코드
를 입력받아 해당 도시의 인구
를 반환하는 함수를 만들어 보겠습니다.CREATE FUNCTION get_city_population(city_name VARCHAR(50), country_code CHAR(3))
RETURNS INT
BEGIN
DECLARE population INT;
SELECT ct.Population INTO population
FROM city AS ct
WHERE ct.Name = city_name AND ct.CountryCode = country_code;
RETURN population;
END;
SELECT get_city_population('New York', 'USA') -- 예시 결과: 8008278
AS NewYorkPopulation
FROM dual;
country
테이블에서 국가 코드
를 입력받아 해당 국가의 인구 밀도
를 계산하여 반환하는 함수를 정의해보겠습니다. Population
)를 면적(SurfaceArea
)으로 나눈 값으로 계산할 수 있습니다.CREATE FUNCTION calculate_population_density(country_code CHAR(3))
RETURNS DECIMAL(10,2)
BEGIN
DECLARE population INT;
DECLARE area DECIMAL(10,2);
DECLARE density DECIMAL(10,2);
SELECT cout.Population, cout.SurfaceArea INTO population, area
FROM country AS cout
WHERE cout.Code = country_code;
SET density = population / area;
RETURN density;
END;
SELECT calculate_population_density('USA')
AS USAPopulationDensity
FROM dual;
DROP FUNCTION
명령어를 사용하여 삭제할 수 있습니다.DROP FUNCTION IF EXISTS function_name;
함수
는 일반적으로 하나의 값을 반환하므로, 여러 결과를 반환해야 하는 경우는 프로시저
가 더 적합할 수 있습니다.MariaDB/MySQL
로 실습을 진행했습니다.)PROCEDURE
(프로시저)는 SQL에서 반복적이고 복잡한 작업을 하나의 단위로 묶어 처리할 수 있는 저장된 프로그램입니다.
프로시저
는 주로 데이터 조작(DML), 제어문(IF, LOOP, WHILE) 등을 포함하여 복잡한 연산을 처리하는 데 사용됩니다.프로시저
내부에서 다양한 SQL 구문(DML, 제어 구문 등)을 사용하여 트랜잭션
을 관리하거나, 조건문과 반복문을 활용할 수 있습니다.CREATE PROCEDURE
구문을 사용하여 프로시저
를 정의하고, 필요에 따라 DROP PROCEDURE
로 삭제할 수 있습니다.CREATE PROCEDURE 프로시저이름(매개변수1 [IN|OUT|INOUT] 데이터형, 매개변수2 [IN|OUT|INOUT] 데이터형, ...)
BEGIN
-- 프로시저 로직
SQL 구문들;
END;
IN
: 입력 매개변수. OUT
: 출력 매개변수. INOUT
: 입력과 출력을 모두 지원하는 매개변수. DROP PROCEDURE 프로시저이름;
country
테이블에서 특정 대륙의 총 인구 수를 조회하는 프로시저를 만들어 보겠습니다.CREATE PROCEDURE get_population_by_continent(
IN continent_name VARCHAR(50),
OUT total_population BIGINT)
BEGIN
SELECT SUM(cout.Population) INTO total_population
FROM country AS cout
WHERE cout.Continent = continent_name;
END;
IN
매개변수 continent_name
을 통해 대륙명을 받아 country
테이블에서 해당 대륙의 총 인구를 조회합니다.OUT
매개변수 total_population
에 조회 결과를 반환합니다.CALL get_population_by_continent('Asia', @total_population);
SELECT @total_population AS AsiaPopulation
FROM dual;
MySQL/MariaDB
에서는 OUT 및 INOUT 매개변수를 사용할 때, 반드시 변수로 지정된 값을 전달해야 합니다. (@
로 정의된 변수입니다)
CREATE PROCEDURE calculate_population_density(
INOUT country_code CHAR(3),
OUT density DECIMAL(10, 2))
BEGIN
DECLARE population INT;
DECLARE area DECIMAL(10, 2);
SELECT cout.Population, cout.SurfaceArea INTO population, area
FROM country AS cout
WHERE cout.Code = country_code;
SET density = population / area;
END;
-- 프로시저 호출 전에 변수를 선언합니다.
SET @country_code = 'USA';
SET @density = NULL; -- OUT 매개변수를 저장할 변수를 선언합니다.
-- 프로시저 호출 시 변수를 사용합니다.
CALL calculate_population_density(@country_code, @density);
-- 결과를 조회합니다.
SELECT @density AS USAPopulationDensity;
MySQL/MariaDB
에서는 OUT 및 INOUT 매개변수를 사용할 때, 반드시 변수로 지정된 값을 전달해야 합니다. (@
로 정의된 변수입니다)
조건문
, 반복문
등 복잡한 비즈니스 로직을 프로시저
내에 포함할 수 있습니다.클라이언트-서버
간의 데이터 전송이 줄어들며, 서버 내에서 작업이 처리되므로 네트워크 부하를 줄일 수 있습니다. 프로시저
를 통해 간접적으로 데이터를 제어할 수 있습니다.프로시저
는 디버깅이 어렵고, 오류를 발견하기 쉽지 않습니다.프로시저
의 복잡성이 증가하면 유지보수가 어려워질 수 있습니다.특정 DBMS
에서만 동작하는 경우가 있어, 다른 DBMS로 이식하기 어려울 수 있습니다.트리거(Trigger)
는 특정한 이벤트가 발생할 때 자동으로 실행되는 SQL 코드 블록입니다.
CREATE TRIGGER 트리거_이름
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 테이블_이름
FOR EACH ROW
BEGIN
-- 트리거에서 실행할 SQL 코드
END;
CREATE TRIGGER
: 트리거를 생성합니다.{BEFORE | AFTER}
: 트리거가 실행될 시점을 지정합니다. BEFORE
는 데이터 변경 전, AFTER
는 데이터 변경 후에 트리거가 실행됩니다.{INSERT | UPDATE | DELETE}
: 트리거를 실행할 작업 유형을 지정합니다.FOR EACH ROW
: 각 행에 대해 트리거가 실행되도록 지정합니다.BEGIN ... END
: 트리거에서 실행할 SQL 문을 작성하는 구문입니다.트리거
에서는 기존 예제와는 다른 새로운 테이블을 생성하여 진행해보도록 하겠습니다.employees
테이블 : 직원 관리 테이블audit_log
테이블 : employees
테이블의 변경 내역을 기록하는 테이블# employees 테이블 생성
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
name VARCHAR(50),
position VARCHAR(50),
salary DECIMAL(10, 2)
);
# audit_log 테이블 생성
CREATE TABLE audit_log (
log_id INT PRIMARY KEY AUTO_INCREMENT,
emp_id INT,
action VARCHAR(50),
action_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
employees
테이블에서 새로운 직원이 추가(INSERT)될 때 audit_log
라는 감사 테이블에 변경 내역을 기록하는 AFTER INSERT 트리거
를 만드는 예제입니다.
AFTER INSERT
트리거 생성CREATE TRIGGER after_employee_insert
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
INSERT INTO audit_log (emp_id, action)
VALUES (NEW.emp_id, 'INSERT');
END;
employees
테이블에 새로운 직원이 추가될 때마다 audit_log
테이블에 변경 내역이 기록됩니다.NEW
키워드는 트리거에 의해 생성되는 테이블 객체를 의미합니다. (INSERT
에서는 NEW
만 존재합니다)INSERT INTO employees (emp_id, name, position, salary)
VALUES (1, 'Alice', 'Manager', 75000.00);
SELECT * FROM audit_log;
이번 트리거는 employees
테이블에서 급여가 업데이트될 때 이전 급여(OLD)
를 salary_changes
테이블에 기록합니다.
salary_changes
테이블 생성CREATE TABLE salary_changes (
change_id INT PRIMARY KEY AUTO_INCREMENT,
emp_id INT,
old_salary DECIMAL(10, 2),
new_salary DECIMAL(10, 2),
change_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
BEFORE UPDATE
트리거 생성CREATE TRIGGER before_salary_update
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
IF OLD.salary <> NEW.salary THEN
INSERT INTO salary_changes (emp_id, old_salary, new_salary)
VALUES (OLD.emp_id, OLD.salary, NEW.salary);
END IF;
END;
employees
테이블에서 급여가 변경되면 salary_changes
테이블에 이전 급여와 새로운 급여가 기록됩니다.<>
은 부정(NOT
)의 의미가 있는 연산자이고, !=
를 사용해도 됩니다.OLD
키워드는 트리거가 실행되기 직전의 테이블 객체를 의미합니다.NEW
키워드는 트리거에 의해 수정되는 테이블 객체를 의미합니다.UPDATE employees
SET salary = 80000.00
WHERE emp_id = 1;
SELECT * FROM salary_changes;
DROP TRIGGER
문을 사용합니다.DROP TRIGGER IF EXISTS after_employee_insert;
SHOW TRIGGERS
구문으로 트리거 정보를 조회할 수 있습니다.SHOW TRIGGERS FROM testdb3 WHERE `Table` = 'employees';
FROM
에는 DB 이름, WHERE
에는 Table 이름이 들어가면 됩니다.
트리거
를 적절히 사용하면 데이터베이스의 일관성과 무결성을 강화할 수 있지만, 과도하게 사용하면 성능 저하를 초래할 수 있으므로 필요한 경우에만 사용하는 것이 좋습니다.
이번 포스팅에서는 SQL 로직 구현의 기본 요소인 CASE문, 사용자 정의 함수(FUNCTION), 프로시저(PROCEDURE), 트리거(TRIGGER)에 대해 다루었습니다.
조건에 따라 다른 값을 반환
하고, 데이터 조회 시 유연한 조건을 적용
할 수 있습니다.반복적인 계산
이나 특정 데이터를 동적으로 반환
하는 로직을 재사용할 수 있습니다.하나의 단위로 묶어 반복적인 작업을 자동화
할 수 있는 방법을 제공합니다.특정 이벤트가 발생할 때 자동으로 실행
되도록 설정하여 데이터 변경 시 자동으로 추가 작업을 수행
하고, 데이터 무결성을 강화할 수 있습니다만, 과도하게 사용하면 성능 저하를 초래
할 수 있으므로 필요한 경우에만 사용
하는 것이 좋습니다.다음 포스팅부터는 JDBC(Java Database Connectivity)를 다루면서 Java 애플리케이션과 데이터베이스를 연동하는 방법을 다룰 예정입니다.