Greenplum 또는 Postgresql 이용하여 Function 을 개발하다 보면 하나의 Function 을 실행했을때 다중 결과 데이터를 조회하고 싶을때 사용하는 것이 RefCursor 라는 기능이다.
RefCursor 는 꼭 다중 결과 데이터를 조회할때만 사용하는 것이 아니라 동적쿼리를 만들어서 실행할 때도 사용하기도 한다.
return query 를 사용하거나 새로운 DataType을 생성하여 return 형식을 고정하는 Function 은 동적쿼리를 생성하여 실행할 수 가 없기때문에 꼭 RefCursor를 이용한 Function 을 개발하여야 한다.
Postgresql 9.4 - Cursor : https://www.postgresql.org/docs/9.4/plpgsql-cursors.html
0. 환경
- Greenplum 6.12.0
- DBeaver 21.1.0
1. RefCurosr를 이용한 Function 개발
CREATE OR REPLACE FUNCTION public.ref_fun_test()
RETURNS SETOF refcursor
AS
$$
DECLARE
ref1 refcursor := 'refcursor_1';
ref2 refcursor := 'refcursor_2';
ref3 refcursor := 'refcursor_3';
v_sql TEXT;
v_table varchar := 'pg_catalog.pg_tables';
BEGIN
-- 레프커서 1
OPEN ref1 FOR
SELECT 'refcursor_1' AS a
, NO
, current_timestamp(0)
FROM pg_catalog.generate_series(1, 10) NO;
RETURN NEXT ref1;
-- 레프커서 2
OPEN ref2 FOR
SELECT 'refcursor_2' AS a
, NO
FROM pg_catalog.generate_series(1, 5) NO;
RETURN NEXT ref2;
-- 레프커서 3 - pg_catalog.pg_tables 테이블을 동적쿼리(Dynamic SQL)로 실행
v_sql := 'select schemaname, tablename from ' || v_table || ' limit 20';
OPEN ref3 FOR EXECUTE v_sql;
RETURN NEXT ref3;
END
$$ LANGUAGE plpgsql
2. DBeaver 를 이용한 RefCursor 실행방법
개발한 함수(public.ref_fun_test)를 실행하면 등록된 3개의 레프커서 이름을 확인할 수 있다.
FETCH ALL FROM <레프커서명>; 쿼리를 실행해서 해당 커서를 조회할려고 하면 커서가 없다는 SQL Error [34000] 에러가 발생한다.
SQL Error [34000]: ERROR: cursor "refcursor_1" does not exist
레프커서를 실행하려면 SELECT 문과 FETCH ALL FROM 문을 영역선택하여 하나로 쿼리로 실행하면 DBeaver에서 조회가 가능하다.
SELECT * FROM public.ref_fun_test();
FETCH ALL FROM refcursor_1;
FETCH ALL FROM refcursor_2;
FETCH ALL FROM refcursor_3;
데이터 결과 패널의 Result 탭을 선택하여 레프커서1, 레프커서2, 레프커서3의 데이터를 확인할 수 있다.
3. JAVA 로 RefCursor 실행하기
JAVA/Maven 으로 Greenplum RefCursor를 실행하여 결과를 확인해 보도록 하자.
pom.xml 정의
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>wyleedp.greenplum.java.examples</groupId>
<artifactId>greenplum-java-examples</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>greenplum-java-examples</name>
<description>Greenplum 자바 예제들</description>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.23</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
wyleedp.greenplum.java.examples.GreenplumRefcursorExample.java 개발
package wyleedp.greenplum.java.examples;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.commons.lang3.StringUtils;
public class GreenplumRefcursorExample {
private static final String GP_DRIVER_CLASSNAME = "org.postgresql.Driver";
private static final String GP_URL = "jdbc:postgresql://192.168.2.141/postgres"; // 접속IP/DB명
private static final String GP_USERNAME = "gpadmin"; // USERNAME
private static final String GP_PASSWORD = "changeme"; // PASSWORD
public void execution() {
Connection con = null;
Statement st = null;
ResultSet rs = null;
try {
Class.forName(GP_DRIVER_CLASSNAME);
con = DriverManager.getConnection(GP_URL, GP_USERNAME, GP_PASSWORD);
st = con.createStatement();
rs = st.executeQuery("select version()");
// Greenplum Version 정보출력
while(rs.next()) {
System.out.println("Greenplum Version : " + rs.getString(1));
}
// 레프커서 실행
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT * FROM public.ref_fun_test();");
sqlBuilder.append("FETCH ALL FROM refcursor_1;");
sqlBuilder.append("FETCH ALL FROM refcursor_2;");
sqlBuilder.append("FETCH ALL FROM refcursor_3;");
boolean isExecution = st.execute(sqlBuilder.toString());
int sqlIndex = 0;
int columnWidth = 25;
String line = StringUtils.repeat("-", 75);
while(isExecution) {
rs = st.getResultSet();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 레프커서별 컬럼개수를 출력한다.
System.out.println(sqlIndex + "] ColumnCount : " + columnCount);
// 컬럼명을 출력한다.
System.out.println(line);
for(int columnIndex=1; columnIndex<=columnCount; columnIndex++) {
String columnName = metaData.getColumnName(columnIndex);
System.out.print(StringUtils.rightPad(columnName, columnWidth, " "));
}
System.out.println();
System.out.println(line);
// 데이터를 출력한다.
while(rs.next()){
for(int columnIndex=1; columnIndex<=columnCount; columnIndex++) {
String columnTypeName = metaData.getColumnTypeName(columnIndex);
if(StringUtils.equalsIgnoreCase(columnTypeName, "refcursor")) {
// 컬럼유형이 레프커서이면 String 으로 출력한다.
String columnValue = rs.getString(columnIndex);
System.out.print(StringUtils.rightPad(columnValue, columnWidth, " "));
}else {
String columnValue = rs.getObject(columnIndex).toString();
System.out.print(StringUtils.rightPad(columnValue, columnWidth, " "));
}
}
System.out.println();
}
System.out.println();
sqlIndex++;
// 실행할 쿼리가 없으면 while 문을 빠져 나간다.
if(!st.getMoreResults()) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(rs != null) rs.close();
if(st != null) st.close();
if(con != null) con.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
GreenplumRefcursorExample example = new GreenplumRefcursorExample();
example.execution();
}
}
정상적으로 실행되면 Greenplum 버전, 레프커서별로 데이터가 표시된다.
Refcursor Function 존재하지 않으면 "No function matches" 에러메세지가 발생한다.
org.postgresql.util.PSQLException: ERROR: function public.ref_fun_test2() does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 15
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2552)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2284)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:322)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:481)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:401)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:322)
at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:308)
at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:284)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:279)
Github 에도 소스가 업로드되어 있다.
'Database > Greenplum' 카테고리의 다른 글
[Greenplum] DBeaver 접속시 master-only utility mode FATAL 에러발생 (0) | 2021.07.22 |
---|---|
[Greenplum] 6.12.0 버전에서 PANIC 상황을 발생시키는 방법 (0) | 2021.07.15 |
[Greenplum] Function 개발 및 디버깅을 위한 RAISE 로깅 사용방법 (0) | 2021.06.18 |
Greenplum 테이블의 분산키를 변경하는 방법 (0) | 2021.04.20 |
Greenplum ETL도구 소개 (0) | 2021.03.14 |