AD JB Toolbox 01

MariaDB 강좌 / WITH로 공통 테이블 식 만들고 쿼리 나누기

WITH로 정의하는 공통 테이블 식(CTE, Common Table Expression)은 한 SQL 문 안에서 사용할 중간 조회 결과에 이름을 붙이는 기능입니다. 복잡한 조건과 집계를 단계별로 나누어 읽기 쉽게 만들지만, 영구 테이블을 만들거나 실행 속도를 자동으로 보장하는 기능은 아닙니다.

중간 결과마다 한 가지 역할 부여하기

결제 주문 선택, 주문별 상품 금액 계산, 고객 이름 연결을 한 문장에 섞으면 조건과 행 단위를 추적하기 어렵습니다. paid_orders는 대상 주문, order_totals는 주문당 한 금액이라는 역할을 갖게 나누겠습니다. 이름만 예쁘게 붙이는 대신 각 결과의 열·키·행 단위를 설명할 수 있어야 합니다. 중간 결과를 나눈 뒤 순위·누적을 붙이는 응용은 윈도 함수로 순번과 순위 계산하기와 윈도 함수로 누적 합계와 이전·다음 행 비교하기, 자기 결과를 반복 참조하는 구조는 재귀 CTE로 계층 데이터 조회하고 순환 막기에서 실습합니다.

중간 결과 한 행의 의미 주요 열
paid_orders paid 주문 한 건 order_id, customer_id
order_totals 주문별 상세 합계 order_id, goods_total
최종 결과 대상 주문 한 건 고객 이름·주문 상품 금액

독립된 주문 데이터와 CTE 준비하기

학습용 MariaDB 서버의 shopcourse에 course_user로 접속합니다. 명령줄 클라이언트와 UTF-8 SQL 파일 실행 방법대로 코드를 UTF-8 파일에 저장하고 SOURCE C:/mariadb-lab/lesson.sql;처럼 실제 파일 경로로 실행할 수 있습니다. SQL 문은 운영체제 터미널이 아닌 MariaDB 연결 안에서 실행합니다.

먼저 SELECT VERSION(), DATABASE();와 SHOW TABLES;로 대상 서버·DB·이름 중복을 확인합니다. 아래 c31_customers, c31_products, c31_orders, c31_items는 이 글의 독립된 연습용 이름입니다. 이미 존재하면 정의와 행을 확인하고, 보관할 자료가 없는 이 글의 실습 테이블임이 확실할 때만 마지막 절의 정리 순서로 지운 뒤 다시 만듭니다. 기존 강좌의 동명 없는 테이블이나 운영 데이터는 삭제하지 않습니다.

USE shopcourse;
CREATE TABLE c31_customers (
 customer_id INT NOT NULL PRIMARY KEY,
 customer_name VARCHAR(50) NOT NULL,
 city VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c31_products (
 product_id INT NOT NULL PRIMARY KEY,
 product_name VARCHAR(100) NOT NULL,
 category VARCHAR(30) NOT NULL,
 price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
 stock INT NOT NULL CHECK (stock >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c31_orders (
 order_id INT NOT NULL PRIMARY KEY,
 customer_id INT NOT NULL,
 ordered_at DATETIME NOT NULL,
 status VARCHAR(20) NOT NULL,
 shipping_fee DECIMAL(10,2) NOT NULL DEFAULT 1000,
 CONSTRAINT fk_c31_order_customer FOREIGN KEY (customer_id)
  REFERENCES c31_customers(customer_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c31_items (
 order_id INT NOT NULL,
 product_id INT NOT NULL,
 quantity INT NOT NULL CHECK (quantity > 0),
 unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0),
 PRIMARY KEY (order_id, product_id),
 CONSTRAINT fk_c31_item_order FOREIGN KEY (order_id)
  REFERENCES c31_orders(order_id) ON DELETE RESTRICT,
 CONSTRAINT fk_c31_item_product FOREIGN KEY (product_id)
  REFERENCES c31_products(product_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO c31_customers VALUES
 (1,'김민수','서울'),(2,'이서연','부산'),
 (3,'박지우','서울'),(4,'최하늘','대전');
INSERT INTO c31_products VALUES
 (1,'노트','문구',3500,20),(2,'볼펜','문구',1500,50),
 (3,'머그컵','생활',8000,12),(4,'스탠드','생활',25000,5);
INSERT INTO c31_orders
 (order_id,customer_id,ordered_at,status,shipping_fee) VALUES
 (101,1,'2026-01-10 10:00:00','paid',1000),
 (102,1,'2026-01-20 11:00:00','paid',1000),
 (103,2,'2026-02-05 12:00:00','paid',1000),
 (104,3,'2026-02-10 13:00:00','canceled',1000);
INSERT INTO c31_items VALUES
 (101,1,2,3500),(101,2,3,1500),(102,3,1,8000),
 (103,1,1,3500),(103,3,2,8000),(104,2,1,1500);

paid 주문 세 건과 취소 주문 한 건을 사용합니다. 상세 합계를 주문별로 먼저 만들기 때문에 주문 헤더 값이 상세 수만큼 중복되는 문제도 추적하기 쉽습니다. 서버 버전과 WITH 지원 여부는 SELECT VERSION();로 확인하고 해당 버전의 WITH 문법을 참고하세요.

쉼표로 여러 CTE를 정의하고 최종 조회하기

WITH paid_orders AS (
 SELECT order_id,customer_id FROM c31_orders WHERE status='paid'
), order_totals AS (
 SELECT order_id,SUM(quantity*unit_price) AS goods_total
 FROM c31_items GROUP BY order_id
)
SELECT p.order_id,c.customer_name,t.goods_total
FROM paid_orders p JOIN order_totals t ON t.order_id=p.order_id
JOIN c31_customers c ON c.customer_id=p.customer_id
ORDER BY p.order_id;
주문 고객 상품 금액
101 김민수 11500.00
102 김민수 8000.00
103 이서연 19500.00

WITH는 문장 앞에 한 번 쓰고 여러 이름은 쉼표로 나눕니다. 각 이름의 AS 괄호 안에는 SELECT를 넣고 그 뒤에 최종 SELECT를 이어 씁니다. 중간 CTE 뒤에 세미콜론을 넣으면 문장이 끝나므로 이후 조회에서 이름을 찾지 못하게 됩니다.

각 단계의 결과를 따로 검증하기

최종 합계가 다르면 CTE 안의 SELECT를 먼저 단독으로 실행해 보세요. paid_orders의 주문 번호가 101·102·103인지, order_totals의 주문 104까지 포함한 네 합계가 의도한 것인지 확인한 뒤 연결합니다. 어느 단계에서 취소 주문을 제외하는지도 명시합니다.

WITH paid_orders AS (
 SELECT order_id,customer_id FROM c31_orders WHERE status='paid'
)
SELECT * FROM paid_orders ORDER BY order_id;

최종 조회만 바꾸어 중간 결과를 볼 수도 있습니다. 같은 CTE 정의를 쓰더라도 출력 단위가 주문에서 고객으로 바뀌면 GROUP BY 등 추가 집계가 필요합니다. 이름이 같은 중간 결과를 반복 정의하기보다 실제 범위를 확인합니다.

유효 범위와 성능을 과장하지 않기

CTE 이름은 해당 SQL 문에서만 유효합니다. 다음 문장에서 SELECT * FROM paid_orders를 실행할 수 있는 영구 객체가 아닙니다. 여러 문장에서 재사용할 정의라면 VIEW, 저장된 임시 데이터가 필요하면 임시 테이블 등을 목적에 맞게 검토합니다.

서버는 쿼리 구조와 최적화 조건에 따라 중간 결과를 병합하거나 구체화할 수 있습니다. WITH를 썼으니 반드시 한 번만 계산되거나 언제나 빠르다고 단정하지 않습니다. 가독성 개선과 실행 계획 개선을 나누어 평가하고 실제 EXPLAIN으로 확인합니다. 재귀 CTE는 이 글의 비재귀 단계 분리와 별도의 주제입니다.

이름·열·집계 단위를 점검하고 정리하기

문법 오류는 괄호·쉼표·세미콜론부터 확인하고, 열을 찾지 못하면 CTE의 SELECT 목록에 그 열이 있는지 살펴봅니다. 같은 이름의 원본 테이블과 혼동되지 않는 이름을 쓰고 SELECT *보다 필요한 열을 적습니다. 정리할 영구 CTE 객체는 없으며 이 글에서 만든 원본 실습 테이블만 정리합니다.

반복하기 전 현재 DB와 실제 정의·행을 확인하고 필요한 자료를 백업합니다. 보관할 데이터가 없는 이 글의 전용 실습 객체만 아래 순서로 정리합니다. 외래키가 있으면 자식부터 지웁니다. DROP TABLE은 구조와 데이터를 모두 없애며 일반적인 ROLLBACK으로 복원되지 않으므로 취소 가능한 DML 실습과 분리합니다.

DROP TABLE shopcourse.c31_items;
DROP TABLE shopcourse.c31_orders;
DROP TABLE shopcourse.c31_products;
DROP TABLE shopcourse.c31_customers;
같은 카테고리의 다른 글
MariaDB 강좌 / Ubuntu에 MariaDB 설치하고 서버 실행하기

MariaDB 강좌 / Ubuntu에 MariaDB 설치하고 서버 실행하기

Ubuntu APT 설치 후 서버 실행·자동 시작·관리자 소켓 인증을 확인합니다. 로컬 TCP 학습 계정을 만들고 패키지 버전, 인증 오류와 서비스 로그를 확인하는 절차를 설명합니다.

MariaDB 강좌 / UPDATE·DELETE를 실행하기 전 대상 행 확인하기

MariaDB 강좌 / UPDATE·DELETE를 실행하기 전 대상 행 확인하기

UPDATE·DELETE 전에 기본키와 현재값으로 대상 행을 확인하고, InnoDB 트랜잭션에서 수정·삭제 결과를 검증한 뒤 취소합니다. 영향받은 행 수와 ROLLBACK의 적용 한계도 설명합니다.

MariaDB 강좌 / 숫자 계산과 타입 변환: ROUND·CAST

MariaDB 강좌 / 숫자 계산과 타입 변환: ROUND·CAST

ROUND와 CAST를 할인 금액·정확한 소수·숫자 문자열로 실습합니다. 반올림 시점, 앞자리 0 손실, 잘림 경고와 SQL 모드에 따른 저장 동작의 차이를 설명합니다.

MariaDB / 데이터베이스 저장 위치 변경하는 방법

MariaDB / 데이터베이스 저장 위치 변경하는 방법

MariaDB의 버전이 12.1이라고 할 때, 윈도우용 MariaDB의 기본 설치 위치는 C:\Program Files\MariaDB 12.1, 데이터베이스 저장 위치는 C:\Program Files\MariaDB 12.1\data입니다. 데이터베이스 저장 위치를 다음으로 변경해보겠습니다. D:\Database\MariaDB\data

MariaDB 강좌 / Windows에 MariaDB 설치하고 서버 실행하기

MariaDB 강좌 / Windows에 MariaDB 설치하고 서버 실행하기

Windows MSI 설치에서 서버·클라이언트, 관리자 인증, 서비스와 포트를 설정합니다. SQL 실행을 확인하고 이후 강좌에서 사용할 학습용 계정을 준비하며 시작·접속 오류를 점검합니다.

MariaDB 강좌 / 데이터베이스 만들기: CREATE DATABASE·USE·문자 집합

MariaDB 강좌 / 데이터베이스 만들기: CREATE DATABASE·USE·문자 집합

shopcourse 데이터베이스를 만들고 현재 연결의 작업 대상으로 선택합니다. utf8mb4와 collation, 기존 DB에서 IF NOT EXISTS의 한계, 권한·문자 설정 확인을 예제로 설명합니다.

MariaDB 강좌 / NULL을 올바르게 비교하기: IS NULL·COALESCE

MariaDB 강좌 / NULL을 올바르게 비교하기: IS NULL·COALESCE

NULL·빈 문자열·0을 구분하고 IS NULL·IS NOT NULL과 COALESCE의 역할을 실습합니다. 일반 비교에서 빠지는 NULL 행, NULL-safe 비교와 NOT IN 목록의 함정을 설명합니다.

MariaDB 강좌 / LEFT JOIN으로 연결되지 않은 행도 조회하기

MariaDB 강좌 / LEFT JOIN으로 연결되지 않은 행도 조회하기

LEFT JOIN으로 주문 없는 고객을 유지하고 paid 주문 수를 0까지 표시합니다. ON·WHERE의 필터 위치, COUNT(*)의 함정, NULL 기본키로 미일치 행을 찾는 기준을 실습합니다.

MariaDB 강좌 / INSERT로 한 행과 여러 행 추가하기

MariaDB 강좌 / INSERT로 한 행과 여러 행 추가하기

INSERT의 열·값 대응, 한 행과 다중 행 입력, 자동 번호와 기본값을 상품 데이터로 실습합니다. 저장 결과와 오류 뒤 행 수를 확인하고 기존 데이터와 분리해 반복하는 방법을 설명합니다.

MariaDB 강좌 / 문자열 다루기: 연결·추출·치환·길이

MariaDB 강좌 / 문자열 다루기: 연결·추출·치환·길이

문자열 연결·추출·치환과 길이 계산을 상품 데이터로 실습합니다. NULL과 빈 문자열의 연결 차이, UTF-8 문자 수·바이트 수, 원본을 보존하며 가공 결과를 확인하는 기준을 설명합니다.