내장 SQL
더 많은 작업
내장 SQL(Embedded SQL)은 C, Java, COBOL 등 일반 프로그래밍 언어의 소스코드 안에 SQL 문장을 삽입하여 데이터베이스를 조작하는 방식이다. 호스트 언어의 절차적 처리 능력과 SQL의 데이터 조작 능력을 함께 사용하기 위해 활용된다.
내장 SQL은 일반 프로그래밍 언어 안에 SQL 문장을 직접 포함시키는 데이터베이스 프로그래밍 방식이다. SQL은 데이터베이스에 대한 질의, 삽입, 수정, 삭제를 담당하고, 호스트 언어는 조건 처리, 반복 처리, 화면 출력, 파일 처리, 업무 로직 처리를 담당한다.
예를 들어 C 언어 프로그램 안에 SQL 문장을 삽입하여 데이터베이스에서 데이터를 조회하고, 조회 결과를 C 언어 변수에 저장하여 처리할 수 있다.
내장 SQL은 독립적으로 실행되는 SQL과 달리, 호스트 언어 프로그램의 일부로 작성되고 전처리 과정을 거쳐 일반 프로그램 코드로 변환된다.
SQL은 데이터베이스를 다루는 데 강력하지만, 단독 SQL만으로 복잡한 업무 로직을 모두 처리하기는 어렵다. 반대로 C, COBOL, Java 같은 일반 프로그래밍 언어는 절차적 처리에는 강하지만 데이터베이스 질의를 직접 표현하기에는 불편하다.
내장 SQL은 이 두 가지를 결합하기 위해 등장하였다.
- SQL로 데이터베이스 접근 수행
- 호스트 언어로 업무 로직 처리
- 데이터베이스 결과를 프로그램 변수에 저장
- 반복, 조건, 예외 처리와 SQL을 결합
- 업무 애플리케이션에서 데이터베이스를 직접 활용
내장 SQL에서는 일반적으로 SQL 문장 앞에 특별한 키워드를 붙여 SQL 문장임을 표시한다.
대표적인 형식은 다음과 같다.
EXEC SQL SQL문;
예:
EXEC SQL SELECT name
INTO :name
FROM employee
WHERE emp_id = :emp_id;
위 예에서 `EXEC SQL`은 해당 문장이 내장 SQL임을 나타낸다. `:name`, `:emp_id`는 호스트 변수이다.
호스트 변수(Host Variable)는 호스트 언어에서 선언된 변수로, SQL 문장과 값을 주고받는 데 사용된다.
SQL 문장에서 호스트 변수를 사용할 때는 보통 변수명 앞에 콜론을 붙인다.
예:
EXEC SQL SELECT salary
INTO :salary
FROM employee
WHERE emp_id = :emp_id;
위 예에서 `salary`와 `emp_id`는 호스트 언어에서 선언된 변수이며, SQL 문장 안에서는 `:salary`, `:emp_id` 형태로 사용된다.
호스트 변수는 다음과 같은 역할을 한다.
- 프로그램에서 SQL에 조건값 전달
- SQL 실행 결과를 프로그램 변수에 저장
- 삽입할 데이터 전달
- 수정할 데이터 전달
- 커서 처리 시 조회 결과 저장
예:
EXEC SQL INSERT INTO employee(emp_id, name, salary)
VALUES (:emp_id, :name, :salary);
위 문장은 호스트 변수의 값을 데이터베이스 테이블에 삽입한다.
내장 SQL은 일반 프로그래밍 언어 코드와 SQL 문장이 섞여 있으므로, 일반 컴파일러가 바로 처리할 수 없다. 따라서 전처리 과정이 필요하다.
일반적인 처리 과정은 다음과 같다.
- 내장 SQL이 포함된 소스코드 작성
- 전처리기(Precompiler)가 SQL 문장 분석
- SQL 문장을 데이터베이스 호출 코드로 변환
- 변환된 호스트 언어 소스코드 생성
- 호스트 언어 컴파일러로 컴파일
- 링크 및 실행 파일 생성
- 실행 시 DBMS와 통신하여 SQL 수행
전처리기 또는 프리컴파일러(Precompiler)는 내장 SQL이 포함된 소스코드를 일반 호스트 언어 소스코드로 변환하는 도구이다.
전처리기의 주요 역할은 다음과 같다.
- `EXEC SQL` 문장 식별
- SQL 문법 검사
- 호스트 변수와 SQL 변수 매핑
- SQL 문장을 DBMS 호출 코드로 변환
- 커서 처리 코드 생성
- 오류 처리 관련 코드 생성
커서(Cursor)는 SQL 질의 결과가 여러 행일 때, 결과 집합을 한 행씩 처리하기 위해 사용하는 개념이다.
일반적인 SELECT 문이 하나의 행만 반환한다면 `SELECT INTO` 방식으로 처리할 수 있다. 그러나 여러 행을 반환하는 경우에는 커서를 사용해야 한다.
커서를 이용한 처리 절차는 일반적으로 다음과 같다.
- 커서 선언
- 커서 열기
- 행 단위로 데이터 가져오기
- 커서 닫기
예:
EXEC SQL DECLARE emp_cursor CURSOR FOR
SELECT emp_id, name
FROM employee;
EXEC SQL OPEN emp_cursor;
EXEC SQL FETCH emp_cursor
INTO :emp_id, :name;
EXEC SQL CLOSE emp_cursor;
| 명령 | 설명 |
|---|---|
| DECLARE CURSOR | 커서를 선언한다. |
| OPEN | 커서를 열고 질의를 실행한다. |
| FETCH | 커서에서 한 행씩 데이터를 가져온다. |
| CLOSE | 커서를 닫는다. |
단일 행 조회는 SELECT 문 결과가 하나의 행일 때 사용한다.
예:
EXEC SQL SELECT name, salary
INTO :name, :salary
FROM employee
WHERE emp_id = :emp_id;
조회 결과는 `INTO` 절에 지정된 호스트 변수에 저장된다.
여러 행을 조회할 때는 커서를 사용한다.
예:
EXEC SQL DECLARE c1 CURSOR FOR
SELECT emp_id, name
FROM employee
WHERE dept_id = :dept_id;
커서를 열고 FETCH를 반복하여 결과 행을 하나씩 처리한다.
내장 SQL에서 INSERT 문을 사용하여 데이터를 삽입할 수 있다.
EXEC SQL INSERT INTO employee(emp_id, name, salary)
VALUES (:emp_id, :name, :salary);
호스트 변수에 저장된 값이 테이블에 삽입된다.
UPDATE 문을 사용하여 데이터를 수정할 수 있다.
EXEC SQL UPDATE employee
SET salary = :salary
WHERE emp_id = :emp_id;
DELETE 문을 사용하여 데이터를 삭제할 수 있다.
EXEC SQL DELETE FROM employee
WHERE emp_id = :emp_id;
내장 SQL에서는 트랜잭션 제어문도 사용할 수 있다.
대표적인 명령은 다음과 같다.
| 명령 | 설명 |
|---|---|
| COMMIT | 트랜잭션의 변경 내용을 확정한다. |
| ROLLBACK | 트랜잭션의 변경 내용을 취소한다. |
예:
EXEC SQL COMMIT;
EXEC SQL ROLLBACK;
내장 SQL에서는 SQL 실행 결과와 오류 상태를 확인해야 한다.
대표적인 오류 처리 방식은 다음과 같다.
- SQLCODE 확인
- SQLSTATE 확인
- WHENEVER 문 사용
- 예외 처리 루틴 호출
- 오류 메시지 기록
SQLCODE는 SQL 실행 결과를 나타내는 상태 코드이다.
일반적으로 다음과 같이 해석한다.
| SQLCODE 값 | 의미 |
|---|---|
| 0 | 정상 수행 |
| 양수 | 경고 또는 특수 상태 |
| 음수 | 오류 발생 |
DBMS와 환경에 따라 세부 값은 달라질 수 있다.
SQLSTATE는 SQL 표준에서 정의한 5자리 문자열 형태의 상태 코드이다.
SQLCODE보다 표준화된 오류 표현 방식으로 사용된다.
WHENEVER 문은 특정 SQL 실행 상태가 발생했을 때 수행할 동작을 지정하는 내장 SQL 구문이다.
예:
EXEC SQL WHENEVER SQLERROR GOTO error_handler; EXEC SQL WHENEVER NOT FOUND GOTO not_found;
대표 조건은 다음과 같다.
| 조건 | 의미 |
|---|---|
| SQLERROR | SQL 오류 발생 |
| NOT FOUND | 조회 결과가 없거나 커서 FETCH 결과가 없음 |
| SQLWARNING | SQL 경고 발생 |
내장 SQL은 SQL 문장이 프로그램 작성 시점에 정해지는지 여부에 따라 정적 SQL과 동적 SQL로 구분할 수 있다.
정적 SQL은 프로그램 작성 시점에 SQL 문장이 고정되어 있는 방식이다.
예:
EXEC SQL SELECT name
INTO :name
FROM employee
WHERE emp_id = :emp_id;
정적 SQL은 전처리 시점에 SQL 구조를 확인할 수 있어 안정적이고 성능 최적화가 쉽다.
동적 SQL은 실행 시점에 SQL 문장을 문자열로 구성하여 실행하는 방식이다.
예를 들어 사용자의 입력이나 조건에 따라 SELECT 문이 달라지는 경우에 사용한다.
동적 SQL은 유연하지만 SQL Injection 위험과 성능 관리 문제가 발생할 수 있다.
내장 SQL 안에서도 동적 SQL을 사용할 수 있다. 즉, 내장 SQL은 프로그래밍 언어 안에 SQL을 포함하는 방식이고, 동적 SQL은 SQL 문장의 구조가 실행 시점에 결정되는 방식이다.
| 구분 | 설명 |
|---|---|
| 내장 SQL | 호스트 언어 안에 SQL 문장을 삽입하는 방식 |
| 정적 SQL | SQL 문장이 프로그램 작성 시점에 고정된 방식 |
| 동적 SQL | SQL 문장이 실행 시점에 생성되거나 결정되는 방식 |
내장 SQL의 장점은 다음과 같다.
- 호스트 언어와 SQL을 함께 사용할 수 있다.
- 업무 로직과 데이터베이스 처리를 결합하기 쉽다.
- SQL 문장이 코드 안에 명시되어 이해하기 쉽다.
- 정적 SQL은 전처리 단계에서 일부 오류를 확인할 수 있다.
- DBMS의 데이터 조작 기능을 직접 활용할 수 있다.
- 커서를 이용해 다중 행 결과를 절차적으로 처리할 수 있다.
내장 SQL의 단점은 다음과 같다.
- 전처리 과정이 필요하다.
- 호스트 언어와 SQL이 섞여 코드가 복잡해질 수 있다.
- 특정 DBMS나 프리컴파일러에 종속될 수 있다.
- SQL 변경 시 프로그램 재컴파일이 필요할 수 있다.
- 동적 SQL 사용 시 보안 취약점이 발생할 수 있다.
- 객체지향 언어의 구조와 잘 맞지 않는 경우가 있다.
내장 SQL은 다음과 같은 환경에서 사용된다.
- 업무용 데이터베이스 애플리케이션
- C 또는 COBOL 기반 레거시 시스템
- 금융권 배치 프로그램
- 공공기관 업무 시스템
- 대형 기간계 시스템
- 데이터베이스 중심 업무 프로그램
데이터베이스를 사용하는 방법에는 내장 SQL 외에도 API 방식이 있다.
대표적인 API 방식은 다음과 같다.
API 방식은 SQL 문장을 문자열로 작성하여 라이브러리나 드라이버를 통해 DBMS에 전달하는 방식이 많다.
| 구분 | 내장 SQL | API 방식 |
|---|---|---|
| 작성 방식 | 호스트 언어 안에 SQL 문장을 직접 삽입 | API 호출을 통해 SQL 실행 |
| 전처리 | 필요 | 일반적으로 불필요 |
| SQL 확인 시점 | 정적 SQL은 전처리 시 확인 가능 | 주로 실행 시 확인 |
| 유연성 | 상대적으로 낮음 | 상대적으로 높음 |
| 대표 예 | EXEC SQL | ODBC, JDBC |
저장 프로시저는 SQL과 절차적 로직을 DBMS 내부에 저장하여 실행하는 방식이다.
| 구분 | 내장 SQL | 저장 프로시저 |
|---|---|---|
| 위치 | 응용 프로그램 소스코드 안 | DBMS 내부 |
| 실행 주체 | 응용 프로그램이 SQL 호출 | DBMS가 저장된 절차 실행 |
| 장점 | 프로그램 로직과 SQL을 함께 관리 가능 | DB 내부에서 반복 로직과 성능 최적화 가능 |
| 단점 | 프로그램 재컴파일 필요 가능 | DBMS 종속성 증가 가능 |
ORM은 객체와 관계형 데이터베이스 테이블을 매핑하여 SQL 작성을 줄이는 방식이다.
| 구분 | 내장 SQL | ORM |
|---|---|---|
| 중심 관점 | SQL 중심 | 객체 중심 |
| SQL 작성 | 개발자가 명시적으로 작성 | 프레임워크가 생성하는 경우 많음 |
| 장점 | SQL 제어가 명확함 | 객체지향 개발에 적합 |
| 단점 | 코드와 SQL이 섞일 수 있음 | 생성 SQL의 성능 이해가 필요 |
내장 SQL에서도 사용자 입력을 부적절하게 SQL에 결합하면 SQL Injection 위험이 발생할 수 있다.
보안 대책은 다음과 같다.
- 호스트 변수와 바인딩 사용
- 사용자 입력값 검증
- 동적 SQL 문자열 결합 최소화
- 최소 권한 DB 계정 사용
- 오류 메시지 과다 노출 방지
- 트랜잭션 처리와 예외 처리 명확화
정보시스템 감리에서는 내장 SQL을 사용하는 시스템에 대해 다음 사항을 점검할 수 있다.
- SQL 문장이 요구사항을 정확히 반영하는가
- 호스트 변수 사용이 적절한가
- 동적 SQL 사용 시 보안 취약점이 없는가
- SQL 오류 처리 로직이 적절한가
- 커서 사용 후 CLOSE가 수행되는가
- 트랜잭션 COMMIT, ROLLBACK 처리가 명확한가
- DBMS 종속성이 과도하지 않은가
- SQL 성능 문제가 없는가
- 개인정보와 중요정보 접근이 통제되는가
- 소스코드와 SQL 변경 이력이 관리되는가
내장 SQL은 데이터베이스 프로그래밍 방식 중 하나로, 호스트 언어와 SQL의 역할 분담을 이해하는 것이 중요하다.
학습 시 다음 내용을 중심으로 정리한다.
- 내장 SQL의 정의
- 호스트 언어와 호스트 변수
- EXEC SQL 구문
- 전처리기 또는 프리컴파일러
- SELECT INTO
- 커서 사용 절차
- SQLCODE와 SQLSTATE
- 트랜잭션 처리
- 정적 SQL과 동적 SQL
- 내장 SQL과 API 방식의 차이
- 내장 SQL은 일반 프로그래밍 언어 안에 SQL 문장을 삽입하는 방식이다.
- SQL 문장을 포함하는 언어를 호스트 언어라고 한다.
- 호스트 변수는 SQL과 프로그램 사이에서 값을 주고받는 변수이다.
- 내장 SQL 문장은 일반적으로 `EXEC SQL`로 시작한다.
- 내장 SQL은 전처리기 또는 프리컴파일러를 통해 일반 호스트 언어 코드로 변환된다.
- 단일 행 조회에는 SELECT INTO를 사용할 수 있다.
- 다중 행 조회에는 커서를 사용한다.
- 커서 처리 절차는 DECLARE, OPEN, FETCH, CLOSE 순서로 정리할 수 있다.
- SQLCODE와 SQLSTATE는 SQL 실행 결과와 오류 상태를 확인하는 데 사용된다.
- COMMIT은 트랜잭션을 확정하고 ROLLBACK은 트랜잭션을 취소한다.
- 정적 SQL은 SQL 문장이 프로그램 작성 시점에 고정되어 있다.
- 동적 SQL은 SQL 문장이 실행 시점에 결정된다.
- 내장 SQL은 SQL을 직접 제어할 수 있지만 전처리 과정과 DBMS 종속성이 문제가 될 수 있다.