

 Amazon Redshift는 패치 198부터 새 Python UDF 생성을 더 이상 지원하지 않습니다. 기존 Python UDF는 2026년 6월 30일까지 계속 작동합니다. 자세한 내용은 [블로그 게시물](https://aws.amazon.com/blogs/big-data/amazon-redshift-python-user-defined-functions-will-reach-end-of-support-after-june-30-2026/)을 참조하세요.

# Amazon Redshift에서 저장 프로시저 생성
<a name="stored-procedure-overview"></a>

이 주제에서는 Amazon Redshift에서 저장 프로시저를 생성하고 사용하는 방법을 설명합니다. 저장 프로시저는 여러 프로그램에서 사용할 수 있는 SQL 문 모음입니다.

PostgreSQL 프로시저 언어 PL/pgSQL을 사용하여 Amazon Redshift 저장 프로시저를 정의함으로써 SQL 쿼리 및 논리 연산을 수행할 수 있습니다. 프로시저는 데이터베이스에 저장되고 충분한 데이터베이스 권한이 있는 모든 사용자가 사용할 수 있습니다.

사용자 정의 함수(UDF)와 달리, 저장 프로시저는 SELECT 쿼리 외에도 데이터 정의 언어(DDL) 및 데이터 조작 언어(DML)를 통합할 수 있습니다. 저장 프로시저는 값을 반환할 필요가 없습니다. 루프 및 조건부 표현식을 포함한 프로시저 언어를 사용하여 논리 흐름을 제어할 수 있습니다.

저장 프로시저를 생성 및 관리하기 위한 SQL 명령에 대한 자세한 내용은 다음 명령 주제를 참조하십시오.
+ [CREATE PROCEDURE](r_CREATE_PROCEDURE.md)
+ [ALTER PROCEDURE](r_ALTER_PROCEDURE.md)
+ [DROP PROCEDURE](r_DROP_PROCEDURE.md)
+ [SHOW PROCEDURE](r_SHOW_PROCEDURE.md)
+ [CALL](r_CALL_procedure.md)
+ [GRANT](r_GRANT.md)
+ [REVOKE](r_REVOKE.md)
+ [ALTER DEFAULT PRIVILEGES](r_ALTER_DEFAULT_PRIVILEGES.md)

**Topics**
+ [Amazon Redshift의 저장 프로시저 개요](stored-procedure-create.md)
+ [PL/pgSQL 언어 참조](c_pl_pgSQL_reference.md)

# Amazon Redshift의 저장 프로시저 개요
<a name="stored-procedure-create"></a>

이 주제에서는 저장 프로시저의 용도와 사용에 대해 자세히 설명합니다.

저장 프로시저는 일반적으로 데이터 변환 로직, 데이터 검증 및 비즈니스별 로직을 캡슐화하는 데 사용됩니다. 여러 SQL 단계를 저장 프로시저로 결합함으로써 애플리케이션과 데이터베이스 간 왕복 횟수를 줄일 수 있습니다.

세분화된 액세스 제어를 통해 사용자에게 기본 테이블에 대한 액세스 권한을 부여하지 않고도 함수를 수행하도록 저장 프로시저를 생성할 수 있습니다. 예를 들어 소유자 또는 수퍼유저만 테이블을 자를 수 있고, 사용자가 데이터를 테이블에 삽입하려면 쓰기 권한이 필요합니다. 사용자에게 기본 테이블에 대한 권한을 부여하는 대신에, 작업을 수행하는 저장 프로시저를 생성할 수 있습니다. 그런 다음 사용자에게 저장 프로시저를 실행할 권한을 부여합니다.

DEFINER 보안 속성이 있는 저장 프로시저는 저장 프로시저의 소유자 권한으로 실행됩니다. 기본적으로 저장 프로시저에는 INVOKER 보안이 있습니다. 이는 프로시저가 프로시저를 호출하는 사용자의 권한을 사용함을 뜻합니다.

저장 프로시저를 생성하려면 [CREATE PROCEDURE](r_CREATE_PROCEDURE.md) 명령을 사용합니다. 프로시저를 실행하려면 [CALL](r_CALL_procedure.md) 명령을 사용합니다. 이 단윈의 뒷부분에 예제가 나와 있습니다.

**참고**  
일부 클라이언트는 Amazon Redshift 저장 프로시저를 생성할 때 다음 오류를 표시할 수 있습니다.  

```
ERROR: 42601: [Amazon](500310) unterminated dollar-quoted string at or near "$$
```
이 오류는 클라이언트가 문을 구분하는 세미콜론과 달러 기호(\$1) 인용으로 CREATE PROCEDURE 문을 올바르게 구문 분석할 수 없어 발생합니다. 그 결과 문의 일부만 Amazon Redshift 서버로 전송됩니다. 보통 클라이언트의 `Run as batch` 또는 `Execute selected` 옵션을 사용하여 이 오류를 해결할 수 있습니다.  
예를 들어 Aginity 클라이언트를 사용하는 경우 `Run entire script as batch` 옵션을 사용합니다. SQL Workbench/J를 사용하는 경우 버전 124를 사용하는 것이 좋습니다. SQL Workbench/J 버전 125를 사용하는 경우 차선책으로서 대체 구분 기호를 지정하는 것을 고려합니다.  
CREATE PROCEDURE에는 세미콜론(;)으로 구분된 SQL 문이 포함되어 있습니다. 슬래시(/) 등의 대체 구분 기호를 정의하고 CREATE PROCEDURE 문의 끝에 이를 배치하면 처리를 위해 문이 Amazon Redshift 서버에 전송됩니다. 다음은 한 예입니다.  

```
CREATE OR REPLACE PROCEDURE test()
AS $$
BEGIN
  SELECT 1 a;
END;
$$
LANGUAGE plpgsql
;
/
```
[Amazon Redshift 콘솔의 쿼리 에디터](https://docs.aws.amazon.com/redshift/latest/mgmt/query-editor.html) 또는 TablePlus와 같이 CREATE PROCEDURE 문의 구문 분석을 지원하는 클라이언트를 사용할 수 있습니다.

**Topics**
+ [저장 프로시저 명명](stored-procedure-naming.md)
+ [저장 프로시저의 보안 및 권한](stored-procedure-security-and-privileges.md)
+ [저장 프로시저에서 결과 세트 반환](stored-procedure-result-set.md)
+ [트랜잭션 관리](stored-procedure-transaction-management.md)
+ [오류 트래핑](stored-procedure-trapping-errors.md)
+ [저장 프로시저 로깅](c_PLpgSQL-logging.md)
+ [저장 프로시저 제한 사항](stored-procedure-constraints.md)

다음 예제에서는 출력 인수가 없는 프로시저를 보여 줍니다. 기본적으로 인수는 입력(IN) 인수입니다.

```
CREATE OR REPLACE PROCEDURE test_sp1(f1 int, f2 varchar)
AS $$
BEGIN
  RAISE INFO 'f1 = %, f2 = %', f1, f2;
END;
$$ LANGUAGE plpgsql;

call test_sp1(5, 'abc');
INFO: f1 = 5, f2 = abc
CALL
```



**참고**  
 저장 프로시저를 작성할 때는 민감한 값을 보호하기 위한 모범 사례를 참조하는 것이 좋습니다.  
민감한 정보를 저장 프로시저 로직 내에 하드 코딩하지 마세요. 예를 들어, 저장 프로시저 본문의 CREATE USER 문에 사용자 암호를 할당하지 마세요. 하드 코딩된 값이 카탈로그 테이블에 스키마 메타 데이터로 기록될 수 있기 때문에 보안 위험이 따릅니다. 암호와 같은 민감한 값은 파라미터를 사용하여 저장 프로시저에 인수로 전달하세요.  
저장 프로시저에 대한 자세한 내용은 [CREATE PROCEDURE](r_CREATE_PROCEDURE.md) 및 [Amazon Redshift에서 저장 프로시저 생성](stored-procedure-overview.md) 섹션을 참조하세요. 카탈로그 테이블에 대한 자세한 내용은 [시스템 카탈로그 테이블](c_intro_catalog_views.md) 섹션을 참조하세요.

다음 예제에서는 출력 인수가 있는 프로시저를 보여 줍니다. 인수는 입력(IN), 입력 및 출력(INOUT), 출력(OUT)입니다.

```
CREATE OR REPLACE PROCEDURE test_sp2(f1 IN int, f2 INOUT varchar(256), out_var OUT varchar(256))
AS $$
DECLARE
  loop_var int;
BEGIN
  IF f1 is null OR f2 is null THEN
    RAISE EXCEPTION 'input cannot be null';
  END IF;
  DROP TABLE if exists my_etl;
  CREATE TEMP TABLE my_etl(a int, b varchar);
    FOR loop_var IN 1..f1 LOOP
        insert into my_etl values (loop_var, f2);
        f2 := f2 || '+' || f2;
    END LOOP;
  SELECT INTO out_var count(*) from my_etl;
END;
$$ LANGUAGE plpgsql;


call test_sp2(2,'2019');

         f2          | column2
---------------------+---------
 2019+2019+2019+2019 | 2
(1 row)
```

# 저장 프로시저 명명
<a name="stored-procedure-naming"></a>

이 주제에서는 저장 프로시저 이름에 대한 세부 정보를 설명합니다.

이름이 동일하나 입력 인수 데이터 유형 또는 서명이 다른 프로시저를 정의하는 경우, 새 프로시저를 생성합니다. 이렇게 하면 프로시저 이름이 오버로드됩니다. 자세한 내용은 [프로시저 이름 오버로드](#stored-procedure-overloading-name) 섹션을 참조하세요. Amazon Redshift는 출력 인수를 기반으로 하는 프로시저 오버로드를 사용하지 않습니다. 이름 및 입력 인수 데이터 유형이 동일하지만 출력 인수 유형은 다른 두 프로시저를 가질 수 없습니다.

소유자 또는 수퍼유저는 저장 프로시저의 본문을 동일한 서명이 있는 새 본문으로 바꿀 수 있습니다. 저장 프로시저의 서명 또는 반환 형식을 변경하려면 저장 프로시저를 삭제한 후 다시 생성합니다. 자세한 내용은 [DROP PROCEDURE](r_DROP_PROCEDURE.md) 및 [CREATE PROCEDURE](r_CREATE_PROCEDURE.md)을 참조하십시오.

저장 프로시저를 실행하기 전에 저장 프로시저의 명명 규칙을 고려하여 잠재적 충돌이나 뜻밖의 결과를 피할 수 있습니다. 프로시저 이름을 오버로드할 수 있으므로 기존 및 향후 Amazon Redshift 프로시저 이름과 충돌할 수 있습니다.

## 프로시저 이름 오버로드
<a name="stored-procedure-overloading-name"></a>

프로시저는 이름과 서명으로 구분합니다. 여기에서 서명이란 입력 인수의 수와 인수의 데이터 형식을 말합니다. 서명만 다르다면 동일한 스키마의 프로시저 2개가 이름이 같을 수도 있습니다. 다시 말해 프로시저 이름을 오버로드할 수 있습니다.

프로시저를 실행하면 쿼리 엔진이 입력 인수의 수와 데이터 형식을 기준으로 호출할 프로시저를 결정합니다. 이때 오버로딩을 사용하면 다수의 인수를 바꿔가며 CREATE PROCEDURE 명령에서 허용하는 최대 수까지 프로시저를 시뮬레이셜할 수 있습니다. 자세한 내용은 [CREATE PROCEDURE](r_CREATE_PROCEDURE.md) 섹션을 참조하세요.

## 이름 충돌 방지
<a name="stored-procedure-name-conflicts"></a>

접두사 `sp_`를 사용하여 모든 절차의 이름을 지정하는 것이 좋습니다. Amazon Redshift는 저장 프로시저 전용으로 `sp_` 접두사를 예약합니다. 프로시저 이름에 `sp_` 접두사를 붙이면 프로시저 이름이 기존 또는 향후 Amazon Redshift 프로시저 이름과 충돌하지 않게 할 수 있습니다.

# 저장 프로시저의 보안 및 권한
<a name="stored-procedure-security-and-privileges"></a>

이 주제에서는 저장 프로시저를 만들고 실행하는 데 필요한 데이터베이스 자격 증명에 대해 설명합니다.

기본적으로 모든 사용자는 프로시저를 생성할 권한이 있습니다. 프로시저를 생성하려면 언어 PL/pgSQL에 대한 USAGE 권한이 있어야 하며, 이 권한은 기본적으로 PUBLIC에 부여됩니다. 기본적으로 수퍼유저 및 소유자만 프로시저를 호출할 권한이 있습니다. 수퍼유저는 사용자가 저장 프로시저를 생성하지 못하도록 사용자의 PL/pgSQL에서 REVOKE USAGE를 실행할 수 있습니다.

프로시저를 호출하려면 프로시저에 대한 EXECUTE 권한을 부여받아야 합니다. 기본적으로 새 프로시저에 대한 EXECUTE 권한은 프로시저 소유자 및 수퍼유저에게 부여됩니다. 자세한 내용은 [GRANT](r_GRANT.md) 섹션을 참조하세요.

기본적으로 프로시저를 생성하는 사용자가 소유자입니다. 소유자는 기본적으로 해당 프로시저에 대한 CREATE, DROP, EXECUTE 권한이 있습니다. 수퍼유저는 모든 권한을 갖습니다.

SECURITY 속성은 데이터베이스 객체에 액세스할 프로시저의 권한을 제어합니다. 저장 프로시저를 생성할 때 SECURITY 속성을 DEFINER 또는 INVOKER로 설정할 수 있습니다. 이 속성은 저장 프로시저의 본문에서 문을 실행할 때 사용되는 권한을 결정합니다. SECURITY INVOKER를 지정하는 경우, 프로시저는 프로시저를 호출하는 사용자의 권한을 사용합니다. SECURITY DEFINER를 지정하는 경우, 프로시저는 프로시저 소유자의 권한을 사용합니다. INVOKER가 기본값입니다.

SECURITY DEFINER 프로시저는 이를 소유한 사용자의 권한으로 실행되므로, 프로시저가 잘못 사용되지 않도록 주의하세요. SECURITY DEFINER 프로시저가 잘못 사용되지 되지 않도록 하려면 다음을 수행합니다.
+ PUBLIC이 아니라 특정 사용자에게 SECURITY DEFINER 프로시저에 대한 EXECUTE를 부여합니다.
+ 프로시저가 스키마 이름을 사용하여 액세스해야 하는 모든 데이터베이스 객체를 한정합니다. 예를 들어 단순히 `myschema.mytable` 대신 `mytable`을 사용합니다.
+ 객체 이름을 스키마로 한정할 수 없는 경우, SET 옵션을 사용하여 프로시저를 생성할 때 `search_path`를 설정합니다. `search_path`를 설정하여 신뢰할 수 없는 사용자가 작성할 수 있는 스키마를 제외합니다. 그러면 이 프로시저의 호출자가 프로시저에서 사용할 객체를 마스킹하는 객체(예: 테이블 또는 뷰)를 생성하지 못하게 할 수 있습니다. SET 옵션에 대한 자세한 내용은 [CREATE PROCEDURE](r_CREATE_PROCEDURE.md) 섹션을 참조하세요.

다음 예제에서는 `search_path`를 `admin`으로 설정하여 `user_creds` 테이블이 `admin` 스키마에서 액세스되고 퍼블릭 또는 호출자의 `search_path`에 있는 다른 스키마에서 액세스하지 못하게 합니다.

```
CREATE OR REPLACE PROCEDURE sp_get_credentials(userid int, o_creds OUT varchar)
AS $$
BEGIN
  SELECT creds INTO o_creds
  FROM user_creds
  WHERE user_id = $1;
END;
$$ LANGUAGE plpgsql
SECURITY DEFINER
-- Set a secure search_path
SET search_path = admin;
```

# 저장 프로시저에서 결과 세트 반환
<a name="stored-procedure-result-set"></a>

이 주제에서는 저장 프로시저가 데이터를 반환하는 방법을 설명합니다.

커서 또는 임시 테이블을 사용하여 결과 세트를 반환할 수 있습니다.

## 커서 반환
<a name="stored-procedure-return-cursor"></a>

커서를 반환하려면 `refcursor` 데이터 형식으로 정의된 INOUT 인수를 사용하여 프로시저를 생성합니다. 프로시저를 호출할 때 커서에 이름을 지정합니다. 그러면 커서에서 이름을 기준으로 결과를 가져올 수 있습니다.

다음 예제에서는 `get_result_set` 데이터 형식을 사용하여 `rs_out`라는 INOUT 인수로 `refcursor`라는 프로시저를 생성합니다. 이 프로시저는 SELECT 문을 사용하여 커서를 엽니다.

```
CREATE OR REPLACE PROCEDURE get_result_set (param IN integer, rs_out INOUT refcursor)
AS $$
BEGIN
  OPEN rs_out FOR SELECT * FROM fact_tbl where id >= param;
END;
$$ LANGUAGE plpgsql;
```

다음 CALL 명령은 이름이 `mycursor`인 커서를 엽니다. 트랜잭션 내에서만 커서를 사용합니다.

```
BEGIN;
CALL get_result_set(1, 'mycursor');
```

커서가 열리면 다음 예제와 같이 커서에서 가져올 수 있습니다.

```
FETCH ALL FROM mycursor;

    id | secondary_id | name
-------+--------------+---------
     1 |            1 | Joe
     1 |            2 | Ed
     2 |            1 | Mary
     1 |            3 | Mike
(4 rows)
```

결국 트랜잭션이 커밋되거나 롤백됩니다.

```
COMMIT;   
```

저장 프로시저에 의해 반환된 커서에는 DECLARE CURSOR에서 설명한 것과 동일한 제약 및 성능 고려 사항이 적용됩니다. 자세한 내용은 [커서 제약 조건](declare.md#declare-constraints) 섹션을 참조하세요.

다음 예제에서는 JDBC의 `get_result_set` 데이터 형식을 사용하여 `refcursor` 저장 프로시저의 호출을 보여 줍니다. 리터럴 `'mycursor'`(커서 이름)는 `prepareStatement`로 전달됩니다. 그런 다음 `ResultSet`에서 결과를 가져옵니다.

```
static void refcursor_example(Connection conn) throws SQLException {
    conn.setAutoCommit(false);
    PreparedStatement proc = conn.prepareStatement("CALL get_result_set(1, 'mycursor')");
    proc.execute();
    ResultSet rs = statement.executeQuery("fetch all from mycursor");
    while (rs.next()) {
      int n = rs.getInt(1);
      System.out.println("n " + n);
    }
```

## 임시 테이블 사용
<a name="stored-procedure-return-cursor"></a>

결과를 반환하기 위해 핸들을 결과 행이 포함된 임시 테이블로 반환할 수 있습니다. 클라이언트는 이름을 파라미터로 저장 프로시저에 제공할 수 있습니다. 저장 프로시저 내부에서 동적 SQL을 사용하여 임시 테이블에서 작업할 수 있습니다. 다음은 그 한 예입니다.

```
CREATE PROCEDURE get_result_set(param IN integer, tmp_name INOUT varchar(256)) as $$
DECLARE
  row record;
BEGIN
  EXECUTE 'drop table if exists ' || tmp_name;
  EXECUTE 'create temp table ' || tmp_name || ' as select * from fact_tbl where id <= ' || param;
END;
$$ LANGUAGE plpgsql;

CALL get_result_set(2, 'myresult');
 tmp_name
-----------
 myresult
(1 row)

SELECT * from myresult;
 id | secondary_id | name
----+--------------+------
  1 |            1 | Joe
  2 |            1 | Mary
  1 |            2 | Ed
  1 |            3 | Mike
(4 rows)
```

# 트랜잭션 관리
<a name="stored-procedure-transaction-management"></a>

기본 트랜잭션 관리 동작 또는 NONATOMIC 동작을 사용하여 저장 프로시저를 만들 수 있습니다.

## 기본 모드 저장 프로시저 트랜잭션 관리
<a name="stored-procedure-transaction-management-default-mode"></a>

기본 트랜잭션 모드 자동 커밋 동작은 별도로 실행되는 각각의 SQL 명령이 개별적으로 커밋되게 합니다. 저장 프로시저 호출은 단일 SQL 명령으로 처리됩니다. 프로시저 내부의 SQL 문은 호출이 시작되면 암시적으로 시작되고 호출이 끝나면 종료되는 트랜잭션 블록에 있는 것처럼 동작합니다. 다른 프로시저에 대한 중첩 호출은 다른 SQL 문처럼 취급되고 호출자와 동일한 트랜잭션의 컨텍스트 내에서 작동합니다. 자동 커밋 동작에 대한 자세한 내용은 [Amazon Redshift의 격리 수준](c_serial_isolation.md) 섹션을 참조하세요.

그러나 사용자 지정 트랜잭션 블록(BEGIN...COMMIT에 의해 정의됨) 내에서 저장 프로시저를 호출한다고 가정합니다. 이 경우 저장 프로시저의 모든 문은 사용자 지정 트랜잭션의 컨텍스트에서 실행됩니다. 프로시저는 종료 시 암시적으로 커밋하지 않습니다. 호출자는 프로시저 커밋 또는 롤백을 제어합니다.

저장 프로시저를 실행하는 동안 오류가 발생하면 현재 트랜잭션의 모든 변경 사항이 롤백됩니다.

저장 프로시저에서 다음과 같은 트랜잭션 제어 문을 사용할 수 있습니다.
+ COMMIT - 현재 트랜잭션에서 수행된 모든 작업을 커밋하고 새 트랜잭션을 암시적으로 시작합니다. 자세한 내용은 [COMMIT](r_COMMIT.md) 섹션을 참조하세요.
+ ROLLBACK - 현재 트랜잭션에서 수행된 작업을 롤백하고 새 트랜잭션을 암시적으로 시작합니다. 자세한 내용은 [ROLLBACK](r_ROLLBACK.md) 섹션을 참조하세요.

TRUNCATE는 저장 프로시저 내에서 발행할 수 있는 또 다른 문으로 트랜잭션 관리에 영향을 줍니다. Amazon Redshift에서 TRUNCATE는 커밋을 암시적으로 실행합니다. 이 동작은 저장 프로시저의 컨텍스트에서 동일하게 유지됩니다. 저장 프로시저 내에서 TRUNCATE 문이 실행되면 현재 트랜잭션을 커밋하고 새 트랜잭션을 시작합니다. 자세한 내용은 [TRUNCATE](r_TRUNCATE.md) 섹션을 참조하세요.

COMMIT, ROLLBACK 또는 TRUNCATE 문을 따르는 모든 문은 새 트랜잭션의 컨텍스트에서 실행됩니다. COMMIT, ROLLBACK 또는 TRUNCATE 문이 나타나거나 저장 프로시저가 종료될 때까지 실행됩니다.

저장 프로시저 내에서 COMMIT, ROLLBACK 또는 TRUNCATE 문을 사용하는 경우, 다음 제약이 적용됩니다.
+ 트랜잭션 블록 내에서 저장 프로시저가 호출된 경우, COMMIT, ROLLBACK 문을 TRUNCATE 문을 실행할 수 없습니다. 이 제약은 저장 프로시저의 자체 본문 및 중첩된 모든 프로시저 호출 내에 적용됩니다.
+ 저장 프로시저가 `SET config` 옵션으로 생성된 경우, COMMIT, ROLLBACK 문을 TRUNCATE 문을 실행할 수 없습니다. 이 제약은 저장 프로시저의 자체 본문 및 중첩된 모든 프로시저 호출 내에 적용됩니다.
+ COMMIT, ROLLBACK 또는 TRUNCATE 문이 처리되면 (명시적으로 또는 암시적으로) 열린 모든 커서가 자동으로 닫힙니다. 명시적 및 암시적 커서에 대한 제약은 [저장 프로시저 제한 사항](stored-procedure-constraints.md) 섹션을 참조하세요.

또한 동적 SQL을 사용하여 COMMIT 또는 ROLLBACK을 실행할 수 없습니다. 그러나 동적 SQL을 사용하여 TRUNCATE를 실행할 수 있습니다. 자세한 내용은 [Dynamic SQL](c_PLpgSQL-statements.md#r_PLpgSQL-dynamic-sql) 섹션을 참조하세요.

저장 프로시저를 작업할 때 PL/pgSQL의 BEGIN 및 END 문은 그룹화에만 사용된다는 점을 고려하십시오. 트랜잭션을 시작하거나 종료하지 않습니다. 자세한 내용은 [차단](c_PLpgSQL-structure.md#r_PLpgSQL-block) 섹션을 참조하세요.

다음 예제에서는 명시적 트랜잭션 블록 내에서 저장 프로시저를 호출할 때 트랜잭션 동작을 보여 줍니다. 저장 프로시저 외부에서 실행된 두 삽입 문과 내부에서 실행된 한 삽입 문은 모두 동일한 트랜잭션(3382)의 일부입니다. 사용자가 명시적 커밋을 실행하면 트랜잭션이 커밋됩니다.

```
CREATE OR REPLACE PROCEDURE sp_insert_table_a(a int) LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO test_table_a values (a);
END;
$$;

Begin;
  insert into test_table_a values (1);
  Call sp_insert_table_a(2);
  insert into test_table_a values (3);
Commit; 

select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  | pid |  type   |               stmt_text
--------+------+-----+---------+----------------------------------------
    103 | 3382 | 599 | UTILITY | Begin;
    103 | 3382 | 599 | QUERY   | insert into test_table_a values (1);
    103 | 3382 | 599 | UTILITY | Call sp_insert_table_a(2);
    103 | 3382 | 599 | QUERY   | INSERT INTO test_table_a values ( $1 )
    103 | 3382 | 599 | QUERY   | insert into test_table_a values (3);
    103 | 3382 | 599 | UTILITY | COMMIT
```

반대로 동일한 문이 명시적 트랜잭션 블록 외부에서 실행되고 세션이 자동 커밋을 ON으로 설정한 경우를 예로 들겠습니다. 이 경우 각 문은 자체 트랜잭션에서 실행됩니다.

```
insert into test_table_a values (1);
Call sp_insert_table_a(2);
insert into test_table_a values (3);

select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  | pid |  type   |                                                                    stmt_text
--------+------+-----+---------+-------------------------------------------------------------------------------------------------------------------------------------------------
    103 | 3388 | 599 | QUERY   | insert into test_table_a values (1);
    103 | 3388 | 599 | UTILITY | COMMIT
    103 | 3389 | 599 | UTILITY | Call sp_insert_table_a(2);
    103 | 3389 | 599 | QUERY   | INSERT INTO test_table_a values ( $1 )
    103 | 3389 | 599 | UTILITY | COMMIT
    103 | 3390 | 599 | QUERY   | insert into test_table_a values (3);
    103 | 3390 | 599 | UTILITY | COMMIT
```

다음 예제에서는 `test_table_a`에 삽입한 후 TRUNCATE 문을 실행합니다. TRUNCATE 문은 현재 트랜잭션(3335)을 커밋하고 새 트랜잭션(3336)을 시작하는 암시적 커밋을 실행합니다. 프로시저가 종료되면 새 트랜잭션이 커밋됩니다.

```
CREATE OR REPLACE PROCEDURE sp_truncate_proc(a int, b int) LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO test_table_a values (a);
  TRUNCATE test_table_b;
  INSERT INTO test_table_b values (b);
END;
$$;

Call sp_truncate_proc(1,2);

select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |  pid  |  type   |                                                                                             stmt_text
--------+------+-------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    103 | 3335 | 23636 | UTILITY | Call sp_truncate_proc(1,2);
    103 | 3335 | 23636 | QUERY   | INSERT INTO test_table_a values ( $1 )
    103 | 3335 | 23636 | UTILITY | TRUNCATE test_table_b
    103 | 3335 | 23636 | UTILITY | COMMIT
    103 | 3336 | 23636 | QUERY   | INSERT INTO test_table_b values ( $1 )
    103 | 3336 | 23636 | UTILITY | COMMIT
```

다음 예제에서는 중첩 호출에서 TRUNCATE를 실행합니다. TRUNCATE는 트랜잭션(3344)의 외부 및 내부 프로시저에서 지금까지 완료된 모든 작업을 커밋합니다. 새 트랜잭션(3345)을 시작합니다. 외부 프로시저가 종료되면 새 트랜잭션이 커밋됩니다.

```
CREATE OR REPLACE PROCEDURE sp_inner(c int, d int) LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO inner_table values (c);
  TRUNCATE outer_table;
  INSERT INTO inner_table values (d);
END;
$$;

CREATE OR REPLACE PROCEDURE sp_outer(a int, b int, c int, d int) LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO outer_table values (a);
  Call sp_inner(c, d);
  INSERT INTO outer_table values (b);
END;
$$;

Call sp_outer(1, 2, 3, 4);

select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |  pid  |  type   |                                                                                              stmt_text
--------+------+-------+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    103 | 3344 | 23636 | UTILITY | Call sp_outer(1, 2, 3, 4);
    103 | 3344 | 23636 | QUERY   | INSERT INTO outer_table values ( $1 )
    103 | 3344 | 23636 | UTILITY | CALL sp_inner( $1 , $2 )
    103 | 3344 | 23636 | QUERY   | INSERT INTO inner_table values ( $1 )
    103 | 3344 | 23636 | UTILITY | TRUNCATE outer_table
    103 | 3344 | 23636 | UTILITY | COMMIT
    103 | 3345 | 23636 | QUERY   | INSERT INTO inner_table values ( $1 )
    103 | 3345 | 23636 | QUERY   | INSERT INTO outer_table values ( $1 )
    103 | 3345 | 23636 | UTILITY | COMMIT
```

다음 예제에서는 TRUNCATE 문이 커밋되었을 때 커서 `cur1`이 닫혔다는 것을 보여 줍니다.

```
CREATE OR REPLACE PROCEDURE sp_open_cursor_truncate()
LANGUAGE plpgsql
AS $$
DECLARE
  rec RECORD;
  cur1 cursor for select * from test_table_a order by 1;
BEGIN
  open cur1;
  TRUNCATE table test_table_b;
  Loop
    fetch cur1 into rec;
    raise info '%', rec.c1;
    exit when not found;
  End Loop;
END
$$;

call sp_open_cursor_truncate();
ERROR: cursor "cur1" does not exist
CONTEXT: PL/pgSQL function "sp_open_cursor_truncate" line 8 at fetch
```

다음 예제에서는 TRUNCATE 문을 실행하고 명시적 트랜잭션 블록 내에서 호출할 수 없습니다.

```
CREATE OR REPLACE PROCEDURE sp_truncate_atomic() LANGUAGE plpgsql
AS $$
BEGIN
  TRUNCATE test_table_b;
END;
$$;

Begin;
  Call sp_truncate_atomic();
ERROR: TRUNCATE cannot be invoked from a procedure that is executing in an atomic context.
HINT: Try calling the procedure as a top-level call i.e. not from within an explicit transaction block. 
Or, if this procedure (or one of its ancestors in the call chain) was created with SET config options, recreate the procedure without them.
CONTEXT: SQL statement "TRUNCATE test_table_b"
PL/pgSQL function "sp_truncate_atomic" line 2 at SQL statement
```

다음 예에서는 슈퍼 사용자 또는 테이블 소유자가 아닌 사용자가 테이블에 TRUNCATE 문을 실행할 수 있음을 보여줍니다. 사용자는 `Security Definer` 저장 프로시저를 사용하여 이 작업을 수행합니다. 이 예에서는 다음 작업을 보여줍니다.
+ user1이 테이블 `test_tbl`을 생성합니다.
+ user1이 저장 프로시저 `sp_truncate_test_tbl`을 생성합니다.
+ user1은 저장 프로시저에 대한 `EXECUTE` 권한을 user2에게 부여합니다.
+ user2는 저장 프로시저를 실행하여 테이블 `test_tbl`을 잘라냅니다. 이 예에서는 `TRUNCATE` 명령 전후의 행 수를 보여줍니다.

```
set session_authorization to user1;
create table test_tbl(id int, name varchar(20));
insert into test_tbl values (1,'john'), (2, 'mary');
CREATE OR REPLACE PROCEDURE sp_truncate_test_tbl() LANGUAGE plpgsql
AS $$
DECLARE
  tbl_rows int;
BEGIN
  select count(*) into tbl_rows from test_tbl;
  RAISE INFO 'RowCount before Truncate: %', tbl_rows;
  TRUNCATE test_tbl;
  select count(*) into tbl_rows from test_tbl;
  RAISE INFO 'RowCount after Truncate: %', tbl_rows;
END;
$$ SECURITY DEFINER;
grant execute on procedure sp_truncate_test_tbl() to user2;
reset session_authorization;


set session_authorization to user2;
call sp_truncate_test_tbl();
INFO:  RowCount before Truncate: 2
INFO:  RowCount after Truncate: 0
CALL
reset session_authorization;
```

다음 예에서는 COMMIT을 두 번 실행합니다. 첫 번째 COMMIT는 트랜잭션 10363에서 수행된 모든 작업을 커밋하고 트랜잭션 10364를 암시적으로 시작합니다. 트랜잭션 10364는 두 번째 COMMIT 문으로 커밋됩니다.

```
CREATE OR REPLACE PROCEDURE sp_commit(a int, b int) LANGUAGE plpgsql
AS $$
BEGIN
  INSERT INTO test_table values (a);
  COMMIT;
  INSERT INTO test_table values (b);
  COMMIT;
END;
$$;

call sp_commit(1,2);

select userid, xid, pid, type, trim(text) as stmt_text
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;
 userid |  xid  | pid  |  type   |                                                                                    stmt_text
--------+-------+------+---------+-----------------------------------------------------------------------------------------------------------------
    100 | 10363 | 3089 | UTILITY | call sp_commit(1,2);
    100 | 10363 | 3089 | QUERY   | INSERT INTO test_table values ( $1 )
    100 | 10363 | 3089 | UTILITY | COMMIT
    100 | 10364 | 3089 | QUERY   | INSERT INTO test_table values ( $1 )
    100 | 10364 | 3089 | UTILITY | COMMIT
```

다음 예제에서는 `sum_vals`가 2보다 큰 경우 ROLLBACK 문을 실행합니다. 첫 번째 ROLLBACK 문은 트랜잭션 10377에서 수행된 모든 작업을 롤백하고 새 트랜잭션 10378을 시작합니다. 프로시저가 종료되면 트랜잭션 10378이 커밋됩니다.

```
CREATE OR REPLACE PROCEDURE sp_rollback(a int, b int) LANGUAGE plpgsql
AS $$
DECLARE
  sum_vals int;
BEGIN
  INSERT INTO test_table values (a);
  SELECT sum(c1) into sum_vals from test_table;
  IF sum_vals > 2 THEN
    ROLLBACK;
  END IF;
  
  INSERT INTO test_table values (b);
END;
$$;

call sp_rollback(1, 2);

select userid, xid, pid, type, trim(text) as stmt_text
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

userid |  xid  | pid  |  type   |                                                                                    stmt_text
--------+-------+------+---------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    100 | 10377 | 3089 | UTILITY | call sp_rollback(1, 2);
    100 | 10377 | 3089 | QUERY   | INSERT INTO test_table values ( $1 )
    100 | 10377 | 3089 | QUERY   | SELECT sum(c1) from test_table
    100 | 10377 | 3089 | QUERY   | Undoing 1 transactions on table 133646 with current xid 10377 : 10377
    100 | 10378 | 3089 | QUERY   | INSERT INTO test_table values ( $1 )
    100 | 10378 | 3089 | UTILITY | COMMIT
```

## NONATOMIC 모드 저장 프로시저 트랜잭션 관리
<a name="stored-procedure-transaction-management-nonatomic-mode"></a>

NONATOMIC 모드에서 생성된 저장 프로시저는 기본 모드에서 생성된 프로시저와는 다른 트랜잭션 제어 동작을 가집니다. 저장 프로시저 외부에 있는 SQL 명령의 자동 커밋 동작과 유사하게, NONATOMIC 프로시저 내의 각 SQL 문은 자체 트랜잭션에서 실행되고 자동으로 커밋됩니다. 사용자가 NONATOMIC 저장 프로시저 내에서 명시적 트랜잭션 블록을 시작하는 경우 블록 내의 SQL 문은 자동으로 커밋되지 않습니다. 트랜잭션 블록은 트랜잭션 블록 내 문의 커밋 또는 롤백을 제어합니다.

NONATOMIC 저장 프로시저에서는 START TRANSACTION 문을 사용하여 프로시저 내에서 명시적 트랜잭션 블록을 열 수 있습니다. 하지만 이미 열려 있는 트랜잭션 블록이 있는 경우 Amazon Redshift는 하위 트랜잭션을 지원하지 않으므로 이 명령문은 아무런 동작도 하지 않습니다. 이전 트랜잭션이 계속됩니다.

NONATOMIC 프로시저에서 cursor FOR 루프를 사용하는 경우 쿼리 결과를 반복하기 전에 명시적 트랜잭션 블록을 열어야 합니다. 그러지 않으면 루프 내의 SQL 문이 자동으로 커밋될 때 커서가 닫힙니다.

NONATOMIC 모드 동작을 사용할 때 고려해야 할 몇 가지 사항은 다음과 같습니다.
+ 열린 트랜잭션 블록이 없고 세션에 자동 커밋이 켜짐으로 설정된 경우 저장 프로시저 내의 각 SQL 문이 자동으로 커밋됩니다.
+ 저장 프로시저가 트랜잭션 블록 내에서 호출되는 경우 COMMIT/ROLLBACK/TRUNCATE 문을 실행하여 트랜잭션을 종료할 수 있습니다. 이는 기본 모드에서는 불가능합니다.
+ START TRANSACTION 문을 실행하여 저장 프로시저 내에서 트랜잭션 블록을 시작할 수 있습니다.

다음 예제는 NONATOMIC 저장 프로시저를 사용할 때의 트랜잭션 동작을 보여줍니다. 다음 예제의 모든 세션에서는 자동 커밋이 켜짐으로 설정되어 있습니다.

다음 예제에서 NONATOMIC 저장 프로시저에는 2개의 INSERT 문이 있습니다. 트랜잭션 블록 외부에서 프로시저를 호출하면 프로시저 내의 모든 INSERT 문이 자동으로 커밋됩니다.

```
CREATE TABLE test_table_a(v int); 
CREATE TABLE test_table_b(v int); 

CREATE OR REPLACE PROCEDURE sp_nonatomic_insert_table_a(a int, b int) NONATOMIC AS
$$
BEGIN
    INSERT INTO test_table_a values (a);
    INSERT INTO test_table_b values (b);
END;
$$ 
LANGUAGE plpgsql;

Call sp_nonatomic_insert_table_a(1,2);

Select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |    pid     |  type   |               stmt_text                
--------+------+------------+---------+----------------------------------------
      1 | 1792 | 1073807554 | UTILITY | Call sp_nonatomic_insert_table_a(1,2);
      1 | 1792 | 1073807554 | QUERY   | INSERT INTO test_table_a values ( $1 )
      1 | 1792 | 1073807554 | UTILITY | COMMIT
      1 | 1793 | 1073807554 | QUERY   | INSERT INTO test_table_b values ( $1 )
      1 | 1793 | 1073807554 | UTILITY | COMMIT
(5 rows)
```

그러나 BEGIN..COMMIT 블록 내에서 프로시저를 호출하면 모든 문이 동일한 트랜잭션의 일부가 됩니다(xid=1799).

```
Begin;
  INSERT INTO test_table_a values (10);
  Call sp_nonatomic_insert_table_a(20,30);
  INSERT INTO test_table_b values (40);
Commit; 

Select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |    pid     |  type   |                stmt_text                 
--------+------+------------+---------+------------------------------------------
      1 | 1799 | 1073914035 | UTILITY | Begin;
      1 | 1799 | 1073914035 | QUERY   | INSERT INTO test_table_a values (10);
      1 | 1799 | 1073914035 | UTILITY | Call sp_nonatomic_insert_table_a(20,30);
      1 | 1799 | 1073914035 | QUERY   | INSERT INTO test_table_a values ( $1 )
      1 | 1799 | 1073914035 | QUERY   | INSERT INTO test_table_b values ( $1 )
      1 | 1799 | 1073914035 | QUERY   | INSERT INTO test_table_b values (40);
      1 | 1799 | 1073914035 | UTILITY | COMMIT
(7 rows)
```

이 예제에서는 START TRANSACTION...COMMIT 사이에 두 개의 INSERT 문이 있습니다. 프로시저가 트랜잭션 블록 외부에서 호출되면 두 INSERT 문은 동일한 트랜잭션에 있게 됩니다(xid=1866).

```
CREATE OR REPLACE PROCEDURE sp_nonatomic_txn_block(a int, b int) NONATOMIC AS
$$
BEGIN
    START TRANSACTION;
    INSERT INTO test_table_a values (a);
    INSERT INTO test_table_b values (b);
    COMMIT;
END;
$$ 
LANGUAGE plpgsql;

Call sp_nonatomic_txn_block(1,2);

Select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |    pid     |  type   |               stmt_text                
--------+------+------------+---------+----------------------------------------
      1 | 1865 | 1073823998 | UTILITY | Call sp_nonatomic_txn_block(1,2);
      1 | 1866 | 1073823998 | QUERY   | INSERT INTO test_table_a values ( $1 )
      1 | 1866 | 1073823998 | QUERY   | INSERT INTO test_table_b values ( $1 )
      1 | 1866 | 1073823998 | UTILITY | COMMIT
(4 rows)
```

BEGIN...COMMIT 블록 내에서 프로시저를 호출하면 이미 열려 있는 트랜잭션이 있기 때문에 프로시저 내의 START TRANSACTION은 아무런 동작도 하지 않습니다. 프로시저 내의 COMMIT은 현재 트랜잭션(xid=1876)을 커밋하고 새 트랜잭션을 시작합니다.

```
Begin;
  INSERT INTO test_table_a values (10);
  Call sp_nonatomic_txn_block(20,30);
  INSERT INTO test_table_b values (40);
Commit; 

Select userid, xid, pid, type, trim(text) as stmt_text 
from svl_statementtext where pid = pg_backend_pid() order by xid , starttime , sequence;

 userid | xid  |    pid     |  type   |               stmt_text                
--------+------+------------+---------+----------------------------------------
      1 | 1876 | 1073832133 | UTILITY | Begin;
      1 | 1876 | 1073832133 | QUERY   | INSERT INTO test_table_a values (10);
      1 | 1876 | 1073832133 | UTILITY | Call sp_nonatomic_txn_block(20,30);
      1 | 1876 | 1073832133 | QUERY   | INSERT INTO test_table_a values ( $1 )
      1 | 1876 | 1073832133 | QUERY   | INSERT INTO test_table_b values ( $1 )
      1 | 1876 | 1073832133 | UTILITY | COMMIT
      1 | 1878 | 1073832133 | QUERY   | INSERT INTO test_table_b values (40);
      1 | 1878 | 1073832133 | UTILITY | COMMIT
(8 rows)
```

이 예제에서는 커서 루프를 사용하는 방법을 보여줍니다. 테이블 test\$1table\$1a에는 세 개의 값이 있습니다. 목표는 세 값을 반복하여 test\$1table\$1b 테이블에 삽입하는 것입니다. 다음과 같은 방식으로 NONATOMIC 저장 프로시저를 만들면 첫 번째 루프에서 INSERT 문을 실행한 후 커서 ‘cur1’이 존재하지 않는다는 오류가 발생합니다. 이는 INSERT의 자동 커밋이 열린 커서를 닫기 때문입니다.

```
insert into test_table_a values (1), (2), (3);

CREATE OR REPLACE PROCEDURE sp_nonatomic_cursor() NONATOMIC
LANGUAGE plpgsql
AS $$
DECLARE
  rec RECORD;
  cur1 cursor for select * from test_table_a order by 1;
BEGIN
  open cur1;
  Loop
    fetch cur1 into rec;
    exit when not found;
    raise info '%', rec.v;
    insert into test_table_b values (rec.v);
  End Loop;
END
$$;

CALL sp_nonatomic_cursor();

INFO:  1
ERROR:  cursor "cur1" does not exist
CONTEXT:  PL/pgSQL function "sp_nonatomic_cursor" line 7 at fetch
```

커서 루프가 작동하도록 하려면 START TRANSACTION...COMMIT 사이에 루프를 배치하세요.

```
insert into test_table_a values (1), (2), (3);

CREATE OR REPLACE PROCEDURE sp_nonatomic_cursor() NONATOMIC
LANGUAGE plpgsql
AS $$
DECLARE
  rec RECORD;
  cur1 cursor for select * from test_table_a order by 1;
BEGIN
  START TRANSACTION;
  open cur1;
  Loop
    fetch cur1 into rec;
    exit when not found;
    raise info '%', rec.v;
    insert into test_table_b values (rec.v);
  End Loop;
  COMMIT;
END
$$;

CALL sp_nonatomic_cursor();

INFO:  1
INFO:  2
INFO:  3
CALL
```

# 오류 트래핑
<a name="stored-procedure-trapping-errors"></a>

이 주제에서는 Amazon Redshift에서 오류를 처리하는 방법을 설명합니다.

저장 프로시저의 쿼리 또는 명령에 오류가 발생하면 후속 쿼리가 실행되지 않고 트랜잭션이 롤백됩니다. 그러나 EXCEPTION 블록을 사용하여 오류를 처리할 수 있습니다.

**참고**  
기본 동작은 저장 프로시저에 추가 오류 생성 조건이 없는 경우에도 오류로 인해 후속 쿼리가 실행되지 않는 것입니다.

```
[ <<label>> ]
[ DECLARE
  declarations ]
BEGIN
  statements
EXCEPTION
  WHEN OTHERS THEN
    statements
END;
```

예외가 발생하고 예외 처리 블록을 추가하면 RAISE 문과 대부분의 다른 PL/pgSQL 문을 작성할 수 있습니다. 예를 들어 사용자 정의 메시지로 예외를 발생시키거나 기록을 로깅 테이블에 삽입할 수 있습니다.

예외 처리 블록에 들어갈 때 현재 트랜잭션이 롤백되고 블록의 명령문을 실행하기 위해 새 트랜잭션이 생성됩니다. 블록의 문이 오류 없이 실행되면 트랜잭션이 커밋되고 예외가 다시 발생합니다. 마지막으로 저장 프로시저가 종료됩니다.

예외 블록에서 지원되는 유일한 조건은 쿼리 취소를 제외한 모든 오류 유형과 일치하는 OTHERS입니다. 또한 예외 처리 블록에서 오류가 발생하면 외부 예외 처리 블록에서 이를 catch할 수 있습니다.

NONATOMIC 프로시저 내에서 오류가 발생하여 예외 블록에서 처리되는 경우 오류가 다시 발생하지 않습니다. 예외 처리 블록에서 포착한 예외를 발생시키려면 PL/pgSQL 문 `RAISE`를 참조하세요. 이 문은 예외 처리 블록에서만 유효합니다. 자세한 내용은 [RAISE](c_PLpgSQL-statements.md#r_PLpgSQL-messages-errors)을 참조하세요.

**저장 프로시저에서 오류가 나타난 후 발생하는 상황을 CONTINUE 핸들러를 사용하여 제어**

 `CONTINUE` 핸들러는 NONATOMIC 저장 프로시저 내의 실행 흐름을 제어하는 일종의 예외 핸들러입니다. 핸들러를 사용하면 기존 문 블록을 종료하지 않고도 예외를 파악하고 처리할 수 있습니다. 일반적으로 저장 프로시저에서 오류가 발생하면 흐름이 중단되고 오류가 호출자에게 반환됩니다. 하지만 일부 사용 사례에서는 오류 상태가 흐름이 중단될 만큼 심각하지 않을 수도 있습니다. 별도의 트랜잭션에서 선택한 오류 처리 로직을 사용하여 오류를 적절하게 처리한 다음 오류 다음에 오는 문을 계속 실행하는 것이 좋습니다. 다음은 구문을 보여줍니다.

```
[ DECLARE
  declarations ]
BEGIN
  statements
EXCEPTION
  [ CONTINUE_HANDLER | EXIT_HANDLER ] WHEN OTHERS THEN
    handler_statements
END;
```

다양한 유형의 오류에 대한 정보를 수집하는 데 도움이 되는 몇 가지 시스템 테이블이 있습니다. 자세한 내용은 [STL\$1LOAD\$1ERRORS](r_STL_LOAD_ERRORS.md), [STL\$1ERROR](r_STL_ERROR.md), [SYS\$1STREAM\$1SCAN\$1ERRORS](r_SYS_STREAM_SCAN_ERRORS.md) 섹션을 참조하세요. 오류 해결에 사용할 수 있는 추가 시스템 테이블도 있습니다. 이에 대한 자세한 정보는 [시스템 테이블 및 뷰 참조](cm_chap_system-tables.md)에서 찾아볼 수 있습니다.

## 예제
<a name="stored-procedure-trapping-errors-examples"></a>

다음 예는 예외 처리 블록에서 문을 작성하는 방법을 보여줍니다. 저장 프로시저는 기본 트랜잭션 관리 동작을 사용합니다.

```
CREATE TABLE employee (firstname varchar, lastname varchar);
INSERT INTO employee VALUES ('Tomas','Smith');
CREATE TABLE employee_error_log (message varchar);

CREATE OR REPLACE PROCEDURE update_employee_sp() AS
$$
BEGIN
    UPDATE employee SET firstname = 'Adam' WHERE lastname = 'Smith';
    EXECUTE 'select invalid';
EXCEPTION WHEN OTHERS THEN
    RAISE INFO 'An exception occurred.';
    INSERT INTO employee_error_log VALUES ('Error message: ' || SQLERRM);
END;
$$ 
LANGUAGE plpgsql;

CALL update_employee_sp();

INFO:  An exception occurred.
ERROR:  column "invalid" does not exist
CONTEXT:  SQL statement "select invalid"
PL/pgSQL function "update_employee_sp" line 3 at execute statement
```

이 예에서 `update_employee_sp`를 호출하면 *예외 발생(An exception occurred.)*이라는 정보 메시지가 나타나고 로깅 테이블의 `employee_error_log` 로그에 오류 메시지가 삽입됩니다. 저장 프로시저가 종료되기 전에 원래 예외가 다시 발생합니다. 다음 쿼리는 예제를 실행한 결과로 생성된 레코드를 보여줍니다.

```
SELECT * from employee;

firstname | lastname 
-----------+----------
 Tomas     | Smith

SELECT * from employee_error_log;

          message                     
------------------------------------------------
 Error message: column "invalid" does not exist
```

형식 관련 도움말 및 추가 수준 목록 등 RAISE에 대한 자세한 내용은 [지원되는 PL/pgSQL 문](c_PLpgSQL-statements.md) 섹션을 참조하세요.

다음 예는 예외 처리 블록에서 문을 작성하는 방법을 보여줍니다. 저장 프로시저는 NONATOMIC 트랜잭션 관리 동작을 사용합니다. 이 예제에서는 프로시저 호출이 완료된 후 호출자에게 다시 오류가 발생하지 않습니다. 다음 문의 오류로 인해 UPDATE 문이 롤백되지 않습니다. 정보 메시지가 표시되고 오류 메시지가 로깅 테이블에 삽입됩니다.

```
CREATE TABLE employee (firstname varchar, lastname varchar); 
INSERT INTO employee VALUES ('Tomas','Smith'); 
CREATE TABLE employee_error_log (message varchar);

-- Create the SP in NONATOMIC mode
CREATE OR REPLACE PROCEDURE update_employee_sp_2() NONATOMIC AS
$$
BEGIN
    UPDATE employee SET firstname = 'Adam' WHERE lastname = 'Smith';
    EXECUTE 'select invalid';
EXCEPTION WHEN OTHERS THEN
    RAISE INFO 'An exception occurred.';
    INSERT INTO employee_error_log VALUES ('Error message: ' || SQLERRM);
END;
$$ 
LANGUAGE plpgsql;

CALL update_employee_sp_2();
INFO:  An exception occurred.
CALL

SELECT * from employee;

 firstname | lastname 
-----------+----------
 Adam      | Smith
(1 row)

SELECT * from employee_error_log;

                    message                     
------------------------------------------------
 Error message: column "invalid" does not exist
(1 row)
```

이 예에서는 2개의 하위 블록으로 프로시저를 만드는 방법을 보여줍니다. 저장 프로시저가 호출되면 첫 번째 하위 블록의 오류가 해당 예외 처리 블록에서 처리됩니다. 첫 번째 하위 블록이 완료된 후 프로시저는 두 번째 하위 블록을 계속 실행합니다. 결과를 보면 프로시저 호출이 완료될 때 오류가 발생하지 않음을 알 수 있습니다. employee 테이블에 대한 UPDATE 및 INSERT 작업이 커밋됩니다. 두 예외 블록의 오류 메시지가 로깅 테이블에 삽입됩니다.

```
CREATE TABLE employee (firstname varchar, lastname varchar); 
INSERT INTO employee VALUES ('Tomas','Smith'); 
CREATE TABLE employee_error_log (message varchar);

CREATE OR REPLACE PROCEDURE update_employee_sp_3() NONATOMIC AS
$$
BEGIN
    BEGIN
        UPDATE employee SET firstname = 'Adam' WHERE lastname = 'Smith';
        EXECUTE 'select invalid1';
    EXCEPTION WHEN OTHERS THEN
        RAISE INFO 'An exception occurred in the first block.';
        INSERT INTO employee_error_log VALUES ('Error message: ' || SQLERRM);
    END;
    BEGIN
        INSERT INTO employee VALUES ('Edie','Robertson');
        EXECUTE 'select invalid2';
    EXCEPTION WHEN OTHERS THEN
        RAISE INFO 'An exception occurred in the second block.';
        INSERT INTO employee_error_log VALUES ('Error message: ' || SQLERRM);
    END;
END;
$$ 
LANGUAGE plpgsql;

CALL update_employee_sp_3();
INFO:  An exception occurred in the first block.
INFO:  An exception occurred in the second block.
CALL

SELECT * from employee;

 firstname | lastname  
-----------+-----------
 Adam      | Smith
 Edie      | Robertson
(2 rows)

SELECT * from employee_error_log;

                     message                     
-------------------------------------------------
 Error message: column "invalid1" does not exist
 Error message: column "invalid2" does not exist
(2 rows)
```

다음 예시는 CONTINUE 예외 핸들러를 사용하는 방법을 보여줍니다. 이 샘플은 두 개의 테이블을 만들어 저장 프로시저에서 사용합니다. CONTINUE 핸들러는 NONATOMIC 트랜잭션 관리 동작을 사용하여 저장 프로시저의 실행 흐름을 제어합니다.

```
CREATE TABLE tbl_1 (a int);
CREATE TABLE tbl_error_logging(info varchar, err_state varchar, err_msg varchar);

CREATE OR REPLACE PROCEDURE sp_exc_handling_1() NONATOMIC AS
$$
BEGIN
    INSERT INTO tbl_1 VALUES (1);
    -- Expect an error for the insert statement following, because of the invalid value
    INSERT INTO tbl_1 VALUES ("val");
    INSERT INTO tbl_1 VALUES (2);
EXCEPTION CONTINUE_HANDLER WHEN OTHERS THEN
    INSERT INTO tbl_error_logging VALUES ('Encountered error', SQLSTATE, SQLERRM);
END;
$$ LANGUAGE plpgsql;
```

저장 프로시저를 호출합니다.

```
CALL sp_exc_handling_1();
```

흐름은 다음과 같이 진행됩니다.

1. 호환되지 않는 데이터 유형을 열에 삽입하려고 하면 오류가 발생합니다. 제어가 EXCEPTION 블록으로 전달됩니다. 예외 처리 블록이 입력될 때 현재 트랜잭션이 롤백되고 문을 실행하기 위해 새 암시적 트랜잭션이 생성됩니다.

1. CONTINUE\$1HANDLER의 문이 오류 없이 실행되면 제어가 문 바로 뒤에 있는 문으로 전달되어 예외가 발생합니다. CONTINUE\$1HANDLER의 문에서 새 예외가 발생하는 경우 EXCEPTION 블록 내에서 예외 핸들러를 사용하여 해당 예외를 처리할 수 있습니다.

샘플 저장 프로시저를 호출한 후 테이블에 다음 레코드가 포함됩니다.
+ `SELECT * FROM tbl_1;`을 실행하면 두 개의 레코드가 반환됩니다. 여기에는 `1` 및 `2` 값이 포함됩니다.
+ `SELECT * FROM tbl_error_logging;`을 실행하면 *오류 발생*, *42703*, *'val' 열이 tbl\$11에 존재하지 않음*이라는 값이 있는 레코드 하나가 반환됩니다.

다음 추가 오류 처리 예시에서는 EXIT 핸들러와 CONTINUE 핸들러를 모두 사용합니다. 그러면 데이터 테이블과 로깅 테이블이라는 두 개의 테이블이 생성됩니다. 또한 오류 처리를 보여주는 저장 프로시저가 생성됩니다.

```
CREATE TABLE tbl_1 (a int);
CREATE TABLE tbl_error_logging(info varchar, err_state varchar, err_msg varchar);

CREATE OR REPLACE PROCEDURE sp_exc_handling_2() NONATOMIC AS
$$
BEGIN
    INSERT INTO tbl_1 VALUES (1);
    BEGIN
        INSERT INTO tbl_1 VALUES (100);
        -- Expect an error for the insert statement following, because of the invalid value
        INSERT INTO tbl_1 VALUES ("val");
        INSERT INTO tbl_1 VALUES (101);
    EXCEPTION EXIT_HANDLER WHEN OTHERS THEN
        INSERT INTO tbl_error_logging VALUES ('Encountered error', SQLSTATE, SQLERRM);
    END;
    INSERT INTO tbl_1 VALUES (2);
    -- Expect an error for the insert statement following, because of the invalid value
    INSERT INTO tbl_1 VALUES ("val");
    INSERT INTO tbl_1 VALUES (3);
EXCEPTION CONTINUE_HANDLER WHEN OTHERS THEN
    INSERT INTO tbl_error_logging VALUES ('Encountered error', SQLSTATE, SQLERRM);
END;
$$ LANGUAGE plpgsql;
```

저장 프로시저를 만든 후 다음을 사용하여 호출합니다.

```
CALL sp_exc_handling_2();
```

BEGIN과 END라는 내부 세트로 괄호가 묶인 내부 예외 블록에서 오류가 발생하면 EXIT 핸들러가 오류를 처리합니다. 외부 블록에서 발생하는 모든 오류는 CONTINUE 핸들러에서 처리합니다.

샘플 저장 프로시저를 호출한 후 테이블에 다음 레코드가 포함됩니다.
+ `SELECT * FROM tbl_1;`을 실행하면 값이 1, 2, 3, 100인 레코드 네 개가 반환됩니다.
+ `SELECT * FROM tbl_error_logging;`을 실행하면 두 개의 레코드가 반환됩니다. 값은 *오류 발생*, *42703*, *'val' 열이 tbl\$11에 존재하지 않음*입니다.

**tbl\$1error\$1logging** 테이블이 존재하지 않는 경우 예외가 발생합니다.

다음 예시는 FOR 루프와 함께 CONTINUE 예외 핸들러를 사용하는 방법을 보여줍니다. 이 샘플은 세 개의 테이블을 만들어 저장 프로시저 내의 FOR 루프에서 사용합니다. FOR 루프는 결과 세트 변형입니다. 즉, 쿼리 결과를 반복합니다.

```
CREATE TABLE tbl_1 (a int);
INSERT INTO tbl_1 VALUES (1), (2), (3);
CREATE TABLE tbl_2 (a int);
CREATE TABLE tbl_error_logging(info varchar, err_state varchar, err_msg varchar);

CREATE OR REPLACE PROCEDURE sp_exc_handling_loop() NONATOMIC AS
$$
DECLARE
 rec RECORD;
BEGIN
    FOR rec IN SELECT a FROM tbl_1
    LOOP
        IF rec.a = 2 THEN
            -- Expect an error for the insert statement following, because of the invalid value
            INSERT INTO tbl_2 VALUES("val");
        ELSE
            INSERT INTO tbl_2 VALUES (rec.a);
        END IF;
    END LOOP;
EXCEPTION CONTINUE_HANDLER WHEN OTHERS THEN
    INSERT INTO tbl_error_logging VALUES ('Encountered error', SQLSTATE, SQLERRM);
END;
$$ LANGUAGE plpgsql;
```

저장 프로시저를 호출합니다.

```
CALL sp_exc_handling_loop();
```

샘플 저장 프로시저를 호출한 후 테이블에 다음 레코드가 포함됩니다.
+  `SELECT * FROM tbl_2;`을 실행하면 두 개의 레코드가 반환됩니다. 여기에는 1 및 3 값이 포함됩니다.
+ `SELECT * FROM tbl_error_logging;`을 실행하면 *오류 발생*, *42703*, *'val' 열이 tbl\$12에 존재하지 않음*이라는 값이 있는 레코드 하나가 반환됩니다.

CONTINUE 핸들러와 관련된 사용 참고 사항:
+ CONTINUE\$1HANDLER 및 EXIT\$1HANDLER 키워드는 NONATOMIC 저장 프로시저에서만 사용할 수 있습니다.
+ CONTINUE\$1HANDLER 및 EXIT\$1HANDLER 키워드는 선택 사항입니다. EXIT\$1HANDLER가 기본값입니다.

# 저장 프로시저 로깅
<a name="c_PLpgSQL-logging"></a>

이 주제에서는 Amazon Redshift가 저장 프로시저 로깅에 사용하는 저장 프로시저 및 뷰에 대해 설명합니다.

저장 프로시저에 대한 세부 정보가 다음 시스템 테이블 및 뷰에 기록됩니다.
+ SVL\$1STORED\$1PROC\$1CALL - 저장 프로시저 호출의 시작 시간과 종료 시간, 호출이 완료 전에 종료되었는지 여부에 대한 세부 정보가 기록됩니다. 자세한 내용은 [SVL\$1STORED\$1PROC\$1CALL](r_SVL_STORED_PROC_CALL.md) 섹션을 참조하세요.
+ SVL\$1STORED\$1PROC\$1MESSAGES - RAISE 쿼리에서 방출되는 저장 프로시저의 메시지는 해당 로깅 수준으로 기록됩니다. 자세한 내용은 [SVL\$1STORED\$1PROC\$1MESSAGES](r_SVL_STORED_PROC_MESSAGES.md) 섹션을 참조하세요.
+ SVL\$1QLOG - 저장 프로시저에서 호출된 각 쿼리에 대해 프로시저 호출의 쿼리 ID가 기록됩니다. 자세한 내용은 [SVL\$1QLOG](r_SVL_QLOG.md) 섹션을 참조하세요.
+ STL\$1UTILITYTEXT - 저장 프로시저 호출이 완료된 후 기록됩니다. 자세한 내용은 [STL\$1UTILITYTEXT](r_STL_UTILITYTEXT.md) 섹션을 참조하세요.
+ PG\$1PROC\$1INFO - 이 시스템 카탈로그 뷰는 저장 프로시저에 대한 정보를 보여줍니다. 자세한 내용은 [PG\$1PROC\$1INFO](r_PG_PROC_INFO.md) 섹션을 참조하세요.

# 저장 프로시저 제한 사항
<a name="stored-procedure-constraints"></a>

이 주제에서는 Amazon Redshift 저장 프로시저에 대한 제한 사항을 설명합니다.

Amazon Redshift 저장 프로시저를 사용하는 경우 다음 고려 사항이 적용됩니다.

## 저장 프로시저 지원에 대한 Amazon Redshift와 PostgreSQL 간의 차이점
<a name="stored-procedure-differences"></a>

 다음은 Amazon Redshift와 PostgreSQL의 저장 프로시저 지원 간 차이점입니다.
+ Amazon Redshift는 하위 트랜잭션을 지원하지 않으므로 예외 처리 블록에 대한 지원이 제한됩니다.

## 고려 사항 및 제한 사항
<a name="stored-procedure-limits"></a>

다음은 Amazon Redshift의 저장 프로시저에 대한 고려 사항입니다.
+ 데이터베이스의 최대 저장 프로시저 수는 10,000개입니다.
+ 프로시저 소스 코드의 최대 크기는 2MB입니다.
+ 사용자 세션 하나에서 동시에 열 수 있는 명시적 및 묵시적 커서의 최대 수는 1개입니다. SQL 문의 결과 세트를 반복하는 FOR 루프는 암시적 커서를 엽니다. 중첩 커서는 지원되지 않습니다.
+ 명시적 및 암시적 커서는 결과 집합 크기의 제한이 표준 Amazon Redshift 커서와 동일합니다. 자세한 내용은 [커서 제약 조건](declare.md#declare-constraints) 섹션을 참조하세요.
+ 중첩 호출의 최대 수준 수는 16입니다.
+ 프로시저 파라미터의 최대 수는 입력 인수의 경우 32, 출력 인수의 경우 32입니다.
+ 저장 프로시저의 최대 변수 수는 1,024입니다.
+ 자체 트랜잭션 컨텍스트가 필요한 모든 SQL 명령은 저장 프로시저 내부에서 지원되지 않습니다. 그러한 예는 다음과 같습니다.
  + PREPARE
  + CREATE/DROP DATABASE
  + CREATE EXTERNAL TABLE
  + VACUUM
  + SET LOCAL
  + ALTER TABLE APPEND
+ Java Database Connectivity(JDBC) 드라이버를 통한 `registerOutParameter` 메서드 호출은 `refcursor` 데이터 형식에서 지원되지 않습니다. 데이터 형식 사용 예는 `refcursor` 섹션을 참조하세요..[저장 프로시저에서 결과 세트 반환](stored-procedure-result-set.md)

# PL/pgSQL 언어 참조
<a name="c_pl_pgSQL_reference"></a>

Amazon Redshift의 저장 프로시저는 PostgreSQL PL/pgSQL 프로시저 언어를 기반으로 하지만 몇 가지 중요한 차이점이 있습니다. 이 참조에서는 Amazon Redshift에서 구현한 PL/pgSQL 구문에 대한 세부 정보를 확인할 수 있습니다. PL/pgSQL에 대한 자세한 내용은 PostgreSQL 설명서의 [PL/pgSQL - SQL procedural language](https://www.postgresql.org/docs/8.0/plpgsql.html) 섹션을 참조하세요.

**Topics**
+ [PL/pgSQL 참조 규칙](c_PL_reference_conventions.md)
+ [PL/pgSQL의 구조](c_PLpgSQL-structure.md)
+ [지원되는 PL/pgSQL 문](c_PLpgSQL-statements.md)

# PL/pgSQL 참조 규칙
<a name="c_PL_reference_conventions"></a>

이 단원에서는 PL/pgSQL 저장 프로시저 언어의 구문을 작성할 때 사용되는 규칙을 찾을 수 있습니다.

[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ko_kr/redshift/latest/dg/c_PL_reference_conventions.html)

# PL/pgSQL의 구조
<a name="c_PLpgSQL-structure"></a>

PL/pgSQL은 다른 프로시저 언어와 여러 동일한 구문를 가진 프로시저 언어입니다.

**Topics**
+ [차단](#r_PLpgSQL-block)
+ [변수 선언](#r_PLpgSQL-variable-declaration)
+ [별칭 선언](#r_PLpgSQL-alias-declaration)
+ [기본 제공 변수](#r_PLpgSQL-builtin-variables)
+ [레코드 형식](#r_PLpgSQL-record-type)

## 차단
<a name="r_PLpgSQL-block"></a>

PL/pgSQL은 블록 구조 언어입니다. 프로시저의 전체 본문은 블록으로 정의되어 있으며, 변수 선언 및 PL/pgSQL 문이 포함되어 있습니다. 문은 중첩 블록, 즉 하위 블록일 수도 있습니다.

선언 및 문은 세미콜론으로 끝납니다. 세미콜론이 있는 블록 또는 하위 블록에서는 END 키워드를 따릅니다. DECLARE 및 BEGIN 키워드 뒤에는 세미콜론을 사용하지 마십시오.

모든 키워드 및 식별자는 대문자와 소문자 혼합으로 작성할 수 있습니다. 큰 따옴표로 묶이지 않은 한 식별자는 소문자로 암시적으로 변환됩니다.

이중 하이픈(--)은 행의 끝까지 확장되는 주석을 시작합니다. /\$1는 다음 \$1/까지 확장되는 블록 주석을 시작합니다. 블록 주석은 중첩할 수 없습니다. 그러나 이중 하이픈 주석을 블록 주석으로 묶을 수 있고, 이중 하이픈은 블록 주석 구분 기호 /\$1 및 \$1/를 숨길 수 있습니다.

블록의 문 섹션의 모든 문은 하위 블록일 수 있습니다. 하위 블록을 사용하여 논리적 그룹화를 하거나 변수를 작은 문 그룹으로 지역화할 수 있습니다.

```
[ <<label>> ]
[ DECLARE
  declarations ]
BEGIN
  statements
END [ label ];
```

블록 앞에 오는 선언 섹션에 선언된 변수는 블록이 입력될 때마다 기본값으로 초기화됩니다. 다시 말해 함수 호출당 한 번만 초기화되지 않습니다.

다음은 그 한 예입니다.

```
CREATE PROCEDURE update_value() AS $$
DECLARE
  value integer := 20;
BEGIN
  RAISE NOTICE 'Value here is %', value;  -- Value here is 20
  value := 50;
  --
  -- Create a subblock
  --
  DECLARE
    value integer := 80;
  BEGIN
    RAISE NOTICE 'Value here is %', value;  -- Value here is 80
  END;

  RAISE NOTICE 'Value here is %', value;  -- Value here is 50
END;
$$ LANGUAGE plpgsql;
```

레이블을 사용하여 EXIT 문에 사용할 블록을 식별하거나 블록에 선언된 변수의 이름을 한정합니다.

PL/pgSQL의 문을 그룹화하기 위한 BEGIN/END 사용을 트랜잭션 제어를 위한 데이터베이스 명령과 혼동하지 마십시오. PL/pgSQL의 BEGIN 및 END는 그룹화에만 사용되며, 트랜잭션을 시작하거나 종료하지 않습니다.

## 변수 선언
<a name="r_PLpgSQL-variable-declaration"></a>

블록의 DECLARE 섹션에서 루프 변수를 제외하고 블록의 모든 변수를 선언합니다. 변수는 모든 유효한 Amazon Redshift 데이터 형식을 사용할 수 있습니다. 지원되는 데이터 형식은 [데이터 타입](c_Supported_data_types.md) 섹션을 참조하세요.

PL/pgSQL 변수는 Amazon Redshift 지원 데이터 형식과 `RECORD` 및 `refcursor`일 수 있습니다. `RECORD`에 대한 자세한 내용은 [레코드 형식](#r_PLpgSQL-record-type) 섹션을 참조하세요. `refcursor`에 대한 자세한 내용은 [커서](c_PLpgSQL-statements.md#r_PLpgSQL-cursors) 섹션을 참조하세요.

```
DECLARE
name [ CONSTANT ] type [ NOT NULL ] [ { DEFAULT | := } expression ];
```

다음으로 변수 선언 예를 찾을 수 있습니다.

```
customerID integer;
numberofitems numeric(6);
link varchar;
onerow RECORD;
```

정수 범위를 반복하는 FOR 루프의 루프 변수는 자동으로 정수 변수로 선언됩니다.

DEFAULT 절이 제공된 경우 블록이 입력되면 변수에 할당되는 초기 값을 지정합니다. DEFAULT 절이 제공되지 않은 경우 변수는 SQL NULL 상태로 초기화됩니다. CONSTANT 옵션은 변수가 할당되지 못하게 합니다. 따라서 값이 블록 기간 동안 일정하게 유지됩니다. NOT NULL이 지정되지 않은 경우 null 값을 할당하면 실행 시간 오류가 발생합니다. NOT NULL로 선언된 모든 변수는 null이 아닌 기본값이 지정되어야 합니다.

기본값은 블록이 입력될 때마다 평가됩니다. 예를 들어 `timestamp` 유형의 변수에 `now()`를 할당하면 변수가 함수가 미리 컴파일된 시간이 아니라 현재 함수 호출의 시간을 갖게 됩니다.

```
quantity INTEGER DEFAULT 32;
url VARCHAR := 'http://mysite.com';
user_id CONSTANT INTEGER := 10;
```

`refcursor` 데이터 형식은 저장 프로시저 내 커서 변수의 데이터 형식입니다. `refcursor` 값은 저장 프로시저 내에서 반환될 수 있습니다. 자세한 내용은 [저장 프로시저에서 결과 세트 반환](stored-procedure-result-set.md) 단원을 참조하십시오.

## 별칭 선언
<a name="r_PLpgSQL-alias-declaration"></a>

저장 프로시저의 서명에 인수 이름이 생략된 경우, 해당 인수의 별칭을 선언할 수 있습니다.

```
name ALIAS FOR $n;
```

## 기본 제공 변수
<a name="r_PLpgSQL-builtin-variables"></a>

다음 기본 제공 변수가 지원됩니다.
+ FOUND
+ SQLSTATE
+ SQLERRM
+ GET DIAGNOSTICS integer\$1var := ROW\$1COUNT;

FOUND는 부울 형식의 특수 변수입니다. FOUND는 각 프로시저 호출 내에서 false를 시작합니다. FOUND는 다음 형식의 문으로 설정됩니다.
+ SELECT INTO

  행을 반환하면 FOUND를 true로 설정하고, 행을 반환하지 않으면 false로 설정합니다.
+ UPDATE, INSERT, DELETE

  하나 이상의 행이 영향을 받으면 FOUND를 true로 설정하고, 행이 영향을 받지 않으면 false로 설정합니다.
+ FETCH

  행을 반환하면 FOUND를 true로 설정하고, 행을 반환하지 않으면 false로 설정합니다.
+ FOR 문

  FOR 문이 한 번 이상 반복되면 FOUND를 true로 설정하고, 그렇지 않으면 false로 설정합니다. 이는 FOR 문의 세 가지 변형인 정수 FOR 루프, 레코드 세트 FOR 루프, 동적 레코드 세트 FOR 루프 모두에 적용됩니다.

  FOR 루프가 종료되면 FOUND가 설정됩니다. 루프 런타임 내부에서 FOUND는 FOR 문에 의해 수정되지 않습니다. 그러나 루프 본문 내 다른 문의 실행에 의해 변경될 수 있습니다.

다음은 그 한 예입니다.

```
CREATE TABLE employee(empname varchar);
CREATE OR REPLACE PROCEDURE show_found()
AS  $$
DECLARE
  myrec record;
BEGIN
  SELECT INTO myrec * FROM employee WHERE empname = 'John';
  IF NOT FOUND THEN
    RAISE EXCEPTION 'employee John not found';
  END IF;
END;
$$ LANGUAGE plpgsql;
```

예외 핸들러 내의 특수 변수 SQLSTATE에는 발생한 예외에 해당하는 오류 코드가 포함되어 있습니다. 특수 변수 SQLERRM에는 해당 예외와 관련된 오류 메시지가 포함되어 있습니다. 이러한 변수는 예외 핸들러 외부에서 정의되지 않으며 사용하면 오류가 발생합니다.

다음은 그 한 예입니다.

```
CREATE OR REPLACE PROCEDURE sqlstate_sqlerrm() AS
$$
BEGIN
  UPDATE employee SET firstname = 'Adam' WHERE lastname = 'Smith';
  EXECUTE 'select invalid';
  EXCEPTION WHEN OTHERS THEN
  RAISE INFO 'error message SQLERRM %', SQLERRM;
  RAISE INFO 'error message SQLSTATE %', SQLSTATE;
END;
$$ LANGUAGE plpgsql;
```

ROW\$1COUNT는 GET DIAGNOSTICS 명령과 함께 사용됩니다. 이는 SQL 엔진으로 전송된 마지막 SQL 명령에서 처리된 행 수를 보여 줍니다.

다음은 그 한 예입니다.

```
CREATE OR REPLACE PROCEDURE sp_row_count() AS
$$
DECLARE
  integer_var int;
BEGIN
  INSERT INTO tbl_row_count VALUES(1);
  GET DIAGNOSTICS integer_var := ROW_COUNT;
  RAISE INFO 'rows inserted = %', integer_var;
END;
$$ LANGUAGE plpgsql;
```

## 레코드 형식
<a name="r_PLpgSQL-record-type"></a>

RECORD 형식은 데이터 형식이 아니라, 자리 표시자에 불과합니다. 레코드 형식 변수는 SELECT 또는 FOR 명령 중에 할당된 행의 실제 행 구조를 가정합니다. 레코드 변수의 하위 구조는 값이 할당될 때마다 변경될 수 있습니다. 레코드 변수가 처음 할당될 때까지 하위 구조가 없습니다. 그 안에 있는 필드에 액세스하려고 시도하면 실행 시간 오류가 발생합니다.

```
name RECORD;
```

다음은 그 한 예입니다.

```
CREATE TABLE tbl_record(a int, b int);
INSERT INTO tbl_record VALUES(1, 2);
CREATE OR REPLACE PROCEDURE record_example()
LANGUAGE plpgsql
AS $$
DECLARE
  rec RECORD;
BEGIN
  FOR rec IN SELECT a FROM tbl_record
  LOOP
    RAISE INFO 'a = %', rec.a;
  END LOOP;
END;
$$;
```

# 지원되는 PL/pgSQL 문
<a name="c_PLpgSQL-statements"></a>

 PL/pgSQL 문은 루프 및 조건 표현식을 비롯한 프로시저 구문으로 SQL 명령을 보완하여 논리 흐름을 제어합니다. COPY, UNLOAD, INSERT 등의 데이터 조작 언어(DML)와 CREATE TABLE 등의 데이터 정의 언어(DDL)를 포함한 대부분의 SQL 명령을 사용할 수 있습니다. 포괄적인 SQL 명령 목록은 [SQL 명령](c_SQL_commands.md) 섹션을 참조하세요. 또한 Amazon Redshift에서 다음 PL/pgSQL 문이 지원됩니다.

**Topics**
+ [대입](#r_PLpgSQL-assignment)
+ [SELECT INTO](#r_PLpgSQL-select-into)
+ [No-op](#r_PLpgSQL-no-op)
+ [Dynamic SQL](#r_PLpgSQL-dynamic-sql)
+ [반환](#r_PLpgSQL-return)
+ [조건부: IF](#r_PLpgSQL-conditionals-if)
+ [조건부: CASE](#r_PLpgSQL-conditionals-case)
+ [Loops](#r_PLpgSQL-loops)
+ [커서](#r_PLpgSQL-cursors)
+ [RAISE](#r_PLpgSQL-messages-errors)
+ [트랜잭션 제어](#r_PLpgSQL-transaction-control)

## 대입
<a name="r_PLpgSQL-assignment"></a>

대입문은 값을 변수에 할당합니다. 표현식은 단일 값을 반환해야 합니다.

```
identifier := expression;
```

`:=` 대신에 대입에 표준이 아닌 `=`를 사용하는 것도 허용됩니다.

표현식의 데이터 형식이 변수의 데이터 형식과 일치하지 않거나 변수에 크기 또는 정밀도가 있는 경우, 결과 값이 암시적으로 변환됩니다.

다음에 예가 나와 있습니다.

```
customer_number := 20;
tip := subtotal * 0.15;
```

## SELECT INTO
<a name="r_PLpgSQL-select-into"></a>

SELECT INTO 문은 여러 열(단 행은 하나만)의 결과를 레코드 변수 또는 스칼라 변수 목록에 할당합니다.

```
SELECT INTO target select_expressions FROM ...;
```

앞의 구문에서 *target*는 레코드 변수이거나 간단한 변수 및 레코드 필드의 쉼표로 구분된 목록일 수 있습니다. *select\$1expressions* 목록과 명령의 나머지는 정규 SQL에서와 동일합니다.

변수 목록이 *target*로 사용되는 경우, 선택한 값이 대상의 구조와 정확히 일치해야 합니다. 그렇지 않으면 실행 시간 오류가 발생합니다. 레코드 변수가 대상인 경우, 쿼리 결과 열의 행 형식으로 자동 구성됩니다.

INTO 절은 SELECT 문의 거의 모든 곳에서 나타날 수 있습니다. 보통 SELECT 절 바로 뒤나 FROM 절 바로 앞에 나타납니다. 즉, *select\$1expressions* 목록 바로 앞이나 바로 뒤에 나타납니다.

쿼리가 행을 반환하지 않으면 NULL 값이 *target*에 할당됩니다. 쿼리가 행을 여러 개 반환하면 첫 번째 행이 *target*에 할당되고 나머지는 삭제됩니다. 명령문에 ORDER BY가 포함되어 있지 않으면 첫 번째 행은 결정적이지 않습니다.

대입이 행을 하나 이상 반환했는지 여부를 확인하려면 특수 FOUND 변수를 사용합니다.

```
SELECT INTO customer_rec * FROM cust WHERE custname = lname;
IF NOT FOUND THEN
  RAISE EXCEPTION 'employee % not found', lname;
END IF;
```

레코드 결과가 null인지 여부를 테스트하려면 IS NULL 조건부를 사용하면 됩니다. 추가 행이 삭제되었는지 확인할 수 있는 방법은 없습니다. 다음 예제에서는 행이 반환되지 않은 경우를 처리합니다.

```
CREATE OR REPLACE PROCEDURE select_into_null(return_webpage OUT varchar(256))
AS $$
DECLARE
  customer_rec RECORD;
BEGIN
  SELECT INTO customer_rec * FROM users WHERE user_id=3;
  IF customer_rec.webpage IS NULL THEN
    -- user entered no webpage, return "http://"
    return_webpage = 'http://';
  END IF;
END;
$$ LANGUAGE plpgsql;
```

## No-op
<a name="r_PLpgSQL-no-op"></a>

no-op 문(`NULL;`)은 아무 것도 수행하지 않는 자리 표시자 문입니다. no-op 문은 IF-THEN-ELSE 체인의 한 브랜치가 비어 있음을 나타낼 수 있습니다.

```
NULL;
```

## Dynamic SQL
<a name="r_PLpgSQL-dynamic-sql"></a>

PL/pgSQL 저장 프로시저에서 실행될 때마다 다른 테이블 또는 다른 데이터 형식을 포함할 수 있는 동적 명령을 생성하려면 `EXECUTE` 문을 사용합니다.

```
EXECUTE command-string [ INTO target ];
```

위의 *command-string*은 실행할 명령을 포함하는 문자열(텍스트 형식)을 생성하는 표현식입니다. 이 *command-string* 값은 SQL 엔진으로 전송됩니다. PL/pgSQL 변수의 대체는 명령 문자열에서 수행되지 않습니다. 변수 값은 생성될 때 명령 문자열에 삽입되어야 합니다.

**참고**  
동적 SQL 내에서 COMMIT 및 ROLLBACK 문을 사용할 수 없습니다. 저장 프로시저 내의 COMMIT 및 ROLLBACK 문 사용에 대한 자세한 내용은 [트랜잭션 관리](stored-procedure-transaction-management.md)를 참조하십시오.

동적 명령을 작업할 경우 보통 작은따옴표의 이스케이핑을 처리해야 합니다. 달러 인용을 사용하여 함수 본문에서 고정 텍스트를 따옴표로 묶는 것이 좋습니다. 생성된 쿼리에 삽입할 동적 값에는 따옴표가 포함될 수 있으므로 특별한 처리가 필요합니다. 다음 예제에서는 함수의 달러 인용을 전체로 가정하므로, 따옴표가 큰따옴표일 필요가 없습니다.

```
EXECUTE 'UPDATE tbl SET '
  || quote_ident(colname)
  || ' = '
  || quote_literal(newvalue)
  || ' WHERE key = '
  || quote_literal(keyvalue);
```

앞의 예제에서는 함수 `quote_ident(text)` 및 `quote_literal(text)`을 보여 줍니다. 이 예제에서는 열 및 테이블 식별자가 포함된 변수를 `quote_ident` 함수로 전달합니다. 또한 생성된 명령에 리터럴 문자열이 포함된 변수를 `quote_literal` 함수로 전달합니다. 두 함수 모두 각각 큰따옴표 또는 작은따옴표로 묶인 입력 텍스트를 반환하기 위한 적절한 단계를 거치며, 포함된 특수 문자는 적절하게 이스케이프됩니다.

달러 인용은 고정 텍스트 인용 시에만 유용합니다. 앞의 예제를 다음 형식으로 작성하지 마십시오.

```
EXECUTE 'UPDATE tbl SET '
  || quote_ident(colname)
  || ' = $$'
  || newvalue
  || '$$ WHERE key = '
  || quote_literal(keyvalue);
```

`newvalue`의 내용에 \$1\$1가 포함되면 이 예제가 중단되기 때문입니다. 선택할 수 있는 다른 달러 인용 구분 기호에도 동일한 문제가 적용됩니다. 미리 알려지지 않은 텍스트를 안전하게 인용하려면 `quote_literal` 함수를 사용합니다.

## 반환
<a name="r_PLpgSQL-return"></a>

RETURN 문은 저장 프로시저에서 호출자에게 다시 반환합니다.

```
RETURN;
```

다음은 그 한 예입니다.

```
CREATE OR REPLACE PROCEDURE return_example(a int)
AS $$  
BEGIN
  FOR b in 1..10 LOOP
    IF b < a THEN
      RAISE INFO 'b = %', b;
    ELSE
      RETURN;
    END IF;
  END LOOP;
END;
$$ LANGUAGE plpgsql;
```

## 조건부: IF
<a name="r_PLpgSQL-conditionals-if"></a>

IF 조건부 문은 Amazon Redshift가 사용하는 PL/pgSQL 언어에서 다음 형식을 취할 수 있습니다.
+ IF ... THEN

  ```
  IF boolean-expression THEN
    statements
  END IF;
  ```

  다음은 그 한 예입니다.

  ```
  IF v_user_id <> 0 THEN
    UPDATE users SET email = v_email WHERE user_id = v_user_id;
  END IF;
  ```
+ IF ... THEN ... ELSE

  ```
  IF boolean-expression THEN
    statements
  ELSE
    statements
  END IF;
  ```

  다음은 그 한 예입니다.

  ```
  IF parentid IS NULL OR parentid = ''
  THEN
    return_name = fullname;
    RETURN;
  ELSE
    return_name = hp_true_filename(parentid) || '/' || fullname;
    RETURN;
  END IF;
  ```
+ IF ... THEN ... ELSIF ... THEN ... ELSE 

  키워드 ELSIF는 ELSEIF로 표기할 수도 있습니다.

  ```
  IF boolean-expression THEN
    statements
  [ ELSIF boolean-expression THEN
    statements
  [ ELSIF boolean-expression THEN
    statements
      ...] ]
  [ ELSE
    statements ]
  END IF;
  ```

  다음은 그 한 예입니다.

  ```
  IF number = 0 THEN
    result := 'zero';
  ELSIF number > 0 THEN
    result := 'positive';
  ELSIF number < 0 THEN
    result := 'negative';
  ELSE
    -- the only other possibility is that number is null
    result := 'NULL';
  END IF;
  ```

## 조건부: CASE
<a name="r_PLpgSQL-conditionals-case"></a>

CASE 조건부 문은 Amazon Redshift가 사용하는 PL/pgSQL 언어에서 다음 형식을 취할 수 있습니다.
+ 단순 CASE 

  ```
  CASE search-expression
  WHEN expression [, expression [ ... ]] THEN
    statements
  [ WHEN expression [, expression [ ... ]] THEN
    statements
    ... ]
  [ ELSE
    statements ]
  END CASE;
  ```

  단순 CASE 문은 피연산자의 동등성을 기반으로 조건부 실행을 제공합니다.

  *search-expression* 값은 한 번 평가되고 WHEN 절의 각 *expression*과 연속적으로 비교됩니다. 일치하는 항목이 있으면 해당 *statements*가 실행된 후 제어가 END CASE 뒤의 다음 문으로 전달됩니다. 후속 WHEN 표현식은 평가되지 않습니다. 일치하는 항목이 없으면 ELSE *statements*가 실행됩니다. 그러나 ELSE가 없으면 CASE\$1NOT\$1FOUND 예외가 발생합니다.

  다음은 그 한 예입니다.

  ```
  CASE x
  WHEN 1, 2 THEN
    msg := 'one or two';
  ELSE
    msg := 'other value than one or two';
  END CASE;
  ```
+ 검색 CASE 

  ```
  CASE
  WHEN boolean-expression THEN
    statements
  [ WHEN boolean-expression THEN
    statements
    ... ]
  [ ELSE
    statements ]
  END CASE;
  ```

  검색된 형태의 CASE는 부울 표현식의 사실성을 기반으로 조건부 실행을 제공합니다.

  각 WHEN 절의 *boolean-expression*은 true를 생성하는 항목이 발견될 때까지 평가됩니다. 그런 다음 해당 문이 실행되고, 제어가 END CASE 뒤의 다음 문으로 전달됩니다. 후속 WHEN *expressions*는 평가되지 않습니다. true 결과가 없으면 ELSE *statements*가 실행됩니다. 그러나 ELSE가 없으면 CASE\$1NOT\$1FOUND 예외가 발생합니다.

  다음은 그 한 예입니다.

  ```
  CASE
  WHEN x BETWEEN 0 AND 10 THEN
    msg := 'value is between zero and ten';
  WHEN x BETWEEN 11 AND 20 THEN
    msg := 'value is between eleven and twenty';
  END CASE;
  ```

## Loops
<a name="r_PLpgSQL-loops"></a>

loop 문은 Amazon Redshift가 사용하는 PL/pgSQL 언어에서 다음 형식을 취할 수 있습니다.
+ 단순 루프 

  ```
  [<<label>>]
  LOOP
    statements
  END LOOP [ label ];
  ```

  단순 루프는 EXIT 또는 RETURN 문에 의해 종료될 때까지 무기한으로 반복되는 무조건부 루프를 정의합니다. 선택적 레이블은 중첩 루프 내의 EXIT 및 CONTINUE 문에서 EXIT 및 CONTINUE 문이 참조하는 루프를 지정하는 데 사용할 수 있습니다.

  다음은 그 한 예입니다.

  ```
  CREATE OR REPLACE PROCEDURE simple_loop()
  LANGUAGE plpgsql
  AS $$
  BEGIN
    <<simple_while>>
    LOOP
      RAISE INFO 'I am raised once';  
      EXIT simple_while;
      RAISE INFO 'I am not raised';
    END LOOP;
    RAISE INFO 'I am raised once as well';
  END;
  $$;
  ```
+ 종료 루프

  ```
  EXIT [ label ] [ WHEN expression ];
  ```

  *label*이 없는 경우 가장 안쪽의 루프가 종료되고 END LOOP 다음의 문이 다음에 실행됩니다. *label*이 있는 경우 중첩 루프 또는 블록의 현재 또는 일부 외부 수준의 레이블이어야 합니다. 그런 다음 명명된 루프 또는 블록이 종료되고 제어가 해당 루프 또는 블록 END 다음에 있는 문으로 계속됩니다.

  WHEN이 지정된 경우 *expression*이 true인 경우에만 루프 종료가 발생합니다. 그렇지 않은 경우 제어가 EXIT 뒤의 문으로 전달됩니다.

  모든 형식의 루프에서 EXIT를 사용할 수 있으며, 무조건부 루프에서 사용하는 것에 국한되지 않습니다.

  BEGIN 블록과 함께 사용하는 경우, EXIT는 블록 종료 후 제어를 다음 문으로 전달합니다. 이 목적으로 레이블을 사용해야 합니다. 레이블이 지정되지 않은 EXIT는 BEGIN 블록과 일치하는 것으로 간주되지 않습니다.

  다음은 그 한 예입니다.

  ```
  CREATE OR REPLACE PROCEDURE simple_loop_when(x int)
  LANGUAGE plpgsql
  AS $$
  DECLARE i INTEGER := 0;
  BEGIN
    <<simple_loop_when>>
    LOOP
      RAISE INFO 'i %', i;
      i := i + 1;
      EXIT simple_loop_when WHEN (i >= x);
    END LOOP;
  END;
  $$;
  ```
+ 계속 루프 

  ```
  CONTINUE [ label ] [ WHEN expression ];
  ```

  *label*이 제공되지 않은 경우, 실행이 가장 안쪽에 있는 루프의 다음 반복으로 이동합니다. 즉, 루프 본문에 남아 있는 모든 문을 건너뜁니다. 그런 다음 제어는 루프 제어 표현식(있는 경우)으로 돌아가 다른 루프 반복이 필요한지 여부를 결정합니다. *label*이 있는 경우, 실행이 계속되는 루프의 레이블을 지정합니다.

  WHEN이 지정된 경우 *expression*이 true인 경우에만 루프의 다음 반복이 시작됩니다. 그렇지 않은 경우 제어가 CONTINUE 뒤의 문으로 전달됩니다.

  모든 형식의 루프에서 CONTINUE를 사용할 수 있으며, 무조건부 루프에서 사용하는 것에 국한되지 않습니다.

  ```
  CONTINUE mylabel;
  ```
+ WHILE 루프 

  ```
  [<<label>>]
  WHILE expression LOOP
    statements
  END LOOP [ label ];
  ```

  WHILE 문은 *boolean-expression*이 true로 평가되는 한 일련의 문을 반복합니다. 표현식은 루프 본문의 각 항목 바로 앞에서 검사됩니다.

  다음은 그 한 예입니다.

  ```
  WHILE amount_owed > 0 AND gift_certificate_balance > 0 LOOP
    -- some computations here
  END LOOP;
  
  WHILE NOT done LOOP
    -- some computations here
  END LOOP;
  ```
+ FOR 루프(정수 변형) 

  ```
  [<<label>>]
  FOR name IN [ REVERSE ] expression .. expression LOOP
    statements
  END LOOP [ label ];
  ```

  FOR 루프(정수 변형)는 정수 값 범위를 반복하는 루프를 생성합니다. 변수 이름은 정수 유형으로 자동 정의되며 루프 내부에만 있습니다. 변수 이름의 모든 기존 정의는 루프 내에서 무시됩니다. 범위의 하한값과 상한값을 제공하는 두 표현식은 루프를 입력할 때 한 번 평가됩니다. REVERSE를 지정하면 각 반복 후에 단계 값이 추가되는 것이 아니라 빠집니다.

  하한값이 상한값보다 크면(REVERSE의 경우 작으면) 루프 본문이 실행되지 않습니다. 오류는 발생하지 않습니다.

  레이블이 FOR 루프에 연결되어 있는 경우, 해당 레이블을 사용하여 정규화된 이름이 있는 정수 루프 변수를 참조할 수 있습니다.

  다음은 그 한 예입니다.

  ```
  FOR i IN 1..10 LOOP
    -- i will take on the values 1,2,3,4,5,6,7,8,9,10 within the loop
  END LOOP;
  
  FOR i IN REVERSE 10..1 LOOP
    -- i will take on the values 10,9,8,7,6,5,4,3,2,1 within the loop
  END LOOP;
  ```
+ FOR 루프(결과 세트 변형) 

  ```
  [<<label>>]
  FOR target IN query LOOP
    statements
  END LOOP [ label ];
  ```

  *target*는 레코드 변수 또는 스칼라 변수의 쉼표로 구분된 목록입니다. 대상은 쿼리에서 생성된 각 행에 연속적으로 할당되고, 행별로 루프 본문이 실행됩니다.

  FOR 루프(결과 세트 변형)를 통해 저장 프로시저는 쿼리 결과를 반복하고 그에 따라 해당 데이터를 조작할 수 있습니다.

  다음은 그 한 예입니다.

  ```
  CREATE PROCEDURE cs_refresh_reports() AS $$
  DECLARE
    reports RECORD;
  BEGIN
    FOR reports IN SELECT * FROM cs_reports ORDER BY sort_key LOOP
      -- Now "reports" has one record from cs_reports
      EXECUTE 'INSERT INTO ' || quote_ident(reports.report_name) || ' ' || reports.report_query;
    END LOOP;
    RETURN;
  END;
  $$ LANGUAGE plpgsql;
  ```
+ 동적 SQL을 사용하는 FOR 루프

  ```
  [<<label>>]
  FOR record_or_row IN EXECUTE text_expression LOOP 
    statements
  END LOOP;
  ```

  동적 SQL을 사용하는 FOR 루프를 통해 저장 프로시저는 동적 쿼리 결과를 반복하고 그에 따라 해당 데이터를 조작할 수 있습니다.

  다음은 그 한 예입니다.

  ```
  CREATE OR REPLACE PROCEDURE for_loop_dynamic_sql(x int)
  LANGUAGE plpgsql
  AS $$
  DECLARE
    rec RECORD;
    query text;
  BEGIN
    query := 'SELECT * FROM tbl_dynamic_sql LIMIT ' || x;
    FOR rec IN EXECUTE query
    LOOP
      RAISE INFO 'a %', rec.a;
    END LOOP;
  END;
  $$;
  ```

## 커서
<a name="r_PLpgSQL-cursors"></a>

한 번에 전체 쿼리를 실행하는 대신에 커서를 설정할 수 있습니다. *커서*는 쿼리를 캡슐화하고 쿼리 결과를 한 번에 몇 행씩 읽습니다. 이렇게 하는 이유는 결과에 다수의 행이 포함된 경우 메모리 오버런을 방지하기 위함입니다. 또 다른 이유는 호출자가 행을 읽을 수 있도록 저장 프로시저가 생성한 커서 참조를 반환하는 것입니다. 이를 통해 저장 프로시저에서 대량의 행 세트를 효율적으로 반환할 수 있습니다.

NONATOMIC 저장 프로시저에서 커서를 사용하려면 START TRANSACTION...COMMIT 사이에 커서 루프를 배치하세요.

커서를 설정하려면 먼저 커서 변수를 선언합니다. PL/pgSQL의 커서에 대한 모든 액세스는 항상 특수 데이터 형식 `refcursor`인 커서 변수를 통과합니다. `refcursor` 데이터 형식에는 커서 참조가 있습니다.

형식 `refcursor`의 변수로 선언하여 커서 변수를 생성할 수 있습니다. 또는 다음과 같은 커서 선언 구문을 사용할 수 있습니다.

```
name CURSOR [ ( arguments ) ] FOR query ;
```

앞의 예에서 *인수*(지정된 경우)는 *쿼리*에서 파라미터 값으로 대체될 이름을 각각 정의하는 쉼표로 구분된 *이름 데이터 형식* 페어 목록입니다. 이러한 이름을 대체할 실제 값은 커서가 열릴 때 나중에 지정됩니다.

다음에 예가 나와 있습니다.

```
DECLARE
  curs1 refcursor;
  curs2 CURSOR FOR SELECT * FROM tenk1;
  curs3 CURSOR (key integer) IS SELECT * FROM tenk1 WHERE unique1 = key;
```

이러한 세 변수는 모두 데이터 형식이 `refcursor`이지만, 첫 번째 변수를 모든 쿼리에 사용할 수 있습니다. 이와 달리 두 번째 변수에는 완전히 지정된 쿼리가 이미 바인딩되어 있고, 마지막 변수에는 파라미터가 있는 쿼리가 바인딩되어 있습니다. 커서가 열리면 `key` 값은 정수 파라미터 값으로 대체됩니다. 변수 `curs1`은 어떤 특정 쿼리에도 바인딩되어 있지 않으므로 *언바운드*되었다고 합니다.

커서를 사용하여 행을 검색하려면 먼저 커서를 열어야 합니다. PL/pgSQL에는 세 가지 형태의 OPEN 문이 있습니다. 이중 두 개는 바인딩되지 않은 커서 변수를 사용하고 세 번째는 바인딩된 커서 변수를 사용합니다.
+ 선택을 위해 열기: 커서 변수가 열리고 지정된 쿼리가 실행됩니다. 커서는 아직 열 수 없습니다. 또한 바인딩되지 않은 커서(즉, 단순 `refcursor` 변수로)로 선언되어 있어야 합니다. SELECT 쿼리는 PL/pgSQL의 다른 SELECT 문과 동일한 방식으로 취급됩니다.

  ```
  OPEN cursor_name FOR SELECT ...;                     
  ```

  다음은 그 한 예입니다.

  ```
  OPEN curs1 FOR SELECT * FROM foo WHERE key = mykey;    
  ```
+ 실행을 위해 열기: 커서 변수가 열리고 지정된 쿼리가 실행됩니다. 커서는 아직 열 수 없습니다. 또한 바인딩되지 않은 커서(즉, 단순 `refcursor` 변수로)로 선언되어 있어야 합니다. 쿼리가 EXECUTE 명령에서와 동일한 방식으로 문자열 표현식으로 지정됩니다. 쿼리가 실행마다 다를 수 있으므로 이 접근 방식은 유연성을 제공합니다.

  ```
  OPEN cursor_name FOR EXECUTE query_string;
  ```

  다음은 그 한 예입니다.

  ```
  OPEN curs1 FOR EXECUTE 'SELECT * FROM ' || quote_ident($1);
  ```
+ 바인딩된 커서 열기: 이 형태의 OPEN은 쿼리가 선언될 때 쿼리가 바인딩된 커서 변수를 여는 데 사용됩니다. 커서는 아직 열 수 없습니다. 실제 인수 값 표현식의 목록은 커서가 인수를 사용하도록 선언된 경우에만 나타나야 합니다. 이러한 값은 쿼리에서 대체됩니다.

  ```
  OPEN bound_cursor_name [ ( argument_values ) ];
  ```

  다음은 그 한 예입니다.

  ```
  OPEN curs2;
  OPEN curs3(42);
  ```

커서가 열린 후 다음에 설명된 문을 사용하여 작업할 수 있습니다. 이러한 문은 커서을 연 동일한 저장 프로시저에서 발생하지 않아도 됩니다. 저장 프로시저에서 `refcursor` 값을 반환하고 호출자가 커서에서 작업하도록 할 수 있습니다. 모든 포털은 트랜잭션 종료 시 암시적으로 닫힙니다. 따라서 트랜잭션이 종료되기 전까지만 `refcursor` 값을 사용하여 열린 커서를 참조할 수 있습니다.
+ FETCH는 다음 행을 커서에서 대상으로 가져옵니다. 이 대상은 SELECT INTO와 마찬가지로 행 변수, 레코드 변수 또는 간단한 변수의 쉼표로 구분된 목록이 될 수 있습니다. SELECT INTO와 마찬가지로 특수 변수 FOUND를 확인하여 행을 가져왔는지 여부를 알 수 있습니다.

  ```
  FETCH cursor INTO target;
  ```

  다음은 그 한 예입니다.

  ```
  FETCH curs1 INTO rowvar;
  ```
+ CLOSE는 열린 커서의 기본 포털을 닫습니다. 이 문을 사용하여 트랜잭션의 종료 이전에 리소스를 해제할 수 있습니다. 또한 이 문을 사용하여 커서 변수가 다시 열리도록 할 수 있습니다.

  ```
  CLOSE cursor;
  ```

  다음은 그 한 예입니다.

  ```
  CLOSE curs1;
  ```

## RAISE
<a name="r_PLpgSQL-messages-errors"></a>

`RAISE level` 문을 사용하여 메시지를 보고하고 오류를 발생시킵니다.

```
RAISE level 'format' [, variable [, ...]];
```

가능한 수준은 NOTICE, INFO, LOG, WARNING, EXCEPTION입니다. EXCEPTION은 현재 트랜잭션을 정상적으로 취소하는 오류를 발생시킵니다. 다른 수준은 다른 우선 순위 수준의 메시지만 생성합니다.

형식 문자열 내부의 %는 다음 선택적 인수의 문자열 표현으로 대체됩니다. %%를 작성하여 리터럴 %를 내보냅니다. 현재 선택적 인수는 표현식이 아닌 간단한 변수여야 하고, 형식은 간단한 문자열 리터럴이어야 합니다.

다음 예제에서는 `v_job_id` 값이 문자열에서 %를 대체합니다.

```
RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
```

`RAISE` 문을 사용하여 예외 처리 블록에서 포착된 예외를 다시 발생시킵니다. 이 문은 NONATOMIC 모드 저장 프로시저의 예외 처리 블록에서만 유효합니다.

```
RAISE;
```

## 트랜잭션 제어
<a name="r_PLpgSQL-transaction-control"></a>

Amazon Redshift가 사용하는 PL/pgSQL 언어의 트랜잭션 제어 문을 사용할 수 있습니다. 저장 프로시저 내의 COMMIT, ROLLBACK, TRUNCATE 사용에 대한 자세한 내용은 [트랜잭션 관리](stored-procedure-transaction-management.md)를 참조하십시오.

NONATOMIC 모드 저장 프로시저에서는 트랜잭션 블록을 시작하는 데 `START TRANSACTION`을 사용합니다.

```
START TRANSACTION;
```

**참고**  
PL/pgSQL 문 START TRANSACTION은 다음과 같은 점에서 SQL 문 START TRANSACTION과 다릅니다.  
저장 프로시저 내에서 START TRANSACTION은 BEGIN과 동의어가 아닙니다.
PL/pgSQL 문은 선택적 격리 수준 및 액세스 권한 키워드를 지원하지 않습니다.