MySQL 고급 쿼리 기법과 내장 함수 활용

중첩 쿼리 분석

하나의 SELECT 문의 실행 결과를 다른 쿼리의 필터 조건으로 활용하는 기법입니다. 내부 쿼리가 먼저 실행되며, 그 결과가 외부 쿼리에 전달됩니다.

-- 상품 분류 테이블 활용 예시
-- '전자기기' 카테고리에 속한 상품 목록 조회
SELECT category_code FROM item_category WHERE category_title = '전자기기';
SELECT * FROM merchandise WHERE category_code = (SELECT category_code FROM item_category WHERE category_title = '전자기기');

-- 특정 상품의 카테고리명 확인
SELECT category_title FROM item_category WHERE category_code = (SELECT category_code FROM merchandise WHERE item_name = '갤럭시');

-- 평균 가격보다 비싼 상품 검색
SELECT * FROM merchandise WHERE unit_price > (SELECT AVG(unit_price) FROM merchandise);

데카르트 곱(Cross Join)

두 테이블의 모든 행을 조합하여 반환하며, 실무에서는 거의 사용되지 않습니다.

-- 카테시안 곱 생성 방식
SELECT * FROM category_master, product_master;
SELECT * FROM product_master CROSS JOIN category_master;

내부 조인(Inner Join)

양쪽 테이블의 공통된 데이터만 반환하는 조인 방식입니다.

-- 암시적 내부 조인 (쉼표 구분)
SELECT * FROM product_master pm, category_master cm WHERE cm.category_id = pm.cat_id;

-- 명시적 내부 조인 (JOIN 키워드 사용)
SELECT * FROM category_master cm INNER JOIN product_master pm ON cm.category_id = pm.cat_id;
-- INNER 생략 가능
SELECT * FROM category_master cm JOIN product_master pm ON cm.category_id = pm.cat_id;

왼쪽 외부 조인(Left Outer Join)

왼쪽 테이블의 모든 레코드를 포함하고, 오른쪽 테이블에서 매칭되는 데이터가 없으면 NULL로 채웁니다.

-- 카테고리 기준 상품 정보 조회 (상품이 없는 카테고리도 표시)
SELECT * FROM category_master cm LEFT JOIN product_master pm ON cm.category_id = pm.cat_id;

-- 상품 기준 카테고리 정보 조회
SELECT * FROM product_master pm LEFT JOIN category_master cm ON cm.category_id = pm.cat_id;

오른쪽 외부 조인(Right Outer Join)

오른쪽 테이블의 모든 레코드를 기준으로 조인합니다.

SELECT * FROM category_master cm RIGHT JOIN product_master pm ON cm.category_id = pm.cat_id;

재귀적 자기 조인(Self Join)

동일한 테이블을 두 개의 별칭으로 분리하여 계층 구조를 표현합니다.

-- 지역 계층 테이블 생성
CREATE TABLE region_hierarchy (
    region_id INT PRIMARY KEY AUTO_INCREMENT,
    region_name VARCHAR(20),
    parent_region_id INT
);

INSERT INTO region_hierarchy VALUES 
(1, '서울특별시', NULL), (2, '부산광역시', NULL),
(3, '강남구', 1), (4, '서초구', 1), (5, '해운대구', 2);

-- 상위-하위 지역 관계 조회
SELECT r1.region_id AS 상위코드, r1.region_name AS 상위지역,
       r2.region_name AS 하위지역, r2.region_id AS 하위코드
FROM region_hierarchy r1 
JOIN region_hierarchy r2 ON r1.region_id = r2.parent_region_id;

-- 특정 상위 지역의 하위 목록 필터링
SELECT a.region_id, a.region_name, b.region_name, b.region_id 
FROM region_hierarchy a 
JOIN region_hierarchy b ON a.region_id = b.parent_region_id 
WHERE a.region_name = '서울특별시';

중첩 쿼리의 세 가지 활용 패턴

-- 패턴 1: 단일 값 비교 (WHERE 절)
SELECT * FROM employee WHERE monthly_salary > (SELECT AVG(monthly_salary) FROM employee);

-- 패턴 2: 다중 값 비교 (IN 연산자)
SELECT * FROM employee WHERE dept_code IN 
(SELECT dept_code FROM department WHERE dept_name IN ('영업팀', '재무팀'));

-- 패턴 3: 파생 테이블 (FROM 절 서브쿼리)
SELECT d.dept_name, d.office_location, e.* 
FROM department d 
JOIN (SELECT * FROM employee WHERE monthly_salary > 5000) e 
ON d.dept_code = e.dept_code;

UNION 세로 결합

여러 SELECT 결과를 세로로 연결하며, 컬럼 수와 데이터 타입이 일치해야 합니다.

-- 중복 제거 (UNION)
SELECT * FROM employee WHERE monthly_salary > 8000
UNION
SELECT * FROM employee WHERE dept_code = 10;

-- 중복 포함 (UNION ALL)
SELECT * FROM employee WHERE monthly_salary > 8000
UNION ALL
SELECT * FROM employee WHERE dept_code = 10;

-- 서로 다른 테이블의 동일 구조 데이터 결합
CREATE TABLE instructor (
    emp_no INT, emp_name VARCHAR(20), gender VARCHAR(10)
);
CREATE TABLE trainee (
    student_no INT, student_name VARCHAR(20), gender VARCHAR(10)
);

SELECT emp_no AS id, emp_name AS name, gender FROM instructor WHERE gender = '남성'
UNION
SELECT student_no, student_name, gender FROM trainee WHERE gender = '남성';

수학 연산 함수

-- 반올림 처리
SELECT ROUND(3.141592);      -- 3
SELECT ROUND(3.141592, 2);   -- 3.14
SELECT ROUND(3.145, 2);      -- 3.15

-- 천 단위 구분 포맷
SELECT FORMAT(12345.1415926, 3);  -- 12,345.142

-- 버림/올림
SELECT FLOOR(12345.941592);  -- 12345 (내림)
SELECT CEIL(12345.141592);   -- 12346 (올림)

-- 나머지, 거듭제곱, 난수
SELECT MOD(17, 5);           -- 2
SELECT POW(3, 4);            -- 81
SELECT RAND();               -- 0~1 사이 무작위 실수
SELECT RAND(100);            -- 시드 기반 고정 난수

문자열 처리 함수

-- 대소문자 변환
SELECT UPPER('hello'), LOWER('WORLD');

-- 문자열 치환
SELECT REPLACE('데이터 분석가', '데이터', 'AI');

-- 문자열 결합
SELECT CONCAT('A', 'B', 'C', 123);           -- 단순 결합
SELECT CONCAT_WS('|', 'A', 'B', 'C');        -- 구분자 포함 결합

-- 반복, 역순, 추출
SELECT REPEAT('안녕', 3);                    -- 안녕안녕안녕
SELECT REVERSE('Python');                    -- nohtyP
SELECT SUBSTRING('데이터베이스', 3, 3);       -- 베이스
SELECT LEFT('데이터베이스', 4);              -- 데이터
SELECT RIGHT('데이터베이스', 3);             -- 이스

-- 길이 측정
SELECT CHAR_LENGTH('한글');                  -- 문자 수: 2
SELECT LENGTH('한글');                       -- 바이트 수: 6 (UTF-8)

-- 실용 예시: 이름의 첫 글자만 대문자로
SELECT CONCAT(UPPER(LEFT(student_name, 1)), SUBSTRING(student_name, 2)) FROM student;

날짜/시간 함수

-- 현재 시점 조회
SELECT NOW();                    -- 2024-09-25 15:56:31
SELECT CURDATE();                -- 2024-09-25
SELECT CURTIME();                -- 15:57:15

-- 날짜 차이 계산
SELECT DATEDIFF('2024-12-25', '2024-01-01');  -- 358

-- 날짜 가감
SELECT DATE_ADD(NOW(), INTERVAL 30 DAY);
SELECT DATE_SUB(NOW(), INTERVAL 6 MONTH);

-- 특정 구성요소 추출
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW()), HOUR(NOW());

-- 형식 지정 출력
SELECT DATE_FORMAT(NOW(), '%Y년 %m월 %d일 %H시 %i분 %s초');

-- 유닉스 타임스탬프 변환
SELECT UNIX_TIMESTAMP();         -- 현재 초 단위 타임스탬프
SELECT FROM_UNIXTIME(1700000000); -- 초를 날짜로 변환

실전 활용 사례

-- 연도별 마지막 로그인 시간 조회
CREATE TABLE user_access_log (
    user_id INT,
    access_time DATETIME
);

-- 2020년 각 사용자의 마지막 접속 기록
SELECT user_id, MAX(access_time) AS last_login
FROM user_access_log
WHERE YEAR(access_time) = 2020
GROUP BY user_id;

태그: MySQL SQL JOIN Subquery Aggregate Function

10월 11일 08:05에 게시됨