SQL 기초
1. 데이터 제어 언어(DCL)
1.1 정의 및 개요
- DCL(Data Control Language, 데이터 제어 언어)
- 데이터베이스의 보안과 무결성을 유지하기 위해 사용자 계정에 권한을 부여하거나 회수하는 명령어
- 실무에서 데이터베이스 관리자(DBA)나 인프라 엔지니어가 주로 사용
- 그러나 백엔드 개발자나 데이터 엔지니어도 로컬/운영 환경 구성을 위해 반드시 알아야 하는 영역
1.2 DCL의 핵심 명령어
- GRANT (권한 부여)
- 특정 사용자 계정이나 역할(Role)에게 데이터베이스, 테이블, 뷰 등의 객체를 다룰 수 있는 권한을 명시적으로 허용해 주는 명령어
- 주요 권한 종류: SELECT(조회), INSERT(삽입), UPDATE(수정), DELETE(삭제), ALL PRIVILEGES(모든 권한)
- 실무에서는 보안을 위해 필요한 최소한의 권한(SELECT, INSERT 등)만 지정하여 부여하는 것이 원칙
기본 구문 형식
GRANT 권한종류1, 권한종류2 ON 데이터베이스명.테이블명 TO '사용자명'@'접속IP';
- 특정 사용자 계정이나 역할(Role)에게 데이터베이스, 테이블, 뷰 등의 객체를 다룰 수 있는 권한을 명시적으로 허용해 주는 명령어
- REVOKE (권한 회수)
- 특정 사용자 계정에게 이미 부여되어 있던 데이터베이스 객체 접근 권한을 회수(박탈)하는 명령어
- 프로젝트가 종료되었거나, 담당자의 보직이 변경되었을 때 시스템 보안을 유지하기 위해 사용
기본 구문 형식
REVOKE 권한종류1, 권한종류2 ON 데이터베이스명.테이블명 FROM '사용자명'@'접속IP';
1.3 실습 예제
- [시나리오]
- 회사에 데이터 분석가 신입 사원(
ana_min)이 입사.- 이 사원에게
company_db데이터베이스의sales테이블을 조회할 수 있는 권한을 주었다가,- 프로젝트가 종료되어 다시 권한을 회수하는 상황
- 권한 부여하기 (
GRANT)- 권한을 부여하기 전에, 안전한 원격 접속을 위해 사용자를 생성한 후 권한을 부여하는 것이 실무 표준
-- [준비] 모든 IP('%')에서 접속 가능한 'ana_min' 계정 생성 CREATE USER 'ana_min'@'%' IDENTIFIED BY 'Password123!'; -- [DCL 실행] 특정 테이블의 조회(SELECT) 권한 부여 GRANT SELECT ON company_db.sales TO 'ana_min'@'%'; -- [권한 적용] 변경된 권한을 메모리에 즉시 반영 FLUSH PRIVILEGES;GRANT SELECT:- 허용할 행위(권한 종류)를 명시
- 여기서는 데이터를 읽을 수만 있도록
SELECT권한만 지정함 - 만약 데이터를 넣고 수정하게 하려면
SELECT, INSERT, UPDATE처럼 쉼표로 연결할 수 있음
ON company_db.sales:- 권한을 적용할 대상을 구체적으로 지정
- 만약
company_db.*라고 적으면 해당 DB 안의 모든 테이블에 권한을 준다는 의미
- 만약
- [ 데이터베이스명.테이블명 ] 구조
- 권한을 적용할 대상을 구체적으로 지정
TO 'ana_min'@'%':- 권한을 받을 ‘대상 계정’을 지정
- 뒤의
%는 어떤 IP 주소로 접속하든 상관없이 허용하겠다는 뜻
FLUSH PRIVILEGES:- MySQL의 권한 테이블을 재로드하여 방금 실행한
GRANT명령을 즉시 시스템에 동기화
- MySQL의 권한 테이블을 재로드하여 방금 실행한
- 권한 회수하기 (
REVOKE)- 프로젝트가 끝나 보안을 위해
ana_min계정의 조회 권한을 박탈해야 하는 상황
-- [DCL 실행] 이미 부여했던 조회(SELECT) 권한 박탈 REVOKE SELECT ON company_db.sales FROM 'ana_min'@'%'; -- [권한 적용] 변경된 권한을 메모리에 즉시 반영 FLUSH PRIVILEGES;REVOKE SELECT:- 회수할 권한의 종류를 명시
ON company_db.sales:- 권한을 빼앗을 대상 지정
- 부여할 때와 정확히 일치하는 대상을 적어야 오류가 나지 않음
FROM 'ana_min'@'%':- 누구에게서 빼앗을지 대상을 지정
GRANT문에서는TO를 썼지만,REVOKE문에서는 빼앗아 오는 것이므로FROM을 사용함
- 프로젝트가 끝나 보안을 위해
- 실무에서 자주 쓰는 권한 프리셋 (종합 예제)
실무에서는 업무 효율을 위해 권한을 묶어서 부여하는 경우가 많음
- 데이터 분석가용 (읽기 전용 권한)
- 데이터를 변경하면 안 되고 오직 통계 및 조회만 해야 하는 계정에 부여
GRANT SELECT ON company_db.* TO 'analyst_user'@'%'; - 애플리케이션 개발자용 (데이터 조작 전체 권한)
- 웹 서비스 코드가 DB와 연동되어 데이터를 읽고, 쓰고, 고치고, 지워야 할 때 부여
- 테이블 구조 자체를 건드리는 DDL 권한(
DROP등)은 제외하여 안전성을 높임
GRANT SELECT, INSERT, UPDATE, DELETE ON company_db.* TO 'dev_user'@'%'; - 서비스 관리자용 (슈퍼 유저 권한)
- 해당 데이터베이스에 대해서 테이블을 만들고 부수는 DDL을 포함해 모든 제어를 가능하게 할 때 부여
- 권한의 최소화 원칙
- 보안 사고를 막기 위해 실무에서는 항상 업무에 필요한 최소한의 권한만 주는 것이 원칙 🡲 개발자 계정에게
ALL PRIVILEGES남발 금지
- 보안 사고를 막기 위해 실무에서는 항상 업무에 필요한 최소한의 권한만 주는 것이 원칙 🡲 개발자 계정에게
GRANT ALL PRIVILEGES ON company_db.* TO 'admin_user'@'%';
2. 데이터 정의 언어(DDL)
2.1 정의 및 개요
- DDL(Data Definition Language, 데이터 정의 언어)
- 데이터베이스의 ‘틀(구조)’을 정의, 변경, 삭제할 때 사용하는 명령어
- DML(
SELECT등)이 바구니 안에 담기는 ‘알맹이(데이터)’를 다룬다면 🡲 DDL은 그 알맹이를 담는 ‘바구니(테이블, 데이터베이스 등) 자체’를 다룸 - DDL은 실행하는 즉시 데이터베이스에 반영(Auto Commit)되므로 사용 시 각별한 주의가 필요함
2.2 DDL의 핵심 명령어
- CREATE:
- 데이터베이스, 테이블, 뷰, 인덱스 등의 데이터베이스 객체를 새로 생성
- 생성 시 각 컬럼의 이름, 데이터 타입, 그리고 무결성을 위한 제약조건(PRIMARY KEY, NOT NULL 등)을 함께 정의함
기본 구문 형식
CREATE TABLE 테이블명 ( 컬럼명1 데이터타입 [제약조건], 컬럼명2 데이터타입 [제약조건], ... );
- ALTER:
- 이미 존재하는 객체의 구조를 변경
- 새로운 컬럼 추가(ADD)
- 기존 컬럼의 타입 및 크기 수정(MODIFY)
- 더 이상 필요 없는 컬럼 삭제(DROP COLUMN)
기본 구문 형식
-- 1. 컬럼 추가 ALTER TABLE 테이블명 ADD 컬럼명 데이터타입 [제약조건]; -- 2. 컬럼 수정 ALTER TABLE 테이블명 MODIFY 컬럼명 변경할데이터타입 [제약조건]; -- 3. 컬럼 삭제 ALTER TABLE 테이블명 DROP COLUMN 컬럼명;
- 이미 존재하는 객체의 구조를 변경
- TRUNCATE:
- 테이블의 뼈대(컬럼 구조, 제약조건, 인덱스 등)는 그대로 유지한 채, 내부의 데이터만 전부 최초 상태로 초기화
- 내부적으로 테이블을 파괴하고 똑같이 새로 만드는 원리이므로
- 속도가 매우 빠름
- AUTO_INCREMENT로 쌓여있던 번호 스택도 다시 1번부터 시작하도록 완전히 초기화
기본 구문 형식
TRUNCATE TABLE 테이블명;
- DROP:
- 데이터베이스 객체를 디스크 상에서 통째로 완전히 삭제 (구조와 데이터가 모두 사라짐)
- 테이블에 저장된 모든 데이터 행(Row)과 정의했던 컬럼 구조 및 스키마 정보까지 시스템에서 완전히 소멸 🡲 사용 시 가장 각별한 주의 필요
기본 구문 형식
DROP TABLE 테이블명;
2.3 실습 예제
- [시나리오]
- 온라인 쇼핑몰의 ‘상품(products)’ 정보를 관리할 테이블을 설계하고,
- 비즈니스 요구사항 변화에 맞춰 구조를 바꾼 뒤, 최종적으로 폐기하는 과정
- 테이블 생성하기 (
CREATE)- 새로운 상품 테이블을 만들면서 데이터 타입과 무결성을 위한 제약조건을 정의
CREATE TABLE products ( product_id INT AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(100) NOT NULL, price INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );CREATE TABLE products:products라는 이름의 새로운 테이블을 생성하겠다는 선언
product_id INT AUTO_INCREMENT PRIMARY KEY:INT- 정수형 데이터만 들어올 수 있음
AUTO_INCREMENT- 데이터를 넣을 때마다 숫자가 1, 2, 3… 자동으로 증가하여 채워짐
PRIMARY KEY- 이 테이블의 기본키(고유 식별자)로 지정
- 중복과 빈 값(
NULL)을 절대 허용하지 않음
product_name VARCHAR(100) NOT NULL:- 최대 100글자까지의 가변 길이 문자열(
VARCHAR)을 허용 - 상품 이름은 필수이므로 빈 값을 허용하지 않는다(
NOT NULL)는 제약조건을 부여
- 최대 100글자까지의 가변 길이 문자열(
price INT DEFAULT 0:- 가격 정보를 담는 정수형 컬럼
- 데이터를 입력할 때 가격을 생략하면 자동으로
0이 기입됨
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP:- 상품이 등록된 날짜와 시간을 기록하는 컬럼
- 별도 입력이 없어도 데이터가 생성되는 현재 시간이 자동으로 저장됨
- 테이블 구조 변경하기 (
ALTER)- 서비스를 운영하다가 “상품의 재고 수량(stock) 컬럼을 추가해 주세요”라는 요구사항이 들어와 기존 테이블 구조를 변경하는 상황
-- 1. 새로운 컬럼 추가 (ADD) ALTER TABLE products ADD stock INT NOT NULL DEFAULT 0; -- 2. 기존 컬럼의 데이터 타입 및 제약조건 수정 (MODIFY) ALTER TABLE products MODIFY product_name VARCHAR(200) NOT NULL; -- 3. 특정 컬럼 삭제 (DROP COLUMN) ALTER TABLE products DROP COLUMN created_at;ALTER TABLE products ADD stock...:- 기존 테이블에
stock이라는 이름의 정수형 컬럼을 새롭게 추가 - 기존에 이미 존재하던 데이터가 있다면 그 데이터들의
stock값은 기본값인0으로 채워짐
- 기존 테이블에
MODIFY product_name VARCHAR(200)...:- 상품 이름이 100글자보다 길어질 수 있어, 기존
VARCHAR(100)이었던 크기를VARCHAR(200)으로 확장 수정
- 상품 이름이 100글자보다 길어질 수 있어, 기존
DROP COLUMN created_at:- 테이블에서 해당 컬럼을 완전히 삭제
- 이 명령이 실행되면 기존에 저장되어 있던 날짜 데이터들도 함께 날아가므로 신중해야 함
- 테이블 데이터 초기화하기 (
TRUNCATE)- 테스트 과정에서 수만 건의 더미 데이터가 쌓여, 테이블 틀(컬럼 구조)은 그대로 둔 채 데이터만 깨끗이 비우고 싶을 때 사용
TRUNCATE TABLE products;TRUNCATE TABLE- DML인
DELETE FROM products;와 겉보기에 똑같이 데이터를 지우는 것처럼 보이지만 작동 원리가 완전히 다름 DELETE는 데이터 한 줄 한 줄을 지우기 때문에 느리고 시스템 로그를 많이 남김TRUNCATE는 테이블을 아예 부수고 새로 똑같이 만들어주는 원리이기 때문에 대용량 데이터도 순식간에 삭제됨
- DML인
- 또한
AUTO_INCREMENT로 증가하던 번호 스택도 다시 1번부터 시작하도록 초기화됨
- 테이블 통째로 삭제하기 (
DROP)- 더 이상 쇼핑몰 서비스를 운영하지 않아 상품 테이블 자체를 시스템에서 완전히 없애버리는 단계
DROP TABLE products;DROP TABLE:- 테이블 안에 들어있던 데이터(Row)뿐만 아니라,
CREATE로 만들었던 컬럼 구조, 제약조건, 인덱스 등 모든 흔적을 데이터베이스 디스크에서 영구적으로 삭제- 복구가 불가능한 아주 위험한 명령어 🡲 실무 환경에서는 명령어 복사-붙여넣기 시 수십 번 확인해야 함
- DROP vs TRUNCATE vs DELETE
- 틀까지 부술 거면
DROP - 틀은 놔두고 속만 다 비울 거면
TRUNCATE - 원하는 데이터 알갱이 몇 개만 골라 지울 거면
DELETE
- 틀까지 부술 거면
| 명령어 | 명령어 종류 | 삭제 대상 | 테이블 구조(틀) 유지 여부 | 복구 가능 여부 (Rollback) |
|---|---|---|---|---|
| DROP | DDL | 테이블 구조 + 데이터 전체 삭제 | X (완전 소멸) | 불가능 |
| TRUNCATE | DDL | 데이터 전체만 삭제 (초기화) | O | 불가능 |
| DELETE | DML | 조건에 맞는 데이터 행(Row) 삭제 | O | 가능 (트랜잭션 복구 가능) |
3. 데이터 조작 언어(DML)
3.1 정의 및 개요
- DML(Data Manipulation Language, 데이터 조작 언어)
- 데이터베이스 테이블에 담긴 ‘실제 데이터(값)’를 조회, 삽입, 수정, 삭제할 때 사용하는 명령어
- DDL(CREATE 등)이 데이터를 담는 ‘바구니(테이블 구조) 자체’를 다룬다면 🡲 DML은 그 바구니 안에 담기는 ‘알맹이(데이터)’를 다룸
- DML 사용하기 위해서는 테이블이 정의되어 있어야 함
- DML 구문이 사용되는 대상은 테이블의 행
- SQL문 중 SELECT, INSERT, UPDATE, DELETE가 이 구문에 해당
- DDL과 달리 실행 후 임시 저장 상태가 되며, COMMIT을 해야 완전히 반영되고 ROLLBACK으로 취소가 가능하여 상대적으로 안전함
- 트랜잭션(Transaction)이 발생하는 SQL도 DML에 속함
3.2 DML 핵심 명령어
- INSERT (데이터 삽입)
- 테이블에 새로운 데이터 행(Row) 추가
- 데이터를 삽입할 컬럼들을 명시하고, 그에 대응하는 실제 값(
VALUES)을 순서대로 매칭해 줌 기본 구문 형식
INSERT INTO 테이블명 (컬럼1, 컬럼2, ...) VALUES (값1, 값2, ...);
- SELECT (데이터 조회)
- 테이블에 저장된 데이터를 검색하고 불러올 때 사용
- DML 중 실무에서 가장 많이 사용되며 가장 중요한 파트
- 특정 컬럼만 골라내거나,
WHERE절을 통해 특정 조건에 맞는 데이터만 필터링하여 볼 수 있음 기본 구문 형식
SELECT 컬럼1, 컬럼2, ... FROM 테이블명 [WHERE 조건식];
- UPDATE (데이터 수정)
- 테이블에 이미 존재하는 기존 데이터 변경
SET뒤에 수정할 컬럼과 값을 지정- 만약
WHERE조건절을 생략하면 테이블 내의 모든 행이 동시에 수정되는 대참사가 발생 🡲 극도로 주의 기본 구문 형식
UPDATE 테이블명 SET 컬럼1 = 값1, 컬럼2 = 값2, ... WHERE 조건식;
- DELETE (데이터 삭제)
- 테이블에서 특정 데이터 행(Row)을 삭제
UPDATE와 마찬가지로WHERE조건절을 생략하면 테이블 내부의 모든 데이터 알맹이가 통째로 삭제 🡲 반드시 삭제 대상을 명확히 타겟팅해야 함기본 구문 형식
DELETE FROM 테이블명 WHERE condition;
- ⚠️ 주의
- “실무에서
WHERE절이 없는UPDATE와DELETE문은 회사를 그만두겠다는 조용한 선언과 같음- 데이터를 변경하거나 지울 때는 내가 조작하려는 ‘단 하나의 고유한 타겟(예: Primary Key)’을
WHERE절에 정확히 명시했는지 눈으로 두 번 세 번 확인하는 버릇을 들여야 함
3.3 실습 예제 1
- [시나리오]
- 당신은 오늘 새로 오픈한 전자제품 쇼핑몰의 관리자임
- 작업의 흐름: 신규 상품 등록 🡲 전체 목록 확인 🡲 가격 인상 🡲 수정 확인 🡲 단종 상품 삭제 🡲 최종 목록 확인
- [준비 단계] 실습용 테이블 생성 (DDL)
DDL 구문을 먼저 실행하여
products테이블 생성 및 초기화DROP TABLE IF EXISTS products; CREATE TABLE products ( product_id INT AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(100) NOT NULL, price INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
[1단계] 데이터 삽입 (
INSERT)- 신규 상품 입고 (스토어 오픈) 🡲 INSERT
- 매장에 손님을 맞이하기 전, 판매할 신규 상품 5종을 창고에 넣고 시스템에 등록할 것
- 액션: 무선 마우스, 기계식 키보드 등 5개의 상품 데이터를 테이블에 삽입
- 테이블에 새로운 데이터 행(Row)을 추가
AUTO_INCREMENT가 설정된product_id와DEFAULT값이 있는created_at은 값을 생략해도 시스템이 알아서 채워줌-- 신규 상품 5건 등록 (product_id와 created_at은 자동 입력되므로 생략) INSERT INTO products (product_name, price) VALUES ('무선 마우스', 25000), ('기계식 키보드', 89000), ('27인치 모니터', 240000), ('C타입 케이블', 9900), ('유선 헤드셋', 45000);
- 신규 상품 입고 (스토어 오픈) 🡲 INSERT
[2단계] 첫 번째 전체 조회 (
SELECT)- 전체 상품 목록 확인 (진열 점검) 🡲 SELECT
- 오픈 전, 매장 진열대에 상품 5개가 빠짐없이 예쁘게 잘 올라갔는지, 가격은 데이터베이스에 정상적으로 찍혀 나오는지 전체 목록 확인
- 액션: 5개의 상품이 ID(1~5번)와 함께 표 형태로 잘 출력되는지 눈으로 점검
- 테이블에 저장된 데이터를 검색
*은 테이블 내의 모든 컬럼을 가져오겠다는 의미자동 생성된 ID와 등록 시간까지 함께 확인할 수 있음
-- 등록된 5건의 상품이 잘 들어왔는지 전체 목록 확인 SELECT * FROM products;- 예상 결과
- 자동 생성된
product_id(1~5번)와 현재 시간이 기록된created_at을 포함하여 총 5행의 상품 표가 출력
- 자동 생성된
- 예상 결과
- 전체 상품 목록 확인 (진열 점검) 🡲 SELECT
[3단계] 데이터 수정 (
UPDATE)- 긴급 가격 변동 (물가 상승 반영) 🡲 UPDATE
- 스토어를 운영하던 중, 공급처로부터 “원자재 가격 상승으로 인해 ‘기계식 키보드’의 도매가가 올랐습니다”라는 연락을 받음
- 마진을 유지하기 위해 시스템에 등록된 기계식 키보드(2번)의 판매가를 기존 89,000원에서 95,000원으로 인상함
- 액션: 전체 상품이 아닌, 오직 2번 상품의 가격 컬럼만 타겟팅하여 값을 수정
- 테이블에 존재하는 기존 데이터를 변경
조건절(
WHERE)을 적지 않으면 모든 상품의 가격이 동시에 바뀌므로, 수정하고자 하는 고유한product_id를 정확히 지정해야 함-- 물가 상승으로 인해 '기계식 키보드(product_id = 2)'의 가격을 95,000원으로 인상 수정 UPDATE products SET price = 95000 WHERE product_id = 2;
- 긴급 가격 변동 (물가 상승 반영) 🡲 UPDATE
[4단계] 수정 결과 확인 조회 (
SELECT)- 가격 수정 결과 확인 (영수증 검수) 🡲 SELECT
- 방금 고친 키보드 가격이 혹시 다른 상품에 영향을 주지는 않았는지, 95,000원으로 정확하게 잘 반영되었는지 확인
- 2번 상품만 쏙 골라서 다시 조회
- 액션: 2번 기계식 키보드의 가격이 정상적으로 변경되었음을 확인
특정 조건(
WHERE)을 활용해 변경된 데이터가 원하는 대로 올바르게 수정되었는지 타겟 조회를 실행-- 가격을 인상한 2번 상품만 쏙 골라서 잘 바뀌었는지 확인 SELECT * FROM products WHERE product_id = 2;- 예상 결과
- 2번 기계식 키보드의 가격이
89000에서95000으로 업데이트된 것을 확인
- 2번 기계식 키보드의 가격이
- 예상 결과
- 가격 수정 결과 확인 (영수증 검수) 🡲 SELECT
[5단계] 데이터 삭제 (
DELETE)- 단종 상품 발생 (제품 라인업 정리) 🡲 DELETE
- 공급처에서 ‘C타입 케이블(4번)’의 생산을 전면 중단(단종)한다는 통보가 옴
- 더 이상 고객들이 이 상품을 주문하면 안 되므로, 쇼핑몰 판매 목록에서 4번 상품을 완전히 삭제하여 내림
- 액션: WHERE product_id = 4 조건을 걸어 해당 행을 데이터베이스에서 지움
- 테이블에서 특정 데이터 행(Row)을 삭제
WHERE조건절을 생략하면 테이블 내의 상품 알맹이가 전부 증발하므로 극도로 주의해서 사용해야 함-- 단종 처리된 'C타입 케이블(product_id = 4)'을 상품 목록에서 삭제 DELETE FROM products WHERE product_id = 4;
- 단종 상품 발생 (제품 라인업 정리) 🡲 DELETE
[6단계] 최종 결과 확인 조회 (
SELECT)- 최종 마감 점검 (현재 판매 중인 목록) 🡲 SELECT
- 오늘의 모든 상품 관리가 끝났음
- 퇴근하기 전, 현재 우리 스토어에서 실제로 정상 판매 중인 최종 상품 라인업이 어떻게 구성되어 있는지 전체 화면을 띄워 마감 점검 수행
- 액션: 단종된 4번은 사라지고, 가격이 오른 2번을 포함해 총 4개의 상품만 깔끔하게 남아있는 최종 결과를 확인하기
삭제 작업이 다른 데이터에 영향을 주지 않고 깔끔하게 처리되었는지 최종 점검하기 위한 전체 조회
-- 4번 상품이 제외된 최종 판매 상품 목록 조회 SELECT * FROM products;- 예상 결과
- 4번 C타입 케이블의 행은 사라지고, 1번, 2번(가격 수정됨), 3번, 5번 상품까지 총 4건의 데이터만 남은 것을 확인
- 예상 결과
- 최종 마감 점검 (현재 판매 중인 목록) 🡲 SELECT
3.4 확장 기능
- 테이블에서 단순하게 데이터를 꺼내오는 수준을 넘어, 데이터를 분석하고 가치 있는 정보로 가공하기 위한 DML의 핵심 기술
- 독립된 명령어가 아니라
SELECT문을 보조하는 절(Clause)이거나 연산자
- 독립된 명령어가 아니라
- 대표적인 확장 기능은
그룹화(GROUP BY), 정렬(ORDER BY), 병합(JOIN) - 실무에서 작성되는 복잡한 SQL 쿼리의 대부분은 이 세 가지 확장 구문을 조합하여 만들어짐
- 특히 데이터 분석과 백엔드 개발에서 전체 SQL의 80% 이상을 차지할 정도로 활용도 및 비중이 높음
| 구문명 | 표준 키워드 | 이유 |
|---|---|---|
| 병합 | JOIN | 여러 테이블의 데이터를 엮어서 조회(SELECT)하기 위함 |
| 그룹화 | GROUP BY | 데이터를 특정 기준으로 묶어서 통계값을 조회하기 위함 |
| 그룹 조건 | HAVING | 그룹화된 결과 중 조건에 맞는 것만 필터링하여 조회하기 위함 |
| 정렬 | ORDER BY | 조회된 결과를 보기 좋게 정돈하여 출력(조회)하기 위함 |
3.5 실습 예제 2
- [준비 단계] 실습용 테이블 생성
JOIN실습을 하려면 상품(products) 외에 주문(orders) 테이블이 추가로 필요함아래 DDL을 실행하여 관계형 구조를 만들어 줌
-- [DDL] 주문 테이블 생성 (DML 실습을 위한 준비) CREATE TABLE orders ( order_id INT AUTO_INCREMENT PRIMARY KEY, product_id INT, quantity INT NOT NULL, -- 주문 수량 order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(product_id) ); -- [DML] 분석을 위한 매출(주문) 데이터 삽입 INSERT INTO orders (product_id, quantity) VALUES (1, 2), (2, 1), (1, 5), (3, 1), (5, 3), (2, 2), (1, 1);
- JOIN (테이블 병합)
- 외래키(FK)를 기반으로 서로 분리된 두 개 이상의 테이블을 옆으로 연결하여 하나의 커다란 표로 묶어내는 기술
실무에서는 공통된 데이터만 결합하는
INNER JOIN이 가장 많이 쓰임기본 구문
SELECT 테이블A.컬럼, 테이블B.컬럼 FROM 테이블A INNER JOIN 테이블B ON 테이블A.공통컬럼 = 테이블B.공통컬럼;- 예제 시나리오
- 쇼핑몰 메인 화면에 주문이 들어올 때마다 “어떤 고객이 어떤 상품을 몇 개 샀는지” 보여주어야 함
orders테이블에는 상품 이름 없이 번호(product_id)만 적혀있음- 액션: 두 테이블을 엮어서 [상품 이름]과 [주문 수량]을 동시에 조회할 것
SELECT p.product_name, o.quantity, o.order_date FROM orders o INNER JOIN products p ON o.product_id = p.product_id;
- GROUP BY (그룹화) & 집계 함수
- 많은 데이터 행들을 특정 컬럼 기준으로 묶어서 통계(합계, 평균, 개수 등)를 낼 때 사용
이때 개수를 세는
COUNT(), 합계를 구하는SUM()등의 집계 함수가 반드시 세트로 따라옴기본 구문
SELECT 그룹기준컬럼, 집계함수(컬럼) FROM 테이블명 GROUP BY 그룹기준컬럼;- 예제 시나리오
- 오늘 마감 정산을 하려고 함
- 각 상품별로 총 몇 번의 주문 건수가 발생했고, 총 몇 개(
SUM)가 판매되었는지 상품별 누적 판매량 통계를 내고자 함
SELECT product_id, COUNT(*) AS "주문 건수", SUM(quantity) AS "총 판매수량" FROM orders GROUP BY product_id;
- HAVING (그룹화 결과 필터링)
- 많이 헷갈려하는 구문
WHERE절은 그룹화하기 전 ‘개별 데이터’에 조건을 거는 반면,HAVING절은GROUP BY가 끝난 후 ‘뭉쳐진 통계 결과물’에 조건을 걸어 필터링할 때 사용기본 구문
SELECT 컬럼, 집계함수 FROM 테이블명 GROUP BY 컬럼 HAVING 집계함수조건;- 예제 시나리오
- 마케팅 팀에서 “인기 상품들만 모아서 이벤트를 하자”고 함
- 방금 구한 상품별 누적 판매량 결과 중, 총 판매수량이 3개 이상인 알짜배기 히트 상품만 골라서 조회함
SELECT product_id, SUM(quantity) AS total_sales FROM orders GROUP BY product_id HAVING SUM(quantity) >= 3;
- ORDER BY (결과 정렬)
- 화면에 쿼리 결과를 뿌려줄 때 특정 컬럼을 기준으로 데이터를 순서대로 정렬
기본값은 오름차순(
ASC, 작은 것부터)이지만, 역순인 내림차순(DESC, 큰 것부터)을 실무에서 훨씬 많이 사용함기본 구문
SELECT 컬럼 FROM 테이블명 ORDER BY 정렬기준컬럼 [ASC | DESC];- 예제 시나리오
- 쇼핑몰 웹사이트의 상품 목록 페이지
- 소비자들이 물건을 고르기 편하게 가장 비싼 상품부터 순서대로(가격 내림차순) 위에서부터 정렬하여 진열할 것
SELECT product_name, price FROM products ORDER BY price DESC;
3.6 SQL 최종 실행 순서
이 모든 확장 구문이 합쳐졌을 때, 작성하는 순서와 데이터베이스 엔진이 내부적으로 해석하는 순서가 완전히 다름
- 작성하는 순서 (문법 순서)
SELECT🡲FROM(JOIN) 🡲WHERE🡲GROUP BY🡲HAVING🡲ORDER BY
- 실제 실행되는 순서 (컴퓨터가 해석하는 순서)
FROM(+JOIN): 먼저 어느 바구니(테이블)들을 합쳐서 열어볼지 대상을 결정WHERE: 원본 데이터 중 쓸모없는 알갱이들을 먼저 걸러냄GROUP BY: 남은 알갱이들을 같은 종류끼리 묶기HAVING: 묶여서 나온 통계치 중 조건에 맞는 그룹만 남김SELECT: 최종적으로 화면에 보여줄 컬럼만 예쁘게 깎아냄 (이때 별칭이 붙음)ORDER BY: 다 만들어진 표를 순서대로 줄 세워 사용자에게 전달함
- 그래서
WHERE절 안에서는SUM()같은 집계 함수를 쓸 수 없음 - 집계 함수는 3번(
GROUP BY) 단계가 지나야 생성되는데, WHERE는 2번 단계라 아직 집계 함수가 세상에 태어나기 전이기 때문- 대신 4번 단계인
HAVING절에서는 마음껏 쓸 수 있음
- 기타 활용 팁
- NULL 처리:
NULL은 0이나 공백이 아님WHERE 컬럼 = NULL이 아니라WHERE 컬럼 IS NULL로 조회해야 함- 세미콜론(;):
- 쿼리의 끝에는 반드시 세미콜론을 붙여 문장이 끝났음을 알려줄 것
4. 추가 실습을 위한 샘플 DB 생성 Query
4.1 테이블 생성 쿼리 (DDL)
-- 1. 기존 테이블이 있다면 실습을 위해 삭제
DROP TABLE IF EXISTS courses;
DROP TABLE IF EXISTS students;
-- 2. 학생(students) 테이블 생성
CREATE TABLE students (
student_id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT NOT NULL,
email VARCHAR(100) UNIQUE,
joined_date DATE DEFAULT (CURRENT_DATE)
);
-- 3. 수강신청(courses) 테이블 생성
CREATE TABLE courses (
course_id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT,
course_name VARCHAR(100) NOT NULL,
FOREIGN KEY (student_id) REFERENCES students(student_id) ON DELETE CASCADE
);
4.2 실습 데이터 삽입 쿼리 (DML - 총 103건)
- 학생 테이블 데이터 (103명)
INSERT INTO students (student_id, name, age, email, joined_date) VALUES
(1, '김철수', 20, 'chulsoo.kim@email.com', '2026-03-02'),
(2, '이영희', 21, 'younghee.lee@email.com', '2026-03-02'),
(3, '박민수', 22, 'minsu.park@email.com', '2026-03-03'),
(4, '최민지', 20, 'minji.choi@email.com', '2026-03-03'),
(5, '정지훈', 23, 'jihoon.jung@email.com', '2026-03-04'),
(6, '한지원', 21, 'jiwon.han@email.com', '2026-03-04'),
(7, '김동현', 22, 'donghyun.kim@email.com', '2026-03-05'),
(8, '윤서연', 24, 'seoyeon.yoon@email.com', '2026-03-05'),
(9, '장민재', 20, 'minjae.jang@email.com', '2026-03-06'),
(10, '임지아', 25, 'jia.lim@email.com', '2026-03-06'),
(11, '김준호', 21, 'junho.kim@email.com', '2026-03-09'),
(12, '오세은', 22, 'seeun.oh@email.com', '2026-03-09'),
(13, '서준우', 20, 'junwoo.seo@email.com', '2026-03-10'),
(14, '신소윤', 23, 'soyoon.shin@email.com', '2026-03-10'),
(15, '김현우', 21, 'hyunwoo.kim@email.com', '2026-03-11'),
(16, '노윤아', 22, 'youna.noh@email.com', '2026-03-11'),
(17, '배진성', 20, 'jinsung.bae@email.com', '2026-03-12'),
(18, '유하은', 24, 'haeun.yoo@email.com', '2026-03-12'),
(19, '김도윤', 22, 'doyuan.kim@email.com', '2026-03-13'),
(20, '송민경', 25, 'minkyung.song@email.com', '2026-03-13'),
(21, '권태현', 21, 'taehyun.kwon@email.com', '2026-03-16'),
(22, '황주원', 20, 'juwon.hwang@email.com', '2026-03-16'),
(23, '김재원', 23, 'jaewon.kim@email.com', '2026-03-17'),
(24, '전다은', 22, 'daeun.jeon@email.com', '2026-03-17'),
(25, '고동우', 21, 'dongwoo.go@email.com', '2026-03-18'),
(26, '문서현', 20, 'seohyun.mun@email.com', '2026-03-18'),
(27, '양준영', 24, 'junyoung.yang@email.com', '2026-03-19'),
(28, '손지민', 22, 'jimin.son@email.com', '2026-03-19'),
(29, '김태민', 21, 'taemin.kim@email.com', '2026-03-20'),
(30, '백수아', 23, 'sua.baek@email.com', '2026-03-20'),
(31, '조현수', 20, 'hyunsu.cho@email.com', '2026-03-23'),
(32, '유진아', 22, 'jina.yoo@email.com', '2026-03-23'),
(33, '심재혁', 21, 'jaehyuk.sim@email.com', '2026-03-24'),
(34, '남지우', 24, 'jiwoo.nam@email.com', '2026-03-24'),
(35, '김성민', 20, 'sungmin.kim@email.com', '2026-03-25'),
(36, '하은지', 25, 'eunji.ha@email.com', '2026-03-25'),
(37, '곽민기', 22, 'mingi.kwak@email.com', '2026-03-26'),
(38, '성유진', 21, 'yujin.sung@email.com', '2026-03-26'),
(39, '김하윤', 23, 'hayoon.kim@email.com', '2026-03-27'),
(40, '차민서', 20, 'minseo.cha@email.com', '2026-03-27'),
(41, '민준서', 21, 'junseo.min@email.com', '2026-03-30'),
(42, '우지현', 22, 'jihyun.woo@email.com', '2026-03-30'),
(43, '진성우', 24, 'sungwoo.jin@email.com', '2026-03-31'),
(44, '지수민', 20, 'sumin.ji@email.com', '2026-03-31'),
(45, '김동우', 22, 'dongwoo2.kim@email.com', '2026-04-01'),
(46, '엄지훈', 23, 'jihoon.eom@email.com', '2026-04-01'),
(47, '원채원', 21, 'chaewon.won@email.com', '2026-04-02'),
(48, '채승우', 25, 'seungwoo.chae@email.com', '2026-04-02'),
(49, '김민재', 20, 'minjae2.kim@email.com', '2026-04-03'),
(50, '천세아', 22, 'seea.cheon@email.com', '2026-04-03'),
(51, '구본혁', 21, 'bonhyuk.koo@email.com', '2026-04-06'),
(52, '홍은서', 24, 'eunser.hong@email.com', '2026-04-06'),
(53, '라건우', 20, 'geonwoo.ra@email.com', '2026-04-07'),
(54, '염지민', 23, 'jimin.yeom@email.com', '2026-04-07'),
(55, '김지후', 22, 'jihoo.kim@email.com', '2026-04-08'),
(56, '지승현', 21, 'seunghyun.ji@email.com', '2026-04-08'),
(57, '변하은', 25, 'haeun.byeon@email.com', '2026-04-09'),
(58, '도민우', 20, 'minwoo.do@email.com', '2026-04-09'),
(59, '김수현', 22, 'suhyun.kim@email.com', '2026-04-10'),
(60, '석지우', 21, 'jiwoo.suk@email.com', '2026-04-10'),
(61, '안성준', 23, 'sungjun.ahn@email.com', '2026-04-13'),
(62, '여민주', 24, 'minju.yeo@email.com', '2026-04-13'),
(63, '김태윤', 20, 'taeyoon.kim@email.com', '2026-04-14'),
(64, '설아인', 22, 'ain.seol@email.com', '2026-04-14'),
(65, '길도현', 21, 'dohyun.gil@email.com', '2026-04-15'),
(66, '탁유나', 23, 'yuna.tak@email.com', '2026-04-15'),
(67, '김준서', 25, 'junseo2.kim@email.com', '2026-04-16'),
(68, '주하윤', 20, 'hayoon.ju@email.com', '2026-04-16'),
(69, '명재민', 22, 'jaemin.myung@email.com', '2026-04-17'),
(70, '은시우', 21, 'siwoo.eun@email.com', '2026-04-17'),
(71, '김도현', 24, 'dohyun2.kim@email.com', '2026-04-20'),
(72, '국지수', 20, 'jisu.kook@email.com', '2026-04-20'),
(73, '선우빈', 23, 'bin.sunwoo@email.com', '2026-04-21'),
(74, '제갈민', 22, 'min.jaegal@email.com', '2026-04-21'),
(75, '김승우', 21, 'seungwoo2.kim@email.com', '2026-04-22'),
(76, '독고연', 25, 'yeon.dokgo@email.com', '2026-04-22'),
(77, '사공훈', 20, 'hoon.sagong@email.com', '2026-04-23'),
(78, '남궁현', 22, 'hyun.namkoong@email.com', '2026-04-23'),
(79, '김민서', 21, 'minseo2.kim@email.com', '2026-04-24'),
(80, '황보경', 23, 'kyung.hwangbo@email.com', '2026-04-24'),
(81, '현승민', 24, 'sungmin.hyun@email.com', '2026-04-27'),
(82, '공은지', 20, 'eunji.gong@email.com', '2026-04-27'),
(83, '김준우', 22, 'junwoo2.kim@email.com', '2026-04-28'),
(84, '부민주', 21, 'minju.boo@email.com', '2026-04-28'),
(85, '어준혁', 23, 'junhyuk.eo@email.com', '2026-04-29'),
(86, '풍지수', 25, 'jisu.pung@email.com', '2026-04-29'),
(87, '김진우', 20, 'jinwoo.kim@email.com', '2026-04-30'),
(88, '서민아', 22, 'mina.seo@email.com', '2026-04-30'),
(89, '강현준', 21, 'hyunjun.kang@email.com', '2026-05-01'),
(90, '조예은', 24, 'yeeun.cho@email.com', '2026-05-01'),
(91, '김우진', 23, 'woojin.kim@email.com', '2026-05-04'),
(92, '윤지아', 20, 'jia.yoon@email.com', '2026-05-04'),
(93, '장동현', 22, 'donghyun.jang@email.com', '2026-05-05'),
(94, '임서연', 21, 'seoyeon.lim@email.com', '2026-05-05'),
(95, '김은우', 25, 'eunwoo.kim@email.com', '2026-05-06'),
(96, '한지민', 20, 'jimin.han@email.com', '2026-05-06'),
(97, '신재원', 22, 'jaewon.shin@email.com', '2026-05-07'),
(98, '오하은', 21, 'haeun.oh@email.com', '2026-05-07'),
(99, '김현서', 24, 'hyunser.kim@email.com', '2026-05-08'),
(100, '송지우', 23, 'jiwoo.song@email.com', '2026-05-08'),
(101, '배준영', 22, 'junyoung.bae@email.com', '2026-05-11'),
(102, '유서현', 20, 'seohyun.yoo@email.com', '2026-05-11'),
(103, '전민수', 21, 'minsu.jeon@email.com', '2026-05-12');
- 수강신청 테이블 데이터 (75건)
INSERT INTO courses (student_id, course_name) VALUES
(1, '데이터베이스 개론'), (1, '파이썬 프로그래밍'),
(2, '데이터베이스 개론'), (3, '알고리즘 분석'),
(4, '파이썬 프로그래밍'), (5, '데이터베이스 개론'),
(6, '운영체제'), (7, '알고리즘 분석'),
(8, '데이터베이스 개론'), (9, '파이썬 프로그래밍'),
(10, '컴퓨터 네트워크'), (11, '데이터베이스 개론'),
(12, '알고리즘 분석'), (13, '파이썬 프로그래밍'),
(14, '운영체제'), (15, '데이터베이스 개론'),
(16, '컴퓨터 네트워크'), (17, '알고리즘 분석'),
(18, '파이썬 프로그래밍'), (19, '데이터베이스 개론'),
(20, '운영체제'), (21, '알고리즘 분석'),
(22, '데이터베이스 개론'), (23, '파이썬 프로그래밍'),
(24, '컴퓨터 네트워크'), (25, '알고리즘 분석'),
(26, '데이터베이스 개론'), (27, '운영체제'),
(28, '파이썬 프로그래밍'), (29, '데이터베이스 개론'),
(30, '컴퓨터 네트워크'), (31, '알고리즘 분석'),
(32, '데이터베이스 개론'), (33, '파이썬 프로그래밍'),
(34, '운영체제'), (35, '데이터베이스 개론'),
(36, '컴퓨터 네트워크'), (37, '알고리즘 분석'),
(38, '파이썬 프로그래밍'), (39, '데이터베이스 개론'),
(40, '운영체제'), (41, '알고리즘 분석'),
(42, '데이터베이스 개론'), (43, '파이썬 프로그래밍'),
(44, '컴퓨터 네트워크'), (45, '알고리즘 분석'),
(46, '데이터베이스 개론'), (47, '운영체제'),
(48, '파이썬 프로그래밍'), (49, '데이터베이스 개론'),
(50, '컴퓨터 네트워크'), (51, '알고리즘 분석'),
(52, '데이터베이스 개론'), (53, '파이썬 프로그래밍'),
(54, '운영체제'), (55, '데이터베이스 개론'),
(56, '컴퓨터 네트워크'), (57, '알고리즘 분석'),
(58, '파이썬 프로그래밍'), (59, '데이터베이스 개론'),
(60, '운영체제'), (61, '알고리즘 분석'),
(62, '데이터베이스 개론'), (63, '파이썬 프로그래밍'),
(64, '컴퓨터 네트워크'), (65, '알고리즘 분석'),
(66, '데이터베이스 개론'), (67, '운영체제'),
(68, '파이썬 프로그래밍'), (69, '데이터베이스 개론'),
(70, '컴퓨터 네트워크'), (71, '알고리즘 분석'),
(72, '데이터베이스 개론'), (73, '파이썬 프로그래밍'),
(74, '운영체제'), (75, '데이터베이스 개론');
4.3 예제 정상 동작 여부 검증 (조회 테스트)
패턴 매칭 검증 (LIKE ‘김%’)
SELECT name FROM students WHERE name LIKE '김%'; -- 결과: 김철수, 김동현, 김준호 등 총 24명의 김씨 성을 가진 학생 출력그룹화 조건 검증 (GROUP BY & HAVING)
SELECT age, COUNT(*) as student_count FROM students GROUP BY age HAVING COUNT(*) >= 5; -- 결과: 20세부터 25세까지 모든 연령대의 학생 수가 5명 이상이므로 전체 연령별 집계 통계 정상 노출조인 기능 검증 (INNER JOIN)
SELECT s.name, c.course_name FROM students s INNER JOIN courses c ON s.student_id = c.student_id; -- 결과: 두 테이블에 모두 정보가 존재하는 총 75건의 학생 이름과 매핑된 과목 정보 리스트 출력