Этот ответ предназначен для решения проблем, возникающих при работе с курсорами ссылки в качестве параметров вывода. Приведенный ниже код вызывает вашу test_dynamic_sql()
процедуру. В этом Proc мы OPEN
курсор, FETCH
данные, на которые он указывает, и мы НЕ CLOSE
курсор, поскольку мы немедленно снова используем этот курсор вне процедуры test_dynamic_sql()
. ПРИМЕЧАНИЕ - при использовании FETCH
курсор больше не будет предоставлять вам данные и должен быть снова открыт. Поскольку ваш курсор использует динамический SQL, мы должны объявить наш динамический «запрос» в том же месте, в котором мы объявляем остальные наши глобальные переменные.
«Курсоры НЕ предназначены для повторного использования: вы читаете их один раз, продолжаете двигаться вперед и, как вы делаете, вы отбрасываете все ранее отсканированные строки». Этот факт был украден из этой SO Post: Oracle. Повторно использовать курсор в качестве параметра в двух процедурах .
Вне этой процедуры мы сначала должны проверить, была ли успешно инициализирована Курсор, используя оператор IF для проверки, существует ли Курсор: IF (g_rc IS NOT NULL) THEN
.
Полный пример кода ниже:
DECLARE
/* g for Global */
g_status VARCHAR2(5);
g_message VARCHAR2(100);
g_rc SYS_REFCURSOR;
/* Store Dynamic SQL Query */
g_SQL VARCHAR2(200);
/* Bind Variables */
g_jobid NUMBER;
g_last_name VARCHAR2(240);
/* Declare Global Record used to FETCH data into */
g_rec_employee employees%ROWTYPE;
PROCEDURE test_dynamic_sql(pv_last_name VARCHAR2,
p_rc OUT SYS_REFCURSOR,
pv_jobid VARCHAR2,
pv_status OUT VARCHAR2,
pv_message OUT VARCHAR2,
pv_q OUT VARCHAR2)
AS
/* Declare Record used to FETCH data into */
rec_employee employees%ROWTYPE;
/* Bind Variables */
jobid NUMBER := to_number(pv_jobid);
last_name VARCHAR2(240) := pv_last_name;
BEGIN
/* Reset/Initialize Cursor Output Variable */
p_rc := NULL;
/* Dynamic SQL statement with placeholder: */
pv_q := 'SELECT * FROM employees WHERE 1=1';
IF last_name IS NOT NULL
THEN pv_q := pv_q || ' AND (lastname = :LAST_NAME)';
ELSE pv_q := pv_q || ' AND (1=1 or :LAST_NAME is null)';
END IF;
IF jobid IS NOT NULL
THEN pv_q := pv_q || ' AND (ID = :JOBID)';
ELSE pv_q := pv_q || ' AND (1=1 or :JOBID is null)';
END IF;
/* Open cursor & specify bind argument in USING clause: */
OPEN p_rc FOR pv_q USING last_name, jobid;
LOOP
/* In order to work with any Data that a Cursor 'points' to we must FETCH the data. This allows us to use %ROWCOUNT and %NOTFOUND */
FETCH p_rc INTO rec_employee;
EXIT WHEN p_rc%NOTFOUND;
END LOOP;
IF p_rc%ROWCOUNT = 0 THEN
pv_status := 'NR';
pv_message := 'Not Found';
--EXIT;
ELSIF p_rc%ROWCOUNT = 1 THEN
pv_status := 'S';
pv_message := 'Found';
--EXIT;
ELSE
pv_status := 'MU';
pv_message := 'Multiple Records';
END IF;
--dbms_output.put_line('Final Count: ' || p_rc%ROWCOUNT);
/* Close Cursor - We don't close the Cursor here as we want to use this cursor as an OUT Parameter outside of this Proc */
CLOSE p_rc;
EXCEPTION
WHEN OTHERS THEN
pv_status :='E';
pv_message := sqlcode||'-->'||sqlerrm;
dbms_output.put_line('STATUS: ' || pv_status);
dbms_output.put_line('MESSAGE: ' || pv_message);
CLOSE p_rc;
END test_dynamic_sql;
BEGIN
g_jobid := null;
g_last_name := 'Loch';
test_dynamic_sql(pv_last_name => g_last_name,
p_rc => g_rc, /* Out Parameter */
pv_jobid => g_jobid,
pv_status => g_status, /* Out Parameter */
pv_message => g_message, /* Out Parameter */
pv_q => g_SQL /* Out Parameter */
);
/* Output OUT Variables aka Provide Output to User */
dbms_output.put_line('STATUS: ' || g_status);
dbms_output.put_line('MESSAGE: ' || g_message);
IF (g_rc IS NOT NULL) THEN
dbms_output.put_line('We have something here. Fetching Data Now:');
OPEN g_rc FOR g_sql USING g_last_name, g_jobid;
LOOP
FETCH g_rc INTO g_rec_employee;
EXIT WHEN g_rc%NOTFOUND;
/* Print the Job ID just to show it is working */
dbms_output.put_line('Job_ID: ' || g_rec_employee.id || ' is the id');
END LOOP;
dbms_output.put_line('Total of: '|| g_rc%ROWCOUNT || ' records Fetched.');
CLOSE g_rc;
ELSE
dbms_output.put_line('null');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error '||TO_CHAR(SQLCODE)||': '||SQLERRM);
CLOSE g_rc;
END;
Сказанное выше работает с теми же данными таблицы сотрудников, что и в моем первом ответе на этот вопрос SO.