使用記錄和游標

此頁面上的命令檔內容僅適用於瀏覽用途,而且無法利用任何方式來更改內容。

您可以將資料值儲存在記錄中,並使用游標作為結果集和相關處理資訊的指標。

另請參閱:Oracle Database PL/SQL Language Reference,瞭解有關記錄的詳細資訊

關於記錄

記錄是一種 PL/SQL 複合變數,可以儲存不同類型的資料值。您可以將「內部」元件 ( 欄位 ) 視為純量變數。您可以傳送整個記錄作為子程式參數。在保存來自表格資料列或表格資料列之特定資料欄時,記錄是很好用的,

記錄是可儲存不同類型之資料值的 PL/SQL 複合變數,類似於 C、C++ 或 Java 中的結構類型。記錄的內部元件稱為欄位。要存取記錄欄位,您可使用 點標記:record_name.field_name。

您可以視記錄欄位為純量變數,您也可以傳送整個記錄作為子程式參數。

在保存來自表格資料列或表格資料列之特定資料欄時,記錄是很好用的,因為每個記錄欄位都對應一個表格資料欄。

建立記錄有三種方式:

另請參閱:

教學課程:宣告 RECORD 類型

下列步驟顯示如何使用 SQL Developer 工具「編輯」來宣告 RECORD 類型 sal_info,該類型的欄位可保存員工的薪資資訊—職務 ID、該職務 ID 的薪資下限與上限、目前薪金以及建議升薪。

宣告 RECORD 類型 sal_info 的步驟:

  1. 在「連線」框架中,展開 hr_conn

    在 hr_conn 圖示下,會顯示綱要物件類型的清單。

  2. 展開套裝程式

    將會顯示套裝程式清單。

  3. EMP_EVAL 上按一下滑鼠右鍵。

    將會顯示選項清單。

  4. 選取編輯

    EMP_EVAL 窗格會開啟,顯示建立套裝程式的 CREATE PACKAGE 敘述句:

    CREATE OR REPLACE PACKAGE EMP_EVAL AS
       
    PROCEDURE eval_department(dept_id IN NUMBER);
    FUNCTION calculate_score(evaluation_id IN NUMBER
                           , performance_id IN NUMBER)
                             RETURN NUMBER;
       
    END EMP_EVAL;
    
  5. 在 EMP_EVAL 窗格中的 END EMP_EVAL 之前,立即新增此程式碼:

     TYPE sal_info IS RECORD
       ( j_id     jobs.job_id%type
       , sal_min  jobs.min_salary%type
       , sal_max  jobs.max_salary%type
       , sal      employees.salary%type
       , sal_raise NUMBER(3,3) );
    

    系統會以斜體字型顯示窗格的標題,表示尚未將這些變更儲存到資料庫。

  6. 選取編譯圖示。

    變更過的套裝程式規格便會進行編譯並儲存到資料庫。EMP_EVAL 窗格的標題已不再是斜體字型。

    現在您可以宣告 sal_info 類型的記錄,如教學課程:使用記錄參數建立和呼叫子程式中。

教學課程:使用記錄參數建立及呼叫子程式

下列步驟顯示如何使用 SQL Developer 工具「編輯」來建立和呼叫具有記錄類型 sal_info 參數的子程式。

記錄類型 sal_info 是在「教學課程:宣告記錄類型」中建立的。

本教學課程示範如何使用 SQL Developer 工具「編輯」來完成下列作業:

因為 EVAL_FREQUENCY 會呼叫 SALARY_SCHEDULE,所以 SALARY_SCHEDULE 的宣告必須在宣告 EVAL_FREQUENCY 之前 (否則,配套將不會編譯)。不過,SALARY_SCHEDULE 的定義可以在套裝程式主體中的任何位置。

建立 SALARY_SCHEDULE 與變更 EVAL_FREQUENCY 的步驟:

  1. 在「連線」框架中,展開 hr_conn

  2. 在綱要物件類型清單中,展開套裝程式

  3. 在薪資配套清單中,展開 EMP_EVAL

  4. 在選項清單中,以滑鼠右鍵按一下 EMP_EVAL 主體

  5. 在選項清單中,選取編輯

    便會顯示「EMP_EVAL 主體」窗格,其中顯示封裝主體程式碼。

  6. 在 EMP_EVAL 主體窗格中的 END EMP_EVAL 前面,立即新增下列 SALARY_SCHEDULE 程序定義:

     PROCEDURE salary_schedule (emp IN sal_info) AS
       accumulating_sal  NUMBER;
     BEGIN
       DBMS_OUTPUT.PUT_LINE('If salary ' || emp.sal ||
         ' increases by ' || ROUND((emp.sal_raise * 100),0) ||
    
         '% each year, it will be:');
    
       accumulating_sal := emp.sal;
    
       WHILE accumulating_sal <= emp.sal_max LOOP
         accumulating_sal := accumulating_sal * (1 + emp.sal_raise);
         DBMS_OUTPUT.PUT_LINE(ROUND(accumulating_sal,2) ||', ');
    
       END LOOP;
     END salary_schedule;
    

    系統會以斜體字型顯示窗格的標題,表示尚未將這些變更儲存到資料庫。

  7. 在 EMP_EVAL 主體窗格中,於下列位置輸入 eval_frequency 函數與 salary_schedule 及 add_eval 程序:

     CREATE OR REPLACE
     PACKAGE BODY EMP_EVAL AS
    
     FUNCTION eval_frequency (emp_id EMPLOYEES.EMPLOYEE_ID%TYPE)
    
       RETURN PLS_INTEGER;
    
     PROCEDURE salary_schedule(emp IN sal_info);
    
     PROCEDURE add_eval(employee_id IN employees.employee_id%type, today IN DATE);
    
     PROCEDURE eval_department (dept_id IN NUMBER) AS
    
  8. 編輯 EVAL_FREQUENCY 函數,進行下列變更:

     FUNCTION eval_frequency (emp_id EMPLOYEES.EMPLOYEE_ID%TYPE)
       RETURN PLS_INTEGER
     AS
       h_date     EMPLOYEES.HIRE_DATE%TYPE;
       today      EMPLOYEES.HIRE_DATE%TYPE;
       eval_freq  PLS_INTEGER;
    
       emp_sal    SAL_INFO;  -- replaces sal, sal_raise, and sal_max
    
     BEGIN
       SELECT SYSDATE INTO today FROM DUAL;
    
       SELECT HIRE_DATE INTO h_date
       FROM EMPLOYEES
       WHERE EMPLOYEE_ID = eval_frequency.emp_id;
    
       IF ((h_date + (INTERVAL '120' MONTH)) < today) THEN
         eval_freq := 1;
    
         /* populate emp_sal */
    
         SELECT j.JOB_ID, j.MIN_SALARY, j.MAX_SALARY, e.SALARY
    
         INTO emp_sal.j_id, emp_sal.sal_min, emp_sal.sal_max, emp_sal.sal
    
         FROM EMPLOYEES e, JOBS j
    
         WHERE e.EMPLOYEE_ID = eval_frequency.emp_id
    
         AND j.JOB_ID = eval_frequency.emp_id;
    
         emp_sal.sal_raise := 0;  -- default
    
         CASE emp_sal.j_id
           WHEN 'PU_CLERK' THEN emp_sal.sal_raise := 0.08;
           WHEN 'SH_CLERK' THEN emp_sal.sal_raise := 0.07;
           WHEN 'ST_CLERK' THEN emp_sal.sal_raise := 0.06;
           WHEN 'HR_REP' THEN emp_sal.sal_raise := 0.05;
           WHEN 'PR_REP' THEN emp_sal.sal_raise := 0.05;
           WHEN 'MK_REP' THEN emp_sal.sal_raise := 0.04;
           ELSE NULL;
         END CASE;
    
         IF (emp_sal.sal_raise != 0) THEN
    
           salary_schedule(emp_sal);
         END IF;
       ELSE
         eval_freq := 2;
       END IF;
    
       RETURN eval_freq;
     END eval_frequency;
    
  9. 選取編譯

關於游標

當 Oracle Database 執行 SQL 敘述句時,會將結果集與處理資訊儲存在未命名的專用 SQL 區域中。此未命名區域的指標 (稱為游標 ),可讓您一次擷取一列的結果集。游標屬性會傳回游標狀態的相關資訊。

每當您執行 SQL DML 敘述句或 PL/SQL SELECT INTO 敘述句時,PL/SQL 便會開啟隱含游標。您可以從游標的屬性取得它的相關資訊,但您無法控制它。執行敘述句之後,資料庫會關閉游標;不過,在執行其他 DML 或 SELECT INTO 敘述句之前,它的屬性值仍然可以使用。

PL/SQL 也可讓您宣告游標。宣告游標具有名稱,且與查詢 (SQL SELECT 陳述式) 相關聯,通常傳回多個列。宣告游標之後,您必須以隱含或明確的方式處理游標。若要以隱含方式處理游標,請使用游標 FOR LOOP。語法如下:

FOR record_name IN cursor_name LOOP
  statement
  [ statement ]...
END LOOP;

若要明確地處理游標,請開啟游標 (使用 OPEN 陳述式),從結果集一次擷取一個或大量擷取列 (使用 FETCH 陳述式),然後關閉游標 (使用 CLOSE 陳述式)。關閉游標後,您就無法擷取結果集的記錄,也不能查看游標屬性值。

隱含游標屬性值的語法為 SQL%attribute (例如,SQL%FOUND)。SQL% 屬性一律參照最近執行的 DML 或 SELECT INTO 敘述句。

宣告之游標屬性值的語法為 cursor_name%attribute (例如 c1%FOUND)。表格 1 列出游標屬性及其可傳回的值。(隱含游標具有一些超出本書說明範圍的屬性。)

表格 1 游標屬性值

屬性 宣告游標的值 隱含游標的值
% 找到

如果游標為開啟 (註腳 1),但未嘗試擷取,則為 NULL

如果最近的擷取傳回資料列,則為 TRUE

如果最近的擷取未傳回資料列,則為 FALSE

如果還沒有執行任何 DML 或 SELECT INTO 敘述句,則為 NULL

如果最近的 DML 或 SELECT INTO 敘述句傳回一個資料列 TRUE

如果最新的 DML 或 SELECT INTO 敘述句未傳回資料列,則為 FALSE

% 找不到

如果游標為開啟 (註腳 1),但未嘗試擷取,則為 NULL

如果最近的擷取傳回資料列,則為 FALSE

如果最近的擷取未傳回資料列,則為 TRUE

如果還沒有執行任何 DML 或 SELECT INTO 敘述句,則為 NULL

如果最近的 DML 或 SELECT INTO 敘述句傳回一個資料列,FALSE

如果最近的 DML 或 SELECT INTO 敘述句未傳回資料列,則為 TRUE

% 列數 如果游標為開啟 (註腳 1),則數字大於或等於零。 如果沒有執行 DML 或 SELECT INTO 敘述句,則為空值;否則,會是大於或等於零的數字。
% 開啟 如果游標已開啟,則為 TRUE;如果未開啟,則為 FALSE。 一律為 FALSE。

註腳 1:如果游標未開啟,則屬性會產生預先定義的例外 INVALID_CURSOR。

另請參閱:

使用已宣告的游標一次擷取結果集資料列一

您可以使用宣告游標,一次擷取一個結果集資料列。

下列程序以最簡單的格式使用每個必要的陳述式,但提供其完整語法的參照。

使用宣告游標一次擷取一個結果集資料列的步驟:

  1. 在宣告的部分:

    1. 宣告游標:

       CURSOR cursor_name IS query;
      

      如需完整的宣告游標宣告語法,請參閱 Oracle Database PL/SQL Language Reference

    2. 宣告保留游標傳回之資料列的記錄:

       record_name cursor_name%ROWTYPE;
      

      如需完整的 %ROWTYPE 語法,請參閱 Oracle Database PL/SQL Language Reference

  2. 在可執行的部分:

    1. 開啟游標:

       OPEN cursor_name;
      

      有關完整的 OPEN 陳述式語法,請參閱 Oracle Database PL/SQL Language Reference

    2. 使用語法類似於下列代碼的 LOOP 陳述式,一次從游標 (結果集的列) 提取一個資料列:

       LOOP
         FETCH cursor_name INTO record_name;
         EXIT WHEN cursor_name%NOTFOUND;
      
         -- Process row that is in record_name:
      
         statement;
         [ statement; ]...
       END LOOP;
      

      有關完整的 FETCH 陳述式語法,請參閱 Oracle Database PL/SQL Language Reference

    3. 關閉游標:

       CLOSE cursor_name;
      

如需完整的 CLOSE 陳述式語法,請參閱 Oracle Database PL/SQL Language Reference

教學課程:使用宣告的游標一次擷取結果集資料列 1

下列步驟顯示如何實作程序 EMP_EVAL.EVAL_DEPARTMENT,該程序使用宣告的游標 emp_cursor。

實施 EMP_EVAL.EVAL_DEPARTMENT 程序的步驟:

  1. 在 EMP_EVAL 套件規格中,變更 EVAL_DEPARTMENT 程序的宣告,如以下所示:

     PROCEDURE eval_department(dept_id IN employees.department_id%TYPE);
    
  2. 在 EMP_EVAL 套件主體中,變更 EVAL_DEPARTMENT 程序的定義,如下列範例所示:

     PROCEDURE eval_department (dept_id IN employees.department_id%TYPE)
     AS
    
       CURSOR emp_cursor IS
    
         SELECT * FROM EMPLOYEES
    
         WHERE DEPARTMENT_ID = eval_department.dept_id;
    
       emp_record  EMPLOYEES%ROWTYPE;  -- for row returned by cursor
    
       all_evals   BOOLEAN;  -- true if all employees in dept need evaluations
    
       today       DATE;
    
     BEGIN
    
       today := SYSDATE;
    
       IF (EXTRACT(MONTH FROM today) < 6) THEN
    
         all_evals := FALSE; -- only new employees need evaluations
    
       ELSE
    
         all_evals := TRUE;  -- all employees need evaluations
    
       END IF;
    
       OPEN emp_cursor;
    
       DBMS_OUTPUT.PUT_LINE (
    
         'Determining evaluations necessary in department # ' ||
    
         dept_id );
    
       LOOP
    
         FETCH emp_cursor INTO emp_record;
    
         EXIT WHEN emp_cursor%NOTFOUND;
    
         IF all_evals THEN
    
           add_eval(emp_record.employee_id, today);
    
         ELSIF (eval_frequency(emp_record.employee_id) = 2) THEN
    
           add_eval(emp_record.employee_id, today);
    
         END IF;
    
       END LOOP;
    
       DBMS_OUTPUT.PUT_LINE('Processed ' || emp_cursor%ROWCOUNT || ' records.');
    
       CLOSE emp_cursor;
     END eval_department;
    

    (如需變更套件主體的逐步範例,請參閱教學課程:在子程式中宣告變數與常數 )。

  3. 編譯 EMP_EVAL 套件規格。

  4. 編譯 EMP_EVAL 套件本文。

關於游標變數

游標變數就像是一個游標,它不僅限於一個查詢。您可以為某個查詢開啟游標,處理結果集,然後讓游標變數供其他查詢使用。游標變數對於在子程式之間傳遞查詢結果很有用。

如需有關游標的資訊,請參閱關於游標

若要宣告游標變數,您宣告了 REF CURSOR 類型,然後宣告該類型的變數 (因此,游標變數通常稱為 REF CURSOR)。REF CURSOR 類型可以是強式或弱型。

強式 REF CURSOR 類型會指定傳回類型,這是其游標變數的 RECORD 類型。PL/SQL 編譯器不允許您將這些強式輸入游標變數用於傳回不是傳回類型的資料列的查詢。強硬的 REF CURSOR 類型較不容易出錯,但較弱者更有彈性。

弱式 REF CURSOR 類型不會指定傳回類型。PL/SQL 編譯器可接受任何查詢中的弱式游標變數。弱 REF CURSOR 類型可互換;因此,您可以改用預先定義的弱游標類型 SYS_REFCURSOR,而不需要建立弱勢 REF CURSOR 類型。

宣告游標變數之後,您必須為特定查詢 (使用 OPEN FOR 陳述式) 開啟游標變數,一次從結果集 (使用 FETCH 陳述式) 擷取一個資料列,然後關閉游標 (使用 CLOSE 陳述式) 或開啟另一個特定查詢 (使用 OPEN FOR 陳述式) 的資料列。若為其他查詢開啟游標變數,等於為前一個查詢關閉游標變數。如果關閉特定查詢的游標變數,您就無法擷取該查詢之結果集的記錄,也不能查看該查詢的游標屬性值。

另請參閱:

使用游標變數一次擷取一個結果集資料列

您可以使用游標變數,一次擷取一個結果集資料列。

下列程序以最簡單的形式使用每個必要的陳述式,但提供其完整語法的參照。

使用游標變數一次擷取一個結果集資料列的步驟:

  1. 在宣告的部分:

    1. 宣告 REF CURSOR 類型:

       TYPE cursor_type IS REF CURSOR [ RETURN return_type ];
      

      如需完整的 REF CURSOR 類型宣告語法,請參閱 Oracle Database PL/SQL Language Reference

    2. 宣告該類型的游標變數:

       cursor_variable cursor_type;
      

      如需完整的游標變數宣告語法,請參閱 Oracle Database PL/SQL Language Reference

    3. 宣告保留游標傳回之資料列的記錄:

       record_name return_type;
      

      如需有關記錄宣告語法的完整資訊,請參閱 Oracle Database PL/SQL Language Reference

  2. 在可執行的部分:

    1. 為特定查詢開啟游標變數:

       OPEN cursor_variable FOR query;
      

      如需 OPEN FOR 陳述式語法的完整資訊,請參閱 Oracle Database PL/SQL Language Reference

    2. 使用與下列語法相似的 LOOP 敘述句,一次從游標變數 (結果集中的資料列) 擷取資料列:

       LOOP
         FETCH cursor_variable INTO record_name;
         EXIT WHEN cursor_variable%NOTFOUND;
      
         -- Process row that is in record_name:
      
         statement;
         [ statement; ]...
       END LOOP;
      

      如需 FETCH 陳述式語法的完整資訊,請參閱 Oracle Database PL/SQL Language Reference

    3. 關閉游標變數:

       CLOSE cursor_variable;
      

      或者,您可以為其他查詢開啟游標變數,這等於為目前查詢關閉游標變數。

      如需有關 CLOSE 敘述句語法的完整資訊,請參閱 Oracle Database PL/SQL Language Reference

教學課程:使用游標變數一次擷取一個結果集資料列

下列步驟顯示如何變更 EMP_EVAL.EVAL_DEPARTMENT 程序,使它使用游標變數,而非宣告的游標 (可使它處理多個部門),以及如何使 EMP_EVAL.EVAL_DEPARTMENT 和 EMP_EVAL.ADD_EVAL 更有效率。

本教學課程如何讓 EMP_EVAL.EVAL_DEPARTMENT 與 EMP_EVAL.ADD_EVAL 更有效率:不用將記錄的一個欄位傳送至 ADD_EVAL,而讓 ADD_EVAL 使用三個查詢來擷取相同記錄的三個其他欄位,EVAL_DEPARTMENT 會將整個記錄傳送至 ADD_EVAL,ADD_EVAL 會使用點標記來存取其他三個欄位的值。

變更 EMP_EVAL.EVAL_DEPARTMENT 程序以使用游標變數的步驟:

  1. 在 EMP_EVAL 套件規格中,新增程序宣告與 REF CURSOR 類型定義,如下列範例所示:

     CREATE OR REPLACE
     PACKAGE emp_eval AS
    
       PROCEDURE eval_department (dept_id IN employees.department_id%TYPE);
    
       PROCEDURE eval_everyone;
    
       FUNCTION calculate_score(eval_id IN scores.evaluation_id%TYPE
                             , perf_id IN scores.performance_id%TYPE)
                               RETURN NUMBER;
       TYPE SAL_INFO IS RECORD
           ( j_id jobs.job_id%type
           , sal_min jobs.min_salary%type
           , sal_max jobs.max_salary%type
           , salary employees.salary%type
           , sal_raise NUMBER(3,3));
    
       TYPE emp_refcursor_type IS REF CURSOR RETURN employees%ROWTYPE;
     END emp_eval;
    
  2. 在 EMP_EVAL 套件本文中,為程序 EVAL_LOOP_CONTROL 新增正向宣告,並變更程序 ADD_EVAL 的聲明,如下所示:

     CREATE OR REPLACE
     PACKAGE BODY EMP_EVAL AS
    
       FUNCTION eval_frequency (emp_id IN EMPLOYEES.EMPLOYEE_ID%TYPE)
         RETURN PLS_INTEGER;
    
       PROCEDURE salary_schedule(emp IN sal_info);
    
       PROCEDURE add_eval(emp_record IN EMPLOYEES%ROWTYPE, today IN DATE);
    
       PROCEDURE eval_loop_control(emp_cursor IN emp_refcursor_type);
     ...
    

    (如需變更套件主體的逐步範例,請參閱教學課程:在子程式中宣告變數與常數 )。

  3. 將 EVAL_DEPARTMENT 程序變更成根據部門擷取三個個別的結果集,然後呼叫 EVAL_LOOP_CONTROL 程序,如下列範例所示:

     PROCEDURE eval_department(dept_id IN employees.department_id%TYPE) AS
    
       emp_cursor    emp_refcursor_type;
    
       current_dept  departments.department_id%TYPE;
    
     BEGIN
    
       current_dept := dept_id;
    
       FOR loop_c IN 1..3 LOOP
    
         OPEN emp_cursor FOR
    
           SELECT *
    
           FROM employees
    
           WHERE current_dept = eval_department.dept_id;
    
         DBMS_OUTPUT.PUT_LINE
           ('Determining necessary evaluations in department #' ||
    
           current_dept);
    
         eval_loop_control(emp_cursor);
    
         DBMS_OUTPUT.PUT_LINE
           ('Processed ' || emp_cursor%ROWCOUNT || ' records.');
    
         CLOSE emp_cursor;
    
         current_dept := current_dept + 10;
    
       END LOOP;
     END eval_department;
    
  4. 如下所示變更 ADD_EVAL 程序:

     PROCEDURE add_eval(emp_record IN employees%ROWTYPE, today IN DATE)
     AS
    
     -- (Delete local variables)
     BEGIN
       INSERT INTO EVALUATIONS (
         evaluation_id,
         employee_id,
         evaluation_date,
         job_id,
         manager_id,
         department_id,
         total_score
       )
       VALUES (
         evaluations_sequence.NEXTVAL,   -- evaluation_id
    
         emp_record.employee_id,    -- employee_id
         today,                     -- evaluation_date
    
         emp_record.job_id,         -- job_id
    
         emp_record.manager_id,     -- manager_id
    
         emp_record.department_id,  -- department_id
         0                           -- total_score
     );
     END add_eval;
    
  5. END EMP_EVAL 之前,請新增下列程序,此程序會從結果集擷取個別記錄並加以處理:

     PROCEDURE eval_loop_control (emp_cursor IN emp_refcursor_type) AS
       emp_record      EMPLOYEES%ROWTYPE;
       all_evals       BOOLEAN;
       today           DATE;
     BEGIN
       today := SYSDATE;
    
       IF (EXTRACT(MONTH FROM today) < 6) THEN
    
         all_evals := FALSE;
       ELSE
         all_evals := TRUE;
       END IF;
    
       LOOP
         FETCH emp_cursor INTO emp_record;
         EXIT WHEN emp_cursor%NOTFOUND;
    
         IF all_evals THEN
           add_eval(emp_record, today);
         ELSIF (eval_frequency(emp_record.employee_id) = 2) THEN
    
           add_eval(emp_record, today);
         END IF;
       END LOOP;
     END eval_loop_control;
    
  6. END EMP_EVAL 之前,請新增以下程序,此程序會擷取包含公司所有員工的結果集:

     PROCEDURE eval_everyone AS
       emp_cursor emp_refcursor_type;
     BEGIN
       OPEN emp_cursor FOR SELECT * FROM employees;
       DBMS_OUTPUT.PUT_LINE('Determining number of necessary evaluations.');
       eval_loop_control(emp_cursor);
       DBMS_OUTPUT.PUT_LINE('Processed ' || emp_cursor%ROWCOUNT || ' records.');
       CLOSE emp_cursor;
     END eval_everyone;
    
  7. 編譯 EMP_EVAL 套件規格。

  8. 編譯 EMP_EVAL 套裝程式主體。