AD JB Toolbox 01

MariaDB 강좌 / UNION·UNION ALL로 조회 결과 합치기

UNION은 열 구조가 맞는 여러 조회 결과를 세로로 합치면서 동일한 결과 행을 제거하고, UNION ALL은 중복을 유지합니다. 열 개수·위치·타입을 맞추고 최종 ORDER BY로 전체 순서를 정해야 두 목록의 의미를 혼동하지 않습니다.

JOIN의 가로 연결과 UNION의 세로 합치기 구분하기

고객 이름 옆에 주문 정보를 붙이는 작업은 JOIN이고, 서울 고객 목록과 결제 고객 목록을 하나의 대상 목록으로 모으는 작업은 UNION입니다. 합칠 결과에서 같은 열 위치가 같은 의미를 가져야 합니다. 첫 열은 고객 번호인데 다음 조회의 첫 열은 주문 번호인 결과를 합치면 타입이 같아도 의미가 어긋납니다. 중복 판단은 SELECT로 열 선택하기: 별칭·계산식·DISTINCT의 DISTINCT처럼 선택 열 전체를 기준으로 하며, INNER JOIN으로 서로 연결된 데이터 조회하기의 관계 키에 따른 JOIN과는 결과를 만드는 방향이 다릅니다.

기능 중복 처리 주요 용도
UNION 전체 선택 열이 같은 행 제거 겹치는 대상 목록 통합
UNION ALL 중복 유지 각 출처의 발생 내역 보존
JOIN 조건으로 열·행 조합 서로 관련된 정보 연결

겹치는 대상 고객을 가진 데이터 준비하기

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

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

USE shopcourse;
CREATE TABLE c30_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 c30_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 c30_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_c30_order_customer FOREIGN KEY (customer_id)
  REFERENCES c30_customers(customer_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c30_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_c30_item_order FOREIGN KEY (order_id)
  REFERENCES c30_orders(order_id) ON DELETE RESTRICT,
 CONSTRAINT fk_c30_item_product FOREIGN KEY (product_id)
  REFERENCES c30_products(product_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO c30_customers VALUES
 (1,'김민수','서울'),(2,'이서연','부산'),
 (3,'박지우','서울'),(4,'최하늘','대전');
INSERT INTO c30_products VALUES
 (1,'노트','문구',3500,20),(2,'볼펜','문구',1500,50),
 (3,'머그컵','생활',8000,12),(4,'스탠드','생활',25000,5);
INSERT INTO c30_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 c30_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);

서울 고객은 1·3번, paid 주문이 있는 고객은 1·2번입니다. 고객 1은 두 목록에 동시에 속합니다. 안쪽 결제 고객 목록에는 EXISTS를 사용해 주문 두 건인 고객 1도 한 번만 나오게 했습니다.

UNION과 UNION ALL의 행 수 비교하기

SELECT customer_id FROM c30_customers WHERE city='서울'
UNION
SELECT c.customer_id FROM c30_customers c WHERE EXISTS
 (SELECT 1 FROM c30_orders o WHERE o.customer_id=c.customer_id AND o.status='paid')
ORDER BY customer_id;
SELECT customer_id FROM c30_customers WHERE city='서울'
UNION ALL
SELECT c.customer_id FROM c30_customers c WHERE EXISTS
 (SELECT 1 FROM c30_orders o WHERE o.customer_id=c.customer_id AND o.status='paid')
ORDER BY customer_id;

첫 결과는 1·2·3의 세 행입니다. 둘째 결과는 1·1·2·3의 네 행으로 고객 1의 두 출처가 유지됩니다. UNION ALL은 중복 검사를 생략할 수 있지만, 필요한 중복 제거를 빠르게 보이기 위해 없애면 잘못된 대상 목록이 됩니다. 기능 선택은 결과 의미를 우선합니다. UNION과 UNION ALL 공식 문법을 확인하세요.

출처 열을 추가하면 중복 기준도 달라지기

SELECT customer_id,'서울 고객' AS source_label FROM c30_customers WHERE city='서울'
UNION
SELECT c.customer_id,'결제 고객' FROM c30_customers c WHERE EXISTS
 (SELECT 1 FROM c30_orders o WHERE o.customer_id=c.customer_id AND o.status='paid')
ORDER BY customer_id,source_label;

고객 1은 서울 고객과 결제 고객이라는 서로 다른 source_label을 가지므로 UNION이어도 두 행이 남습니다. 중복은 고객 번호 하나가 아니라 선택한 열 조합 전체를 기준으로 제거됩니다. 고객당 한 행이 필요하면서 여러 출처를 함께 보여 줘야 한다면 집계 등 별도의 출력 설계를 해야 합니다.

열 수·타입·정렬 위치 맞추기

각 SELECT는 같은 개수의 열을 반환해야 하며 위치별로 호환 가능한 타입이어야 합니다. 결과 열 이름은 기본적으로 첫 조회의 선택 목록을 따르므로 이후 쿼리에서 붙인 별칭이 최종 이름이 된다고 가정하지 않습니다. 숫자와 문자열을 섞어 생기는 타입 변환도 확인하세요.

전체 목록의 순서는 마지막 ORDER BY로 정합니다. 각 분기 안의 ORDER BY만으로 최종 순서가 보장되지 않습니다. 분기별 상위 몇 행을 뽑아야 한다면 괄호와 LIMIT의 범위를 명확히 하고 전체 정렬과 구분합니다. 출처별 건수를 확인할 때는 합친 뒤의 중복 제거가 원래 행 수를 바꾸었는지도 살펴봅니다.

합치기 전 두 결과를 확인하고 정리하기

각 조회를 단독으로 실행해 열 의미와 행 수를 확인한 뒤 UNION ALL 결과부터 대조하면 겹치는 행의 출처를 이해하기 쉽습니다. 그다음 중복 제거가 필요한지 판단합니다. UNION은 원본 테이블의 행을 삭제하거나 두 테이블을 영구적으로 하나로 만드는 명령이 아닙니다.

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

DROP TABLE shopcourse.c30_items;
DROP TABLE shopcourse.c30_orders;
DROP TABLE shopcourse.c30_products;
DROP TABLE shopcourse.c30_customers;
같은 카테고리의 다른 글
MariaDB 강좌 / 명령줄 클라이언트로 접속하고 SQL 파일 실행하기

MariaDB 강좌 / 명령줄 클라이언트로 접속하고 SQL 파일 실행하기

터미널 명령과 SQL 입력 위치를 구분하고 학습 계정으로 접속합니다. UTF-8 SQL 파일의 SOURCE 실행, 예상 결과, 오류 중단·한글 표시·경로 문제를 함께 확인합니다.

MariaDB 강좌 / 트랜잭션 시작하기: InnoDB·COMMIT·ROLLBACK

MariaDB 강좌 / 트랜잭션 시작하기: InnoDB·COMMIT·ROLLBACK

InnoDB에서 같은 변경을 ROLLBACK과 COMMIT으로 끝내어 미확정·확정의 차이를 비교합니다. 연결 유지, 영향 행 수 검사, 오류 처리와 구조 변경·외부 작업의 취소 한계를 설명합니다.

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

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

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

MariaDB 강좌 / 중복 데이터를 줄이는 정규화와 주문 데이터베이스 설계

MariaDB 강좌 / 중복 데이터를 줄이는 정규화와 주문 데이터베이스 설계

주문 상세에 반복한 고객 정보의 변경 오류를 관찰하고 키별 사실을 네 테이블로 나눕니다. 부분·이행 종속, 거래 당시 단가와 스냅샷 보존, 무결성 제약과 성능의 별도 검토를 설명합니다.

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

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

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

MariaDB 강좌 / Docker로 MariaDB 실습 환경 만들기

MariaDB 강좌 / Docker로 MariaDB 실습 환경 만들기

Compose 파일로 MariaDB 포트·암호 파일·데이터 볼륨·헬스체크를 구성합니다. 연결 확인과 컨테이너 교체 실습을 통해 데이터 유지, 초기화와 접속 오류의 차이를 이해합니다.

MariaDB 강좌 / ALTER TABLE로 구조 변경하기: 열·제약 조건·변경 전 점검

MariaDB 강좌 / ALTER TABLE로 구조 변경하기: 열·제약 조건·변경 전 점검

ALTER TABLE을 새 코드 열 추가·기존 행 채우기·제약 적용으로 나누어 실습합니다. NULL·중복 사전 검사, MODIFY 속성 보존, 실제 실패 입력과 잠금·복구 계획을 설명합니다.

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

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

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

MariaDB / 샘플 데이터베이스 employees 설치하는 방법

MariaDB / 샘플 데이터베이스 employees 설치하는 방법

데이터베이스를 다루는 공부를 할 때 데이터가 들어있는 데이터베이스가 있으면 좋습니다. 데이터베이스를 직접 만드는 방법도 있지만, 공개된 샘플 데이터베이스를 이용하면 편합니다. 예를 들어 SQL Server라면 AdventureWorks라는 샘플 데이터베이스를 마이크로소프트에서 제공합니다. MariaDB라면 employees를 사용하면 됩니다.

MariaDB 강좌 / INNER JOIN으로 서로 연결된 데이터 조회하기

MariaDB 강좌 / INNER JOIN으로 서로 연결된 데이터 조회하기

고객과 주문을 INNER JOIN으로 연결하고 결과 한 행의 의미를 확인합니다. ON과 WHERE, 테이블 별칭, 일대다로 늘어나는 행과 조건 없는 곱집합을 비교합니다.