AD JB Toolbox 01

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

INNER JOIN은 연결 조건이 참인 두 테이블의 행 조합만 반환합니다. 어떤 열로 연결할지 ON에 명시하고, 결과 한 행이 고객인지 주문인지부터 정해야 일대다 관계에서 늘어난 행을 중복 오류로 오해하지 않습니다.

연결 조건과 결과의 단위 정하기

주문 화면에 고객 이름을 보여 주려면 주문의 customer_id와 고객의 기본키를 연결합니다. 두 테이블에 customer_id라는 같은 이름이 있어도 자동으로 올바르게 연결되는 것은 아닙니다. AS로 짧은 테이블 별칭을 정하고 각 열의 소속을 명시합니다. 연결 키의 근거는 테이블 관계 설계하기: 일대다·다대다·외래키의 관계 설계이며, 일치 행이 없는 기준 행까지 남길 때는 LEFT JOIN으로 연결되지 않은 행도 조회하기를 선택합니다.

요소 예 역할
주 테이블 orders AS o 주문 한 건을 기준으로 시작
연결 테이블 customers AS c 고객 이름 읽기
ON 조건 c.customer_id=o.customer_id 관계 키를 대응
WHERE 조건 o.status='paid' 반환할 주문의 업무 조건

고객·주문과 연결 데이터를 함께 준비하기

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

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

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

실습 테이블 네 개를 완전히 준비하므로 다른 JOIN 강좌를 먼저 실행할 필요는 없습니다. 이 글은 customers·orders만 사용하고 나머지는 동일한 주문 데이터의 구조를 유지하기 위해 함께 제공합니다. 학습을 끝낸 뒤에는 이 글의 접두어 c26_ 객체만 정리합니다.

주문과 고객을 기본키로 연결하기

SELECT o.order_id,c.customer_id,c.customer_name,o.status
FROM c26_orders AS o
INNER JOIN c26_customers AS c ON c.customer_id=o.customer_id
ORDER BY o.order_id;
order_id customer_id 고객 상태
101 1 김민수 paid
102 1 김민수 paid
103 2 이서연 paid
104 3 박지우 canceled

김민수 이름이 두 번 보이는 것은 주문 두 건에 각각 대응하기 때문입니다. 결과 한 행은 고객 하나가 아니라 주문 하나입니다. 고객 목록 한 번씩만 필요하면 EXISTS나 목적에 맞는 집계를 검토하며 무조건 DISTINCT로 행을 숨기지 않습니다. MariaDB JOIN 문법에서 ON과 테이블 별칭의 구성을 확인하세요.

연결 조건과 필터 조건을 분리하기

SELECT o.order_id,c.customer_name,o.ordered_at
FROM c26_orders AS o
INNER JOIN c26_customers AS c ON c.customer_id=o.customer_id
WHERE o.status='paid' ORDER BY o.order_id;

paid 주문은 101·102·103번이며 canceled인 104번이 빠집니다. ON은 어떤 고객과 주문이 연결되는지를, WHERE는 연결된 주문 중 어떤 상태를 보여 줄지를 드러냅니다. INNER JOIN에서는 조건을 이동해 같은 결과를 만들 수 있는 경우도 있지만 이후 OUTER JOIN으로 확장할 때 의미가 달라질 수 있으므로 역할을 구분하는 습관이 좋습니다.

양쪽의 customer_id를 모두 표시하면 연결 키가 맞는지 점검하기 쉽습니다. customer_name처럼 업무상 바뀔 수 있는 값으로 연결하거나, 서로 의미가 다른 번호를 이름만 같다는 이유로 연결하지 않습니다.

ON 조건이 빠진 곱집합과 행 증가 확인하기

SELECT COUNT(*) AS unconstrained_pairs
FROM c26_customers AS c CROSS JOIN c26_orders AS o;

고객 4명과 주문 4건의 모든 조합은 16개입니다. CROSS JOIN은 모든 조합이 필요할 때 쓰는 기능이지만 잘못된 JOIN 조건으로 같은 결과를 만들면 각 주문에 엉뚱한 고객까지 연결됩니다. 실제 연결 JOIN의 4행과 비교해 보세요.

외래키가 있는 이 데이터에서는 모든 주문의 부모가 존재하므로 주문 기준 INNER JOIN에서 네 건이 모두 남습니다. 실제로 연결되지 않은 주문이 있을 수 있는 데이터에서는 INNER JOIN이 그 행을 제외하므로, 누락을 점검할 때는 LEFT JOIN과 NULL 검사를 사용해야 합니다.

모호한 열·잘못된 연결을 확인하고 반복하기

Column is ambiguous 오류가 나면 양쪽에 있는 customer_id 같은 열 앞에 o. 또는 c.를 붙입니다. 예상보다 행이 많으면 두 연결 열이 고유한지와 일대다 관계를, 너무 적으면 필터와 연결되지 않는 키를 확인합니다. 결과 정렬도 ORDER BY로 명시하고 원본 저장 순서에 의존하지 않습니다.

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

DROP TABLE shopcourse.c26_items;
DROP TABLE shopcourse.c26_orders;
DROP TABLE shopcourse.c26_products;
DROP TABLE shopcourse.c26_customers;
같은 카테고리의 다른 글
MariaDB 강좌 / 데이터베이스 만들기: CREATE DATABASE·USE·문자 집합

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

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

MariaDB / Query / LEFT JOIN, RIGHT JOIN, INNER JOIN

MariaDB / Query / LEFT JOIN, RIGHT JOIN, INNER JOIN

SQL의 JOIN은 여러 테이블을 한 번에 나타낼 때 사용합니다. 대표적인 JOIN 방식은 LEFT JOIN, RIGHT JOIN, INNER JOIN이 있습니다. 다음은 세 가지 JOIN의 차이를 알아보기 위한 간단한 예제 테이블입니다.

MariaDB 강좌 / COUNT·SUM·AVG·MIN·MAX로 집계하기

MariaDB 강좌 / COUNT·SUM·AVG·MIN·MAX로 집계하기

COUNT·SUM·AVG·MIN·MAX의 집계 대상과 단위를 상품 재고로 비교합니다. NULL·0·빈 결과의 차이, 행별 계산 후 합계, 최솟값과 해당 행을 혼동하지 않는 기준을 설명합니다.

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

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

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

MariaDB / 연산자 / 논리 연산자

MariaDB / 연산자 / 논리 연산자

MariaDB의 논리 연산자에 대하여 알아보겠습니다. 예제로 사용하는 테이블은 아래의 데이터를 가진 T_2이라는 테이블입니다. 각 열의 데이터 형식은 다음과 같습니다. C_1 : 정수 C_2 : 문자 +----+------+------+ | ID | C_1 | C_2 | +----+------+------+ | 1 | 1 | A | | 2 | ...

MariaDB 강좌 / 문자열 타입 선택하기: CHAR·VARCHAR·TEXT

MariaDB 강좌 / 문자열 타입 선택하기: CHAR·VARCHAR·TEXT

CHAR·VARCHAR·TEXT를 저장 목적에 따라 선택하고 문자 수·바이트 수·끝 공백을 비교합니다. 한글과 이모지 입력, 길이 초과 오류, 정렬 규칙과 긴 본문 저장 시 주의점을 설명합니다.

MariaDB 강좌 / SELECT로 열 선택하기: 별칭·계산식·DISTINCT

MariaDB 강좌 / SELECT로 열 선택하기: 별칭·계산식·DISTINCT

SELECT의 열 선택과 AS 별칭, 행별 계산식, DISTINCT의 열 조합 기준을 실습합니다. 원본 데이터와 출력 결과를 구분하고 WHERE에서 별칭을 사용할 때의 주의점을 설명합니다.

MariaDB 강좌 / 첫 실습: 상품 테이블을 만들고 데이터를 넣어 조회하기

MariaDB 강좌 / 첫 실습: 상품 테이블을 만들고 데이터를 넣어 조회하기

상품 세 건을 저장하는 전체 SQL로 테이블 생성·입력·조회 과정을 실습합니다. 구조와 행 수, 가격·재고 조건의 예상 결과를 확인하고 반복 입력 오류와 실습 테이블 초기화 범위를 구분합니다.

MariaDB 강좌 / 여러 테이블 JOIN하기: 중복 행과 집계 오류 확인

MariaDB 강좌 / 여러 테이블 JOIN하기: 중복 행과 집계 오류 확인

고객·주문·상세·상품을 JOIN하고 행 증가에 따른 배송비 중복 합산을 확인합니다. COUNT DISTINCT와 SUM DISTINCT의 차이, 주문별 선집계로 올바른 금액을 얻는 방법을 설명합니다.

MariaDB 강좌 / 윈도 함수로 누적 합계와 이전·다음 행 비교하기

MariaDB 강좌 / 윈도 함수로 누적 합계와 이전·다음 행 비교하기

누적 합계와 LAG·LEAD를 계산하고 동점 데이터로 ROWS·RANGE를 비교합니다. 날짜 필터를 누적 전후에 적용한 결과와 정렬·기간 경계를 확인합니다.