🤔
왜 다를까
SQL은 "표준"이 있는데, 왜 DB마다 다를까?
표준(ANSI SQL)은 있지만, 회사(벤더)마다 조금씩 다르게 구현했어요.
SQL에는 ANSI/ISO 표준이 있어서 SELECT·JOIN·WHERE 같은 기본은 거의 똑같아요. 하지만 "상위 N개 뽑기", "자동 증가 번호", "문자열 합치기", "현재 시각" 같은 세부 기능은 벤더마다 다르게 만들었어요. 그래서 MySQL에서 되던 게 Oracle에선 안 되기도 해요.
🧭
실무 팁. 가능하면 표준 문법(예: COALESCE, FETCH FIRST)을 쓰면 DB가 바뀌어도 잘 돌아가요. 벤더 전용 문법(NVL·TOP 등)은 그 DB에서만 동작한다는 걸 기억하세요.
| 주제 | MySQL / MariaDB | PostgreSQL | Oracle | SQL Server |
| 상위 N행 | LIMIT n | LIMIT n | FETCH FIRST n ROWS ONLY | TOP n |
| NULL 대체 | IFNULL | COALESCE | NVL | ISNULL |
| 문자열 연결 | CONCAT() | || | || | + |
| 현재 시각 | NOW() | NOW() | SYSDATE | GETDATE() |
| 자동 증가 PK | AUTO_INCREMENT | SERIAL | SEQUENCE | IDENTITY |
👇 아래에서 항목별로 자세한 예제와 함께 볼게요.
🔑
자동증가 PK
기본키를 자동으로 1씩 증가
테이블 만들 때(CH09) 벤더마다 다른 부분
-- MySQL / MariaDB
CREATE TABLE member (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
-- PostgreSQL (SERIAL, 또는 표준 IDENTITY)
CREATE TABLE member (
id SERIAL PRIMARY KEY, -- 또는 GENERATED ALWAYS AS IDENTITY
name VARCHAR(50)
);
-- SQL Server
CREATE TABLE member (
id INT IDENTITY(1,1) PRIMARY KEY, -- 1부터 1씩 증가
name VARCHAR(50)
);
-- Oracle 12c+ (IDENTITY) / 구버전은 SEQUENCE + 트리거
CREATE TABLE member (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR2(50)
);
| DBMS | 자동 증가 방식 |
| MySQL | AUTO_INCREMENT |
| PostgreSQL | SERIAL / GENERATED AS IDENTITY |
| SQL Server | IDENTITY(시작, 증가폭) |
| Oracle | SEQUENCE(+트리거) / 12c+ IDENTITY |
🔤
문자열
연결 · 부분 추출 · 길이
특히 "문자열 합치기"가 제일 헷갈려요
-- 성 + 이름 합치기
-- MySQL (|| 는 기본적으로 'OR'로 해석 → CONCAT 사용)
SELECT CONCAT(first_name, ' ', last_name) FROM emp;
-- PostgreSQL / Oracle / SQLite (표준 || 연결)
SELECT first_name || ' ' || last_name FROM emp;
-- SQL Server ( + 로 연결, 또는 CONCAT )
SELECT first_name + ' ' + last_name FROM emp;
SELECT CONCAT(first_name, ' ', last_name) FROM emp; -- CONCAT은 대부분 지원
| 기능 | MySQL | PostgreSQL/Oracle | SQL Server |
| 연결 | CONCAT(a,b) | a || b | a + b |
| 부분 추출 | SUBSTRING/SUBSTR | SUBSTR | SUBSTRING |
| 길이 | LENGTH | LENGTH | LEN |
💡CONCAT()는 MySQL·PostgreSQL·SQL Server·Oracle 대부분에서 동작해요. 어느 DB에서든 안전하게 쓰고 싶으면 CONCAT() 을 쓰는 게 편해요.
🕳️
NULL 처리
값이 없을 때 기본값 채우기
COALESCE는 표준 — 어디서나 돼요
-- 전화번호가 없으면 '미등록'으로
-- 표준 (모든 DB에서 동작) ✅ 권장
SELECT COALESCE(phone, '미등록') FROM member;
-- MySQL
SELECT IFNULL(phone, '미등록') FROM member;
-- Oracle
SELECT NVL(phone, '미등록') FROM member;
-- SQL Server
SELECT ISNULL(phone, '미등록') FROM member;
💡COALESCE(값, 기본값)는 ANSI 표준이라 4대 DB 전부에서 돌아가요. IFNULL·NVL·ISNULL은 각자 그 DB 전용이에요.
⚙️
프로시저·함수·트리거
DB 안에 로직을 저장해 두고 부르기
스토어드 프로시저 · 함수 · 트리거
📦 스토어드 프로시저(Stored Procedure)란?
자주 쓰는 SQL 묶음(로직)을 DB 안에 이름 붙여 저장해두고, 필요할 때 이름으로 호출하는 거예요. 매번 긴 쿼리를 보내는 대신 CALL 이름(...) 한 줄로 실행하죠.
🍱 비유: 자주 먹는 메뉴를 "세트 메뉴"로 등록해두고 "1번 세트 주세요"라고 부르는 것과 같아요.
-- ▼ MySQL: 특정 부서의 인원수를 돌려주는 프로시저
DELIMITER //
CREATE PROCEDURE count_by_dept(IN dept_id INT, OUT cnt INT)
BEGIN
SELECT COUNT(*) INTO cnt FROM emp WHERE department_id = dept_id;
END //
DELIMITER ;
-- 호출
CALL count_by_dept(10, @result);
SELECT @result;
① 파라미터 3종 — IN · OUT · INOUT
| 종류 | 방향 | 쓰임 |
IN | 입력(기본값) | 값을 받아서 안에서 사용 (예: 부서 번호) |
OUT | 출력 | 계산 결과를 밖으로 돌려줌 (예: 인원수) |
INOUT | 입출력 | 받은 값을 고쳐서 다시 돌려줌 |
② 변수 · 제어문 (조건·반복)
프로시저 안에서는
변수 선언(DECLARE),
조건(IF),
반복(WHILE·LOOP) 같은 "프로그래밍"을 할 수 있어요. 그래서 단순 쿼리를 넘어
로직을 담아요.
-- ▼ MySQL: 등급을 판정해 돌려주는 프로시저 (변수 + IF 분기)
DELIMITER //
CREATE PROCEDURE grade_of(IN score INT, OUT grade CHAR(1))
BEGIN
IF score >= 90 THEN
SET grade = 'A';
ELSEIF score >= 80 THEN
SET grade = 'B';
ELSE
SET grade = 'C';
END IF;
END //
DELIMITER ;
CALL grade_of(85, @g);
SELECT @g; -- B
-- ▼ 반복(WHILE) 예: 1..n 합계
DELIMITER //
CREATE PROCEDURE sum_to(IN n INT, OUT total INT)
BEGIN
DECLARE i INT DEFAULT 1;
SET total = 0;
WHILE i <= n DO
SET total = total + i;
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
③ 실전 예제 — 여러 작업을 하나로 묶기
프로시저의 진짜 가치는
여러 SQL + 로직 + 트랜잭션을 한 덩어리로 묶는 거예요.
-- ▼ 주문 처리: 재고 확인 → 차감 → 주문 생성 (하나의 트랜잭션)
DELIMITER //
CREATE PROCEDURE place_order(IN p_id INT, IN qty INT, OUT ok INT)
BEGIN
DECLARE stock INT;
START TRANSACTION;
SELECT quantity INTO stock FROM product WHERE id = p_id FOR UPDATE;
IF stock >= qty THEN
UPDATE product SET quantity = quantity - qty WHERE id = p_id;
INSERT INTO orders(product_id, qty) VALUES (p_id, qty);
SET ok = 1;
COMMIT;
ELSE
SET ok = 0; -- 재고 부족
ROLLBACK;
END IF;
END //
DELIMITER ;
호출·선언 문법은 벤더마다 조금씩 달라요.
| DBMS | 본문 언어/문법 | 호출 |
| MySQL | CREATE PROCEDURE ... BEGIN ... END (DELIMITER 필요) | CALL 이름() |
| PostgreSQL | CREATE PROCEDURE ... LANGUAGE plpgsql | CALL 이름() |
| Oracle | CREATE OR REPLACE PROCEDURE ... IS BEGIN ... END; (PL/SQL) | EXEC 이름() |
| SQL Server | CREATE PROCEDURE ... AS BEGIN ... END (T-SQL) | EXEC 이름() |
🆚 프로시저 vs 함수 vs 트리거
· 프로시저(PROCEDURE) — 여러 작업을 수행. 값을 안 돌려주거나 OUT 파라미터로 줌. CALL/EXEC로 직접 호출.
· 함수(FUNCTION) — 값 하나를 반드시 반환. SELECT 안에서 SELECT fn(x)처럼 사용.
· 트리거(TRIGGER) — INSERT/UPDATE/DELETE가 일어나면 자동으로 실행. 직접 호출하지 않아요(예: 이력 자동 기록).
한 줄: 프로시저=시켜서 실행, 함수=값 계산해 반환, 트리거=이벤트에 자동 반응.
-- ▼ 함수 예 (MySQL): 세금 포함 가격 반환
DELIMITER //
CREATE FUNCTION with_tax(price INT) RETURNS INT DETERMINISTIC
BEGIN
RETURN price + (price * 0.1);
END //
DELIMITER ;
SELECT name, with_tax(price) AS total FROM product; -- SELECT 안에서 사용
-- ▼ 트리거 예 (MySQL): 회원 삭제 시 로그 테이블에 자동 기록
CREATE TRIGGER after_member_delete
AFTER DELETE ON member
FOR EACH ROW
INSERT INTO member_log(member_id, deleted_at) VALUES (OLD.id, NOW());
④ 한 걸음 더 — 커서(Cursor)와 예외 처리
커서는 조회 결과를
한 행씩 꺼내 반복 처리할 때 써요(집합 처리로 안 될 때만 최후에).
예외 처리는 오류가 나면 잡아서 롤백·기본값 처리를 해요.
-- ▼ MySQL: 커서로 한 행씩 순회 + 예외 핸들러
DELIMITER //
CREATE PROCEDURE raise_all_salary()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE eid INT;
DECLARE cur CURSOR FOR SELECT id FROM emp; -- 커서 선언
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; -- 더 없으면 종료
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK; -- 오류 시 롤백
OPEN cur;
read_loop: LOOP
FETCH cur INTO eid; -- 한 행씩 꺼냄
IF done = 1 THEN LEAVE read_loop; END IF;
UPDATE emp SET salary = salary * 1.1 WHERE id = eid;
END LOOP;
CLOSE cur;
END //
DELIMITER ;
🧭커서는 느려요. 가능하면 UPDATE emp SET salary = salary * 1.1; 처럼 한 번에(집합) 처리하는 게 훨씬 빨라요. 커서는 "행마다 다른 복잡한 처리가 꼭 필요할 때"만 마지막 수단으로 써요.
⚠️프로시저·트리거는 DB에 로직이 숨어 있어 편하지만, 너무 많이 쓰면 디버깅·이관이 어려워져요. 실무에선 "간단한 자동화·성능이 중요한 배치"엔 쓰고, 복잡한 비즈니스 로직은 애플리케이션(서버) 쪽에 두는 경우가 많아요.