본문 바로가기
Back-end/DB | MySQL

[SQL 문법 정리] 입문편

by 디지털 전산일지 2025. 11. 17.

Index

1. 데이터베이스 CRUD / 현재 있는 데이터베이스 확인

2. 데이터 타입

3. 제약 조건

4. 테이블 구조 확인

5. PK와 FK의 관계

6. DDL (CREATE, ALTER, DROP, TRUNCATE)

7. DML (INSERT, SELECT, UPDATE, DELETE)

8. SELECT, WHERE절

9. ORDER BY, LIMIT절

10. NULL

11. 문자열 함수

12. 집계 함수와 GROUP BY절

13. 별칭 지정 (alias)

14. SQL 연산 순서

 

1. 데이터베이스 CRUD / 현재 있는 데이터베이스 확인

-- 데이터베이스 생성 (Create)
CREATE DATABASE my_shop;

 

-- 현재 있는 데이터베이스 확인 (Read)
SHOW DATABASES;

-- 현재 있는 테이블 확인 (Read)
SHOW TABLES;

 

-- 데이터베이스 삭제 (Delete)
DROP DATABASE my_shop;

 

2. 데이터 타입

데이터 타입 설명    
INT 소숫점이 아닌 정수    
VARCHAR 가변 문자열(Variable Character)을 의미
VARCHAR(5)는 최대 5글자 저장 가능

Ex) VARCHAR(5) 에서
'안녕' 을 넣었을 때 2글자 만큼의 용량 할당
CHAR 고정 문자열을 의미
CHAR(5)는 항상 5글자 저장

Ex) CHAR(5) 에서
'안녕'을 넣었다면 5글자 만큼 용량을 할당하고 빈 자리는 공백으로 채워넣음

6글자를 사용해야 하는 상황이 오면 문제가 발생하므로 잘 사용하지 않음
DATE
DATETIME
YYYY-MM-DD 형태로 저장
YYYY-MM-DD HH:MM:SS 형태로 저장
TIMESTAMP YYYY-MM-DD HH:MM:SS 형태로 저장

※ 단, 표현할 수 있는 기간이 1970~2038년 까지 이므로 현대에는 사용하지 않음

 

3. 제약 조건

제약 조건 설명
NOT NULL NULL이 없어야 함.
※ NULL은 값이 없음을 의미
UNIQUE 중복 불가
※ 단, NULL은 중복으로 잡지 않음
PRIMARY KEY(PK) NOT NULL + UNIQUE
AUTO_INCREMENT 새로운 데이터가 추가될 때마다 n씩 더해준다.
보통 거래정보id 와 같은 컬럼에서 많이 사용됨 
FOREIGN KEY(FK) 테이블 간의 관계를 설정
참조 무결성을 보장 : 참조하는 것이 무조건 있다는 것을 보장한다. == FK는 반드시 참조하는 데이터가 있어야 한다. == 참조하는 열의 값은 반드시 참조되는 테이블의 PK 값 중 하나여야 한다.

- [INSERT]
orders 테이블의 customer_id (FK) 값이 99가 추가되는 INSERT 문을 작성했다고 가정하면, customers 테이블의 customer_id (PK) 값에는 99가 없으므로 이런 경우 데이터의 입력을 막는다.

단, FK가 NULL 허용인 경우 부모를 참조하지 않아도 NULL로 생성하는 것은 예외적으로 가능



- [DELETE]
삭제할 때 customer_id(FK)를 먼저 삭제하고, 연결되는 것이 없으면 customer_id(PK)를 삭제할 수 있다.

customer_id(PK) 값이 2를 삭제(DELETE) 한다고 가정하면, 그와 연결된 customer_id(FK) 2는 참조하는 것이 없으므로 참조 무결성 제약 조건에 위배된다.
따라서 삭제할 때는 customer_id(FK) 값이 2인 것을 모두 삭제하고, 그와 연결된 customer_id(PK)을 삭제하면 된다.

       삭제 할 때 참조되고 있는 데이터도 같이 삭제하려면           CASCADE 키워드를 사용한다.
        삭제 할 때 참조되고 있는 데이터는 NULL으로 설정하려면 SET NULL 키워드를 사용한다.


CHECK 컬럼에 입력되는 값이 특정 조건을 만족하는지 검사

Ex) age 컬럼에 10보다 작은 숫자가 입력되지 않도록 막는다.
DEFAULT 값 미지정 시 기본값을 설정

※ 날짜의 기본값 설정 옵션
CREATE TABLE test (
     created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

DATETIME DEFAULT CURRENT_TIMESTAMP : 새로운 데이터 행(row)가 추가될 때, 해당 컬럼에 별도의 값을 지정하지 않으면 현재의 날짜와 시간이 자동으로 입력된다.

DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP : 새로운 데이터 행(row)가 추가될 때 또는 같은 행의 컬럼 값이 변경되어 업데이트 될 때, 이 컬럼의 값은 현재 날짜와 시간으로 자동 갱신된다.

 

4. 테이블 구조 확인

// 테이블 구조 확인
DESC sample_tbl;
DESCRIBE sample_tbl;

 

5. PK와 FK 관계

  • PK -> FK 관계
  • 즉, PK는 참조를 하는 주체이고 FK는 참조를 당하는 주체
  • 참조 무결성을 유지해야 하므로, PK-FK가 연결된 상태에서 PK쪽의 테이블을 멋대로 지우면(DROP, TRUNCATE) 오류가 발생한다.
    • PK쪽의 테이블을 지워버리면 오류가 발생하는 이유 : FK는 참조하는 것이 무조건 있다는 것을 보장해야 하는데, 참조하고 있는 데이터인 student_id(PK)를 지워버리면 참조 무결성이 지켜지지 않기 때문이다.

 

CREATE TABLE orders(
	order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    ...
    
    -- orders.customer_id 가 customers.customer_id 를 참조하도록 한다.
    CONSTRAINT fk_orders_customers FOREIGN KEY (customer_id) 
		REFERENCES customers(customer_id)
);

 

6. DDL (CREATE, ALTER, DROP, TRUNCATE)

  • 데이터 구조를 정의 (definition)
  • 데이터베이스, 테이블 대상 : CREATE(C), ALTER(U), DROP(D), TRUNCATE(D), RENAME
  • 대용량 테이블은 변경 시 잠금, 속도를 고려하여 새벽에 작업
  • PK - FK 관계가 걸려 있으면 DROP, TRUNCATE 명령은 차단된다.

※ SHOW은 DDL이 아니다.

-- 데이터베이스 생성 (Create)
CREATE DATABASE my_shop_tbl;

-- 테이블 생성 (Create)
CREATE TABLE sample_tbl (
	product_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    price INT,
    stock_quantity INT,
    release_date DATE,
    join_date DATETIME DEFAULT CURRENT_TIMESTAMP
);

 

-- 테이블에서 칼럼 추가 ALTER TABLE (Update)

-- customers_tbl 테이블에서 point 칼럼 추가
ALTER TABLE customers_tbl
ADD COLUMN point INT NOT NULL DEFAULT 0;

-- customers_tbl 테이블에서 address 칼럼을 수정
ALTER TABLE customers_tbl
MODIFY COLUMN address VARCHAR(500) NOT NULL;

-- customers_tbl 테이블에서 point 칼럼을 제거
ALTER TABLE customers_tbl
DROP COLUMN point;

 

-- 테이블 삭제 (Delete)
DROP TABLE sample_tbl;

-- 테이블 구조는 남기고 내부 데이터만 삭제 (Delete)
-- ※ TRUNCATE는     AUTO_INCREMENT 값도 초기화됨
-- ※ 반면, DELETE는 AUTO_INCREMENT 값이 초기화되지 않음
TRUNCATE TABLE sample_tbl;

 

 

7. DML (INSERT, SELECT, UPDATE, DELETE)

  • 데이터를 조작 (Manufactur)
  • 테이블 내 데이터를 대상 : INSERT(C), SELECT(R), UPDATE(U), DELETE(D)
-- Create
	-- 1개인 경우
INSERT INTO sample (product_id, name, price, stock_quantity, release_date)
VALUES (1, "청바지", 59900, 100, '2025-06-11');

	-- 여러개인 경우
INSERT INTO sample (product_id, name, price, stock_quantity, release_date)
VALUES 
	(1, "청바지", 59900, 100, '2025-06-11'),
    (2, "떡볶이", 3000, 110, '2025-06-12'),
    (3, "연필", 500, 100, '2025-06-11');

 

-- Read
SELECT * FROM sample;

 

※ 실무에서는 UPDATE 작업 시 WHERE 절에 들어갈 대상을 미리 확인하고 UPDATE 하는 습관을 들이자.

-- Update
UPDATE sample
SET price = 40000, stock_quantity = 580
WHERE product_id = 1;

-- 안전 모드

    -- ※ 실수로 WHERE 절을 쓰지 않으면 모든 데이터가 바뀌기 때문에 위험하다.
    -- 그래서 MySQL Workbench에서는 자동적으로 안전 모드가 설정되어 WHERE 절을 쓰지 않은 경우나
    -- WHERE을 썼더라도 PK가 아닌 데이터로 조건을 주면 오류를 발생시킨다.
    -- (왜냐하면 의도하지 않게 여러 개가 지워질 수 있기 때문이다.)

    -- 그러나, Connection을 통해 접근하는 애플리케이션에서는 안전 모드가 꺼져 있으므로 주의가 필요하다.

 

※ 실무에서는 DELETE 작업 시 WHERE 절에 들어갈 대상을 미리 확인하고 DELETE 하는 습관을 들이자.

-- Delete
DELETE FROM sample
WHERE product_id = 1;

-- 안전 모드

	-- UPDATE문과 마찬가지로 안전 모드를 제공한다.
    -- WHERE 절을 쓰지 않은 경우에만 안전 모드 덕분에 오류가 발생한다.

 

TRUNCATE는 DROP 하고 다시 테이블을 만든 느낌으로 이해하면 된다.

 

8. SELECT, WHERE

SELECT

  • SELECT 구문에서 필요한 칼럼만 선택해서 추출하는 것이 성능상 더 좋다.
-- 전체 칼럼을 조회하면 조회 성능이 비교적 저하됨.
SELECT * FROM THDAY_TBL


-- 조회할 칼럼만 선택하여 조회하면 전체 칼럼 조회(*) 보다 성능이 향상
SELECT 
	INTEREST,
    	ACNT_NO
FROM
	THDAY_TBL

 

  ➀ DISTINCT

  • 값의 중복을 제거한다.
  • DISTINCT ACNT_NO, INTEREST  이라면 DISTINCT (ACNT_NO, INTEREST) 처럼 동작한다. 즉, ACNT_NO 값도 같고, INTEREST 값도 같은 것을 중복으로 보고 제거한다. (하나만 같은 경우 중복으로 보지 않는다.)
  • DISTINCT 연산 시 내부에서 정렬 등 여러가지 작업이 일어나기 때문에 데이터가 많은 경우 단순 SELECT 보다는 성능 저하가 일어날 수 있다. (즉, 데이터가 많은 경우 DISTINCT 연산 사용 시 성능 확인이 필요하다.)
SELECT 
	DISTINCT INTEREST,
    	ACNT_NO
FROM
	THDAY_TBL

 

예시 ) DISTINCT INTERST, ACNT_NO

INTEREST ACNT_NO
1.0 999929123456
2.0 999929123456
2.0 999929123456
2.0 999929111111

INTEREST, ACNT_NO가 동일한 칼럼만 중복 제거됨.

INTEREST ACNT_NO
1.0 999929123456
2.0 999929123456
2.0 999929111111

 

WHERE

  • =, !=, <, <=, >, >= 와 같은 연산자가 있으나, BETWEEN, IN, LIKE, IS NULL 과 같은 연산자로 가독성이 좋은 쿼리 작성이 가능하다.

  ➀ BETWEEN (NOT BETWEEN)

      해당 범위도 포함하여 조회한다. (아래 코드에서 이자가 1(1도 포함) ~ 2(2도 포함)인 행을 조회)

SELECT *
FROM THDAY_TBL
WHERE INTEREST BETWEEN 1 AND 2 -- 이자가 1~2인 행만 조회


SELECT *
FROM THDAY_TBL
WHERE INTEREST NOT BETWEEN 1 AND 2 -- 이자가 1~2가 아닌 행만 조회

 

  ➁ IN (NOT IN)

      특정 데이터만 골라서 조회한다.

SELECT *
FROM THDAY_TBL
WHERE INTEREST IN (1, 3, 5) -- 이자가 1,3,5인 행만 조회


SELECT *
FROM THDAY_TBL
WHERE INTEREST NOT IN (1, 3, 5) -- 이자가 1,3,5가 아닌 행만 조회

 

  LIKE (NOT LIKE)

      특정 패턴과 일치하는 것을 조회한다.

      % = 들어오는 글자 수 제한 없음 (0개 이상의 임의의 글자가 들어올 수 있으므로 뒤에 글자가 없는 경우도 가능)

       _ = 들어오는 글자 수는 _ 하나 당 반드시 1개

/* LIKE */
SELECT *
FROM THDAY_TBL
WHERE ACNT_NO LIKE '999929%' -- 계좌번호가 999929 로 시작하는 모든 행을 조회


SELECT *
FROM THDAY_TBL
WHERE ACNT_NO LIKE '_999929' -- 계좌번호 앞 글자가 하나 존재하고 999929 인 모든 행을 조회

/* NOT LIKE */
SELECT *
FROM THDAY_TBL
WHERE ACNT_NO NOT LIKE '999929%' -- 계좌번호가 999929 로 시작하지 않는 모든 행을 조회


SELECT *
FROM THDAY_TBL
WHERE ACNT_NO NOT LIKE '_999929' -- 계좌번호 앞 글자가 하나 존재하고 999929 이지 않은 모든 행을 조회

 

       Ex) LIKE '%999929'

999929 일치
9999291 불일치
12999929 일치

 

       Ex) LIKE '999929%'

999929 일치
9999291 일치
12999929 불일치

 

       Ex) LIKE '%999929%'

999929 일치
9999291 일치
12999929 일치

 

  ➃ WHERE 안에 여러 데이터를 비교해야 하는 유형

SELECT *
FROM products
WHERE (category, price) = ('전자기기', 75000);
-- WHERE category = '전자기기' AND price = 75000; 과 동일

 

9. ORDER BY, LIMIT

ORDER BY

  ASC (default) : 오름차순

  DESC            : 내림차순

SELECT *
FROM THDAY_TBL
ORDER BY ACNT_NO, INTREST DESC -- ACNT_NO 기준 오름차순, 같다면 INTERST DESC 기준 내림차순

 

LIMIT

  • 데이터가 100만건, 1000만건이라고 생각해보자. SELECT로 한 번에 다 조회하면 시스템에 부하를 주게 된다.
  • 이때, 상위 n개나 하위 n개만 끊어서 조회한다는 의미의 명령어를 사용하면 시스템의 부하를 줄일 수 있다.
  • 정렬(ORDER BY) 후에 사용해야 LIMIT에 의미가 있다. (물론 ORDER BY 절과 꼭 같이 써야하는건 아니다.)
/* 계좌번호 순으로 오름차순 정렬했을 때 상위 5개만 추출 */
SELECT *
FROM THDAY_TBL
ORDER BY ACNT_NO -- ACNT_NO 기준 오름차순
LIMIT 5

 

LIMIT - 오프셋(offset)

  • n개를 건너 뛰고 그 다음 상위 k개를 출력할 수 있다.
  • LIMIT 건너뛸개수(offset), 가져올개수;
  • LIMIT 2 와 LIMIT 0,2 는 같은 의미이다.
/* 문법 1 */
SELECT *
FROM THDAY_TBL
ORDER BY ACNT_NO -- ACNT_NO 기준 오름차순
LIMIT 10, 5 -- 처음 상위 10개는 건너뛰고, 11번째 데이터부터 5개를 가져온다.

/* 문법 2 */
SELECT *
FROM THDAY_TBL
ORDER BY ACNT_NO -- ACNT_NO 기준 오름차순
LIMIT 5 OFFSET 10 -- 처음 상위 10개는 건너뛰고, 11번째 데이터부터 5개를 가져온다.

 

 

  응용 - 페이징 처리

  • 한 페이지에 상품을 2개씩을 보여준다고 가정하자. 그럼 아래와 같은 쿼리문으로 조회해야 할 것이다.
    • 1페이지 : LIMIT 0, 2
    • 2페이지 : LIMIT 2, 2
    • 3페이지 : LIMIT 4, 2
  • 규칙을 찾아볼까?
    • offset = (페이지 번호 - 1 ) * 2

10. NULL

  • DB에서 '값이 없음' 을 의미하는 키워드
  • MySQL에서는 NULL 값을 가장 작은 값으로 취급한다.
  • Oracle은 NULL을 가장 큰 값으로 취급한다.
  • NULL은 IS NULL, IS NOT NULL 연산을 제외한 모든 연산에서 항상 False로 나온다. (김영한의 실전 데이터베이스 입문 51. 문제와 풀이 8:35 참고)

IS NULL (IS NOT NULL)

/* IS NULL */
SELECT *
FROM THDAY_TBL
WHERE INTEREST IS NULL -- INTEREST 값이 없는 것만(NULL인 것만) 조회

/* IS NOT NULL */
SELECT *
FROM THDAY_TBL
WHERE INTEREST IS NOT NULL -- INTEREST 값이 있는 것만(NULL이 아닌 것만) 조회

 

실무) NULL의 위치를 강제로 바꿔야 한다면?

  • Q. MySQL에서 INTEREST를 기준으로 내림차순 정렬을 하되, INTEREST가 NULL인 것은 빨리 확인할 수 있도록 맨 앞으로 보내려면?
    • 과정1) MySQL에서 NULL 값은 가장 작은 값으로 취급된다. 그래서 INTEREST가 NULL인 것은 맨 뒤에 배치된다. 어떻게 INTEREST가 NULL인 값을 앞으로 보낼 수 있을까?
    • 과정2) 과정1을 처리했으면, 이제 INTEREST을 기준으로 내림차순 정렬을 한다.
/* 과정1 해결 */
SELECT INTEREST, INTEREST IS NULL
FROM THDAY_TBL
ORDER BY INTEREST IS NULL DESC -- INTEREST가 NULL이면 1, NULL이 아니면 0이 담기므로 
                            -- 내림차순 정렬을 하여 NULL인 값이 가장 앞으로 나오도록 한다.

 

/* 과정2 해결 */
SELECT INTEREST, INTEREST IS NULL
FROM THDAY_TBL
ORDER BY INTEREST IS NULL DESC, INTEREST DESC

 

NULL을 처리하는 함수들

(MySQL) IFNULL
(Oracle) NVL
(MSSQL) ISNULL
IFNULL(value1, value2)
NVL (value1, value2)
ISNULL (value1, value2)

value1 값이 NULL이면 value2 값을 출력
value1 값이 NULL이 아니면 value1 값을 출력

SELECT IFNULL(name, '이름 없음')
SELECT NVL(name, '이름 없음')
SELECT ISNULL(name, '이름 없음')
COALESCE COALESCE(value1, value2, .... )
NULL이 아닐 때까지 찾아서 출력 (끝까지 가면 끝을 출력)
SELECT COALESCE(name, price, '둘 다 없음')

- name이 null이면 -> price가 null인지 보고 price도 null이면 '둘 다 없음' 출력
- name이 null이면 -> price가 null인지 보고 price가 null이 아니면 price 출력
     

 

 

11. 문자열 함수

 

칼럼의 name 값이 '피자', price가 1000, quantity가 2 라고 가정

CONCAT 문자열 합치기(더하기) SELECT CONCAT(name, ' ', price);
-- 피자 1000
CONCAT_WS
※ MySQL 전용 함수임
구분자를 포함한 문자열 합치기(더하기) SELECT CONCAT_WS('-', name, price, quantity);
-- 피자-1000-2

SELECT CONCAT_WS('-', '010', '1234', '5678');
-- 010-1234-5678
UPPER() 문자열을 모두 대문자로 SELECT UPPER('hi')
-- HI
LOWER() 문자열을 모두 소문자로 SELECT LOWER('HI')
-- hi
LENGTH() 문자열의 길이를 byte 단위로 반환 SELECT LENGTH("안녕")
-- 6 (한글은 3바이트로 처리)
CHAR_LENGTH() 문자열의 글자 수를 반환 SELECT CHAR_LENGTH("안녕")
-- 2

 

 

12. 집계 함수와 GROUP BY절

집계 함수

  • 집계 함수는 GROUP BY 절과 함께 자주 사용된다.
SUM(expression) 합계
AVG(expression) 평균
COUNT(expression) NULL이 아닌 행의 수
COUNT(*) 테이블의 전체 행 수 (NULL 포함)
MAX(expression) 최댓값
MIN(expression) 최솟값

 

NULL이 포함된 경우 집계 함수는 어떻게 처리될까?

  • NULL은 없는 데이터로 취급한다.

아래 테이블명이 testing 인 테이블이 정의되어 있다고 가정하자.

num1 num2
1 1
2 2
3 3
4 4
NULL 5

 

그리고 아래 쿼리를 실행하면 결과는 아래와 같다.

SELECT
	AVG(num1),
    AVG(num2),
    SUM(num1),
    SUM(num2)
FROM 
	testing;

 

MIN(num1) MAX(num1) SUM(num1) SUM(num2) AVG(num1) AVG(num2)
1 4 10 15 2.5000

(1+2+3+4) / 4
3.0000

(1+2+3+4+5) / 5

 

GROUP BY

  • SELECT 절에는 GROUP BY로 지정한 컬럼이나, 집계 함수만 올 수 있으며 그 외에는 오류가 발생한다.
SELECT
	category,
    COUNT(*) as `주문 건수`,
    price,                    -- 오류 발생
    SUM(price)
FROM order_stat
GROUP BY category;
-- HAVING 주문 건수 >= 5 -- MySQL에서는 가능, Oracle과 MSSQL은 오류
                (원칙적으로는 HAVING 다음 SELECT 구문이 실행되므로 불가능한 것이 원칙이나 MySQL에서는 허용해줌)
                DBMS 범용적으로 사용해야 하는 경우, COUNT(*)과 같이 집계 함수를 그대로 넣어서 사용하는 것을 권장

 

  • WHERE 절과 HAVING 절의 차이

(MySQL 기준) WHERE절은 집계 함수에 해당하는 GROUP BY절을 사용하기 이전에 수행되는 구문으로 집계 함수 사용이 불가능하다. 반면 HAVING절은 GROUP BY 절을 사용한 이후에 수행되는 구문이기 때문에 MySQL에서는 예외적으로 집계 함수 사용이 가능하다.

 

13. 별칭 지정 (alias)

  • ` ` 백틱 방식 (추천)
    • SELECT 절에서 사용하는 것은 상관이 없다.
    • SELECT 절이 아닌 다른 절에서 alias 된 것을 사용해도 의도한대로 코드가 작동된다.
SELECT
	category,
    COUNT(*) as `주문 건수`
FROM order_stat
GROUP BY category
ORDER BY `주문 건수`; -- 오류 없이 의도대로 정상적으로 작동
  • ' ', " " 따옴표 방식 (비추천)
    • SELECT 절에서 사용하는 것은 상관이 없다.
    • SELECT 절이 아닌 다른 절에서 alias 된 것을 사용할 때 사용자가 의도한대로 코드가 작동되지 않는다.
    • ' '과 " "는 문자 상수로 인식되기 때문에 아래 예시에서는 "주문 건수" 문자열 상수를 기준으로 정렬을 해버린다.
SELECT
	category,
    COUNT(*) as `주문 건수`
FROM order_stat
GROUP BY category
ORDER BY '주문 건수'; -- 백틱 유의

 

14. SQL 연산 순서

  • From > Where > Group by > Having > Select > Order by
  • 암기 방법 : From 왜 구 했 서 오
  • SQL 연산 순서를 기억해야 하는 이유 : 문법적으로 사용 가능한지 체크하기 위해서
  • 추후 실행 계획 학습에 중요한 개념
SELECT
	category,
    COUNT(*) as `premium_order_count`
FROM order_stat
WHERE price >= 100000 -- WHERE 절에서 별칭이 사용 불가능한 이유는, 
			-- SELECT는 아직 수행이 되지 않았기 때문에 프로그램은 별칭을 모르는 상태이기 때문이다.
GROUP BY category
HAVING COUNT(*) >= 2
ORDER BY premium_order_count; 
-- ORDER BY에서 별칭이 사용 가능한 이유는, SELECT 수행을 마치고 그 이후에 ORDER BY가 실행되기 때문이다.

 

 

출처

일부 자료는 김영한의 실전 데이터베이스 입문 - 모든 IT인을 위한 SQL 첫걸음(SQL부터 차근차근) 을 참조하였음.