Database/Mysql

MySQL 명령어 : JOIN

js-kkk 2025. 2. 20. 17:40

 

MySQL 의 명령어 중 JOIN에 대한 설명과 예제코드에 대한 내용이다.

사용할 db 정보는 글 아랫부분에 있다.

 

JOIN

JOIN은 두개 이상의 테이블을 관련 있는 컬럼을 통해 결합하는데 사용된다. 두개 이상의 테이블은 반드시 연관 있는 컬럼이 존재해야 하며 이를 통해 JOIN된 테이블들의 컬럼을 모두 활용할 수 있다.

 

ALIAS

  • SQL문의 컬럼 또는 테이블에 별칭을 달아줄 수 있다. 이러한 별칭을 ALIAS라고 한다.
  • 컬럼 별칭
    • resultSet의 컬럼명이 별칭으로 바뀜
    • 별칭에 띄어쓰기나 특수 기호가 없다면 홑 따옴표(')와 AS는 생략 가능하다.
SELECT 
    menu_code AS 'code',
    menu_name AS name,
    menu_price 'price'
FROM
    tbl_menu
ORDER BY price;

 

 

  • 테이블 별칭
    • 테이블에 별칭을 작성할 수 있으며 어떤 테이블 소속 인지를 쉽게 알 수 있게 한다.
    • 테이블 별칭은 AS를 써도 되고 생략도 가능하다.
SELECT
    a.category_code,
    a.menu_name
FROM
    -- tbl_menu AS a
    tbl_menu a
ORDER BY 
    a.category_code,
    a.menu_name;

 

 

JOIN의 종류

    INNER JOIN

  • 두 테이블의 교집합을 반환하는 SQL JOIN 유형
  • INNER JOIN에서 INNER 키워드는 생략이 가능하다.
    • ON을 활용한 JOIN
      • 컬럼명이 같거나 다를 경우 ON으로 서로 연관 있는 컬럼에 대한 조건을 작성하여 JOIN하는 경우
SELECT 
    a.menu_name,
    b.category_name
FROM 
    tbl_menu a
-- INNER JOIN tbl_category b ON a.category_code = b.category_code;
JOIN tbl_category b ON a.category_code = b.category_code;

 

 

 

  • USING을 활용한 JOIN
    • 컬럼명이 같을 경우 USING으로 서로 연관 있는 컬럼에 대한 조건을 작성하여 JOIN하는 경우
SELECT 
    a.menu_name,
    b.category_name
FROM 
    tbl_menu a
INNER JOIN tbl_category b USING (category_code);

 

 

LEFT JOIN

  • 첫 번째(왼쪽) 테이블의 모든 레코드와 두 번째(오른쪽) 테이블에서 일치하는 레코드를 반환하는 SQL JOIN 유형
SELECT 
    a.category_name,
    b.menu_name
FROM 
    tbl_category a
LEFT JOIN tbl_menu b ON a.category_code = b.category_code;

 

 

 

RIGHT JOIN

  • 두 번째(오른쪽) 테이블의 모든 레코드와 첫 번째(왼쪽) 테이블에서 일치하는 레코드를 반환하는 SQL JOIN 유형
SELECT 
    a.menu_name,
    b.category_name
FROM 
    tbl_menu a
RIGHT JOIN tbl_category b ON a.category_code = b.category_code;

 

 

CROSS JOIN

  • 두 테이블의 모든 가능한 조합을 반환하는 SQL JOIN 유형
SELECT 
    a.menu_name,
    b.category_name
FROM 
    tbl_menu a
CROSS JOIN tbl_category b;

 

 

SELF JOIN

  • 같은 테이블 내에서 행과 행 사이의 관계를 찾기 위해 사용되는 SQL JOIN 유형
  • 카테고리별 대분류 확인을 위한 SELF JOIN 조회
SELECT
    a.category_name,
    b.category_name
FROM 
    tbl_category a
JOIN tbl_category b ON a.ref_category_code = b.category_code
WHERE a.ref_category_code IS NOT NULL;

 

 

JOIN 알고리즘

    NESTED LOOP JOIN

  • MySQL은 기본적으로 NESTED LOOP JOIN을 사용한다.
  • 두 개 이상의 테이블에서 하나의 집합을 기준으로 순차적으로 상대방 Row를 결합하여 조합하는 방식
  • 중첩 반복문처럼 첫 번째 테이블의 Row와 관련된 두 번째 테이블에 대한 Row를 검색하고 이후 첫 번째 테이블의 다음 Row에 대해 두 번쨰 테이블에 대한 것을 검색하며 이후 이와 같은 방식을 반복한다.
  • MySQL은 기본적으로 NESTED LOOP JOIN을 사용하지만 이를 강제하려면 힌트절을 작성해 HASH JOIN을 사용하지 않도록 강제할 수 있다.
SELECT /*+ NO_HASH_JOIN(a) */
    a.menu_name,
    b.category_name
FROM 
    tbl_menu a
JOIN tbl_category b ON a.category_code = b.category_code;

 

    HASH JOIN

  • MySQL 8.0.18버전 이후 지원하게 되었다.
  • 대규모 데이터 세트에 대한 조인 연산을 효과적으로 진행할 수 있다.
  • 해싱 단계에서 조인을 수행하는 두 테이블 중 작은 쪽을 선택하여 해시 테이블을 만들어 메모리에 저장하고 해시 함수를 사용해서 각 행을 특정 "해시 버킷"에 할당한다.
  • 조인 단계에서 다른 테이블을 순회하며 각 행에 대해 동일한 해시 함수를 사용하여 해당 행이 어떤 버킷에 속하는지 결정하고 이 버킷의 모든 행과 해당 행을 비교하여 조인 조건을 만족한다.
  • 이 방법은 조인할 테이블 중 하나가 메모리에 적합할 만큼 충분히 작아야 한다. 그렇지 않으면 해시 테이블이 메모리를 넘어서 디스크로까지 넘어가고 이는 성능 저하를 초래한다.
  • HASH JOIN은 등가 조인('=' 연산자를 사용하는 조인)에만 사용할 수 있고 비등가 조인에는 사용할 수 없다.
SELECT /*+ HASH_JOIN(a) */
    a.menu_name,
    b.category_name
FROM
    tbl_menu a
JOIN tbl_category b ON a.category_code = b.category_code;

 

 

 

 

 

 

 

아래의 menu db와 employee db를 사용한 코드를 사용할 것 이다. ERD로 나타내면 아래와 같고 실습 전에 그림 밑에 MySQL에서 사용할 db의 script를 넣고 진행하면 된다. 

MENU DB

  • ERD (Entity Relationship Diagram)
    • 논리모델

  • 물리모델

use menudb;

-- 테이블 삭제
DROP TABLE IF EXISTS tbl_payment_order CASCADE;
DROP TABLE IF EXISTS tbl_payment CASCADE;
DROP TABLE IF EXISTS tbl_order_menu CASCADE;
DROP TABLE IF EXISTS tbl_order CASCADE;
DROP TABLE IF EXISTS tbl_menu CASCADE;
DROP TABLE IF EXISTS tbl_category CASCADE;

-- 테이블 생성
-- category 테이블 생성
CREATE TABLE IF NOT EXISTS tbl_category
(
    category_code    INT AUTO_INCREMENT COMMENT '카테고리코드',
    category_name    VARCHAR(30) NOT NULL COMMENT '카테고리명',
    ref_category_code    INT COMMENT '상위카테고리코드',
    CONSTRAINT pk_category_code PRIMARY KEY (category_code),
    CONSTRAINT fk_ref_category_code FOREIGN KEY (ref_category_code) REFERENCES tbl_category (category_code)
) ENGINE=INNODB COMMENT '카테고리';

CREATE TABLE IF NOT EXISTS tbl_menu
(
    menu_code    INT AUTO_INCREMENT COMMENT '메뉴코드',
    menu_name    VARCHAR(30) NOT NULL COMMENT '메뉴명',
    menu_price    INT NOT NULL COMMENT '메뉴가격',
    category_code    INT NOT NULL COMMENT '카테고리코드',
    orderable_status    CHAR(1) NOT NULL COMMENT '주문가능상태',
    CONSTRAINT pk_menu_code PRIMARY KEY (menu_code),
    CONSTRAINT fk_category_code FOREIGN KEY (category_code) REFERENCES tbl_category (category_code)
) ENGINE=INNODB COMMENT '메뉴';


CREATE TABLE IF NOT EXISTS tbl_order
(
    order_code    INT AUTO_INCREMENT COMMENT '주문코드',
    order_date    VARCHAR(8) NOT NULL COMMENT '주문일자',
    order_time    VARCHAR(8) NOT NULL COMMENT '주문시간',
    total_order_price    INT NOT NULL COMMENT '총주문금액',
    CONSTRAINT pk_order_code PRIMARY KEY (order_code)
) ENGINE=INNODB COMMENT '주문';


CREATE TABLE IF NOT EXISTS tbl_order_menu
(
    order_code INT NOT NULL COMMENT '주문코드',
    menu_code    INT NOT NULL COMMENT '메뉴코드',
    order_amount    INT NOT NULL COMMENT '주문수량',
    CONSTRAINT pk_comp_order_menu_code PRIMARY KEY (order_code, menu_code),
    CONSTRAINT fk_order_menu_order_code FOREIGN KEY (order_code) REFERENCES tbl_order (order_code),
    CONSTRAINT fk_order_menu_menu_code FOREIGN KEY (menu_code) REFERENCES tbl_menu (menu_code)
) ENGINE=INNODB COMMENT '주문별메뉴';


CREATE TABLE IF NOT EXISTS tbl_payment
(
    payment_code    INT AUTO_INCREMENT COMMENT '결제코드',
    payment_date    VARCHAR(8) NOT NULL COMMENT '결제일',
    payment_time    VARCHAR(8) NOT NULL COMMENT '결제시간',
    payment_price    INT NOT NULL COMMENT '결제금액',
    payment_type    VARCHAR(6) NOT NULL COMMENT '결제구분',
    CONSTRAINT pk_payment_code PRIMARY KEY (payment_code)
) ENGINE=INNODB COMMENT '결제';


CREATE TABLE IF NOT EXISTS tbl_payment_order
(
    order_code    INT NOT NULL COMMENT '주문코드',
    payment_code    INT NOT NULL COMMENT '결제코드',
    CONSTRAINT pk_comp_payment_order_code PRIMARY KEY (payment_code, order_code),
    CONSTRAINT fk_payment_order_order_code FOREIGN KEY (order_code) REFERENCES tbl_order (order_code),
    CONSTRAINT fk_payment_order_payment_code FOREIGN KEY (order_code) REFERENCES tbl_payment (payment_code)
) ENGINE=INNODB COMMENT '결제별주문';

-- 데이터 삽입
INSERT INTO tbl_category VALUES (null, '식사', null);
INSERT INTO tbl_category VALUES (null, '음료', null);
INSERT INTO tbl_category VALUES (null, '디저트', null);
INSERT INTO tbl_category VALUES (null, '한식', 1);
INSERT INTO tbl_category VALUES (null, '중식', 1);

INSERT INTO tbl_category VALUES (null, '일식', 1);
INSERT INTO tbl_category VALUES (null, '퓨전', 1);
INSERT INTO tbl_category VALUES (null, '커피', 2);
INSERT INTO tbl_category VALUES (null, '쥬스', 2);
INSERT INTO tbl_category VALUES (null, '기타', 2);

INSERT INTO tbl_category VALUES (null, '동양', 3);
INSERT INTO tbl_category VALUES (null, '서양', 3);

INSERT INTO tbl_menu VALUES (null, '열무김치라떼', 4500, 8, 'Y');
INSERT INTO tbl_menu VALUES (null, '우럭스무디', 5000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '생갈치쉐이크', 6000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '갈릭미역파르페', 7000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '앙버터김치찜', 13000, 4, 'N');

INSERT INTO tbl_menu VALUES (null, '생마늘샐러드', 12000, 4, 'Y');
INSERT INTO tbl_menu VALUES (null, '민트미역국', 15000, 4, 'Y');
INSERT INTO tbl_menu VALUES (null, '한우딸기국밥', 20000, 4, 'Y');
INSERT INTO tbl_menu VALUES (null, '홍어마카롱', 9000, 12, 'Y');
INSERT INTO tbl_menu VALUES (null, '코다리마늘빵', 7000, 12, 'N');

INSERT INTO tbl_menu VALUES (null, '정어리빙수', 10000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '날치알스크류바', 2000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '직화구이젤라또', 8000, 12, 'Y');
INSERT INTO tbl_menu VALUES (null, '과메기커틀릿', 13000, 6, 'Y');
INSERT INTO tbl_menu VALUES (null, '죽방멸치튀김우동', 11000, 6, 'N');

INSERT INTO tbl_menu VALUES (null, '흑마늘아메리카노', 9000, 8, 'Y');
INSERT INTO tbl_menu VALUES (null, '아이스가리비관자육수', 6000, 10, 'Y');
INSERT INTO tbl_menu VALUES (null, '붕어빵초밥', 35000, 6, 'Y');
INSERT INTO tbl_menu VALUES (null, '까나리코코넛쥬스', 9000, 9, 'Y');
INSERT INTO tbl_menu VALUES (null, '마라깐쇼한라봉', 22000, 5, 'N');

INSERT INTO tbl_menu VALUES (null, '돌미나리백설기', 5000, 11, 'Y');

COMMIT;

 

EMPLOYEE DB

  • ERD (Entity Relationship Diagram)
    • 논리모델

  •  물리모델

use employeedb;

DROP TABLE IF EXISTS `EMPLOYEE`;
DROP TABLE IF EXISTS `DEPARTMENT`;
DROP TABLE IF EXISTS `JOB`;
DROP TABLE IF EXISTS `LOCATION`;
DROP TABLE IF EXISTS `NATION`;
DROP TABLE IF EXISTS `SAL_GRADE`;

CREATE TABLE SAL_GRADE
(
    `SAL_LEVEL`    CHAR(2)  NOT NULL COMMENT '급여등급',
    `MIN_SAL`    DECIMAL COMMENT '최소급여',
    `MAX_SAL`    DECIMAL COMMENT '최대급여',
 PRIMARY KEY ( `SAL_LEVEL` )
)
 COMMENT = '급여등급';
 
INSERT INTO SAL_GRADE (SAL_LEVEL,MIN_SAL,MAX_SAL) VALUES 
('S1',6000000,10000000),
('S2',5000000,5999999),
('S3',4000000,4999999),
('S4',3000000,3999999),
('S5',2000000,2999999),
('S6',1000000,1999999);


CREATE TABLE NATION
(
    `NATIONAL_CODE`    CHAR(2)  NOT NULL COMMENT '국가코드',
    `NATIONAL_NAME`    VARCHAR(35) COMMENT '국가명',
 PRIMARY KEY ( `NATIONAL_CODE` )
)
 COMMENT = '국가';
 
INSERT INTO NATION (NATIONAL_CODE,NATIONAL_NAME) VALUES 
('KO','한국'),
('JP','일본'),
('CH','중국'),
('US','미국'),
('RU','러시아');


CREATE TABLE `LOCATION`
(
    `LOCAL_CODE`    CHAR(2)  NOT NULL COMMENT '지역코드',
    `NATIONAL_CODE`    CHAR(2) NOT NULL COMMENT '국가코드',
    `LOCAL_NAME`    VARCHAR(40) COMMENT '지역명',
 PRIMARY KEY ( `LOCAL_CODE` ),
 FOREIGN KEY (`NATIONAL_CODE`) REFERENCES NATION (`NATIONAL_CODE`)
)
 COMMENT = '지역';

INSERT INTO LOCATION (LOCAL_CODE,NATIONAL_CODE,LOCAL_NAME) VALUES 
('L1','KO','ASIA1'),
('L2','JP','ASIA2'),
('L3','CH','ASIA3'),
('L4','US','AMERICA'),
('L5','RU','EU');


CREATE TABLE `JOB`
(
    `JOB_CODE`    CHAR(2)  NOT NULL COMMENT '직급코드',
    `JOB_NAME`    VARCHAR(35) COMMENT '직급명',
 PRIMARY KEY ( `JOB_CODE` )
)
 COMMENT = '직급';
 
INSERT INTO JOB (JOB_CODE,JOB_NAME) VALUES 
('J1','대표'),
('J2','부사장'),
('J3','부장'),
('J4','차장'),
('J5','과장'),
('J6','대리'),
('J7','사원');


CREATE TABLE `DEPARTMENT`
(
    `DEPT_ID`    CHAR(2)  NOT NULL COMMENT '부서코드',
    `DEPT_TITLE`    VARCHAR(35) NOT NULL COMMENT '부서명',
    `LOCATION_ID`    CHAR(2) NOT NULL COMMENT '지역코드',
 PRIMARY KEY ( `DEPT_ID` ),
 FOREIGN KEY (`LOCATION_ID`) REFERENCES LOCATION (`LOCAL_CODE`)
)
 COMMENT = '부서';
 
INSERT INTO DEPARTMENT (DEPT_ID,DEPT_TITLE,LOCATION_ID) VALUES 
('D1','인사관리부','L1'),
('D2','회계관리부','L1'),
('D3','마케팅부','L1'),
('D4','국내영업부','L1'),
('D5','해외영업1부','L2'),
('D6','해외영업2부','L3'),
('D7','해외영업3부','L4'),
('D8','기술지원부','L5'),
('D9','총무부','L1');


CREATE TABLE `EMPLOYEE`
(
    `EMP_ID`    VARCHAR(3)  NOT NULL COMMENT '사원번호',
    `EMP_NAME`    VARCHAR(20) NOT NULL COMMENT '직원명',
    `EMP_NO`    CHAR(14) NOT NULL COMMENT '주민등록번호',
    `EMAIL`    VARCHAR(35) COMMENT '이메일',
    `PHONE`    VARCHAR(12) COMMENT '전화번호',
    `DEPT_CODE`    CHAR(2) COMMENT '부서코드',
    `JOB_CODE`    CHAR(2) NOT NULL COMMENT '직급코드',
    `SAL_LEVEL`    CHAR(2) NOT NULL COMMENT '급여등급',
    `SALARY`    DECIMAL COMMENT '급여',
    `BONUS`    float COMMENT '보너스율',
    `MANAGER_ID`    VARCHAR(3) COMMENT '관리자사번',
    `HIRE_DATE`    DATE COMMENT '입사일',
    `ENT_DATE`    DATE COMMENT '퇴사일',
    `ENT_YN`    CHAR(1) DEFAULT 'N' COMMENT '퇴직여부',
 PRIMARY KEY ( `EMP_ID` ),
 FOREIGN KEY (`DEPT_CODE`) REFERENCES DEPARTMENT (`DEPT_ID`),
 FOREIGN KEY (`JOB_CODE`) REFERENCES JOB (`JOB_CODE`),
 FOREIGN KEY (`SAL_LEVEL`) REFERENCES SAL_GRADE (`SAL_LEVEL`)
)
 COMMENT = '사원';
 
INSERT INTO EMPLOYEE (EMP_ID,EMP_NAME,EMP_NO,EMAIL,PHONE,DEPT_CODE,JOB_CODE,SAL_LEVEL,SALARY,BONUS,MANAGER_ID,HIRE_DATE,ENT_DATE,ENT_YN) VALUES 
('200','선동일','621235-1985634','sun_di@greedy.com','01099546325','D9','J1','S1',8000000,0.3,null,STR_TO_DATE('90/02/06','%y/%m/%d'),null,'N'),
('201','송종기','631156-1548654','song_jk@greedy.com','01045686656','D9','J2','S1',6000000,null,'200',STR_TO_DATE('01/09/01','%y/%m/%d'),null,'N'),
('202','노옹철','861015-1356452','no_oc@greedy.com','01066656263','D9','J2','S4',3700000,null,'201',STR_TO_DATE('01/01/01','%y/%m/%d'),null,'N'),
('203','송은희','631010-2653546','song_eh@greedy.com','01077607879','D6','J4','S5',2800000,null,'204',STR_TO_DATE('96/05/03','%y/%m/%d'),null,'N'),
('204','유재식','660508-1342154','yoo_js@greedy.com','01099999129','D6','J3','S4',3400000,0.2,'200',STR_TO_DATE('00/12/29','%y/%m/%d'),null,'N'),
('205','정중하','770102-1357951','jung_jh@greedy.com','01036654875','D6','J3','S4',3900000,null,'204',STR_TO_DATE('99/09/09','%y/%m/%d'),null,'N'),
('206','박나라','630709-2054321','pack_nr@greedy.com','01096935222','D5','J7','S6',1800000,null,'207',STR_TO_DATE('08/04/02','%y/%m/%d'),null,'N'),
('207','하이유','690402-2040612','ha_iy@greedy.com','01036654488','D5','J5','S5',2200000,0.1,'200',STR_TO_DATE('94/07/07','%y/%m/%d'),null,'N'),
('208','김해술','870927-1313564','kim_hs@greedy.com','01078634444','D5','J5','S5',2500000,null,'207',STR_TO_DATE('04/04/30','%y/%m/%d'),null,'N'),
('209','심봉선','750206-1325546','sim_bs@greedy.com','0113654485','D5','J3','S4',3500000,0.15,'207',STR_TO_DATE('11/11/11','%y/%m/%d'),null,'N'),
('210','윤은해','650505-2356985','youn_eh@greedy.com','0179964233','D5','J7','S5',2000000,null,'207',STR_TO_DATE('01/02/03','%y/%m/%d'),null,'N'),
('211','전형돈','830807-1121321','jun_hd@greedy.com','01044432222','D8','J6','S5',2000000,null,'200',STR_TO_DATE('12/12/12','%y/%m/%d'),null,'N'),
('212','장쯔위','780923-2234542','jang_zw@greedy.com','01066682224','D8','J6','S5',2550000,0.25,'211',STR_TO_DATE('15/06/17','%y/%m/%d'),null,'N'),
('213','하동운','621111-1785463','ha_dh@greedy.com','01158456632',null,'J6','S5',2320000,0.1,null,STR_TO_DATE('99/12/31','%y/%m/%d'),null,'N'),
('214','방명수','856795-1313513','bang_ms@greedy.com','01074127545','D1','J7','S6',1380000,null,'200',STR_TO_DATE('10/04/04','%y/%m/%d'),null,'N'),
('215','대북혼','881130-1050911','dae_bh@greedy.com','01088808584','D5','J5','S4',3760000,null,null,STR_TO_DATE('17/06/19','%y/%m/%d'),null,'N'),
('216','차태연','770808-1364897','cha_ty@greedy.com','01064643212','D1','J6','S5',2780000,0.2,'214',STR_TO_DATE('13/03/01','%y/%m/%d'),null,'N'),
('217','전지연','770808-2665412','jun_jy@greedy.com','01033624442','D1','J6','S4',3660000,0.3,'214',STR_TO_DATE('07/03/20','%y/%m/%d'),null,'N'),
('218','이오리','870427-2232123','lee_or@greedy.com','01022306545',null,'J7','S5',2890000,null,null,STR_TO_DATE('16/11/28','%y/%m/%d'),null,'N'),
('219','임시환','660712-1212123','im_sh@greedy.com',null,'D2','J4','S6',1550000,null,null,STR_TO_DATE('99/09/09','%y/%m/%d'),null,'N'),
('220','이중석','770823-1113111','lee_js@greedy.com',null,'D2','J4','S5',2490000,null,null,STR_TO_DATE('14/09/18','%y/%m/%d'),null,'N'),
('221','유하진','800808-1123341','yoo_hj@greedy.com',null,'D2','J4','S5',2480000,null,null,STR_TO_DATE('94/01/20','%y/%m/%d'),null,'N'),
('222','이태림','760918-2854697','lee_tr@greedy.com','01033000002','D8','J6','S5',2436240,0.35,'100',STR_TO_DATE('97/09/12','%y/%m/%d'),STR_TO_DATE('17/09/12','%y/%m/%d'),'Y');

commit;

 

 

 

 

 

ex)

 

SELECT a.category_name, b.menu_name

FROM tbl_category a

LEFT JOIN tbl_menu b ON a.category_code = b.category_code;

 

tbl_category a LEFT JOIN tbl_menu

LEFT JOIN 기준으로 '왼쪽'이 '베이스 테이블'.

RIGHT JOIN 기준으로 '오른쪽'이 '베이스 테이블'

 

베이스 테이블 ? 

= 기준이 되는 테이블