美文网首页
PL/SQL programing 第六版学习笔记-5

PL/SQL programing 第六版学习笔记-5

作者: e652d1fb12eb | 来源:发表于2020-12-19 16:15 被阅读0次

    CHAPTER 11 Records

    Suppose that I have defined a table to keep track of my
    favorite books:

    CREATE TABLE books (
    book_id INTEGER,
    isbn VARCHAR2(13),
    title VARCHAR2(200),
    summary VARCHAR2(2000),
    author VARCHAR2(200),
    date_published DATE,
    page_count NUMBER
    );
    

    I can then easily create a record based on this table, populate it with a query from the
    database, and then access the individual columns through the record’s fields:

    DECLARE
    my_book books%ROWTYPE;
    BEGIN
    SELECT *
    INTO my_book
    FROM books
    WHERE title = 'Oracle PL/SQL Programming, 6th Edition';
    IF my_book.author LIKE '%Feuerstein%'
    THEN
    DBMS_OUTPUT.put_line ('Our newest ISBN is ' || my_book.isbn);
    END IF;
    END;
    

    I can also define my own record type and use that as the basis for declaring records.
    Suppose, for example, that I want to work only with the author and title of a book. Rather than use %ROWTYPE to declare my record, I will instead create a record type:

    DECLARE
    TYPE author_title_rt IS RECORD (
    author books.author%TYPE
    ,title books.title%TYPE
    );
    l_book_info author_title_rt;
    BEGIN
    SELECT author, title INTO l_book_info
    FROM books WHERE isbn = '978-1-449-32445-2';
    

    Declaring Records

    • Table-based record
    DECLARE
    one_book books%ROWTYPE;
    
    • Cursor-based record
    DECLARE
    CURSOR my_books_cur IS
      SELECT * FROM books
      WHERE author LIKE '%FEUERSTEIN%';
    one_SF_book my_books_cur%ROWTYPE;
    
    DECLARE
    TYPE book_info_rt IS RECORD (
    author books.author%TYPE,
    category VARCHAR2(100),
    total_page_count POSITIVE);
    steven_as_author book_info_rt;
    

    Here is an example of the creation of a record based on a cursor variable:

    DECLARE
    TYPE book_rc IS REF CURSOR RETURN books%ROWTYPE;
    book_cv book_rc;
    one_book book_cv%ROWTYPE;
    BEGIN
    ...
    

    The other way to declare and use a record is to do so implicitly, with a cursor FOR loop.
    In the following block, the book_rec record is not defined in the declaration section;
    PL/SQL automatically declares it for me with the %ROWTYPE attribute against the
    loop’s query:

    BEGIN
    FOR book_rec IN (SELECT * FROM books)
    LOOP
    calculate_total_sales (book_rec);
    END LOOP;
    END;
    
    • Programmer-Defined Records
    • Declaring programmer-defined record TYPEs
      The general syntax of the record TYPE definition is:
    TYPE type_name IS RECORD
    (field_name1 datatype1 [[NOT NULL]:=|DEFAULT default_value],
    field_name2 datatype2 [[NOT NULL]:=|DEFAULT default_value],
    ...
    field_nameN datatypeN [[NOT NULL]:=|DEFAULT default_value]
    );
    

    Here is an example of a record TYPE statement:

    TYPE company_rectype IS RECORD (
    comp# company.company_id%TYPE
    , list_of_names DBMS_SQL.VARCHAR2S
    , dataset SYS_REFCURSOR
    );
    

    To build a customer sales record, for example, I first define a record type called customer_sales_rectype, as follows:

    PACKAGE customer_sales_pkg
    IS
    TYPE customer_sales_rectype IS RECORD
    (customer_id customer.customer_id%TYPE,
    customer_name customer.name%TYPE,
    total_sales NUMBER (15,2)
    );
    

    I can then use this new record type to declare records with the same structure as this type:

    DECLARE
    prev_customer_sales_rec customer_sales_pkg.customer_sales_rectype;
    top_customer_rec customer_sales_pkg.customer_sales_rectype;
    

    Notice that I do not need the %ROWTYPE attribute, or any other kind of keyword, to
    denote this as a record declaration. The %ROWTYPE attribute is needed only for table
    and cursor records.

    Working with Records

    • Record-level operations
      Suppose that I have created the following table:
    CREATE TABLE cust_sales_roundup (
    customer_id NUMBER (5),
    customer_name VARCHAR2 (100),
    total_sales NUMBER (15,2)
    )
    

    Then the three records defined as follows all have compatible structures, and I can “mix
    and match” the data in these records as shown:

    DECLARE
    cust_sales_roundup_rec cust_sales_roundup%ROWTYPE;
    CURSOR cust_sales_cur IS SELECT * FROM cust_sales_roundup;
    cust_sales_rec cust_sales_cur%ROWTYPE;
    TYPE customer_sales_rectype IS RECORD
    (customer_id NUMBER(5),
    customer_name customer.name%TYPE,
    total_sales NUMBER(15,2)
    );
    preferred_cust_rec customer_sales_rectype;
    BEGIN
    -- Assign one record to another.
    cust_sales_roundup_rec := cust_sales_rec;
    preferred_cust_rec := cust_sales_rec;
    END;
    

    In this example, I’ll assign a default value to a record. You can initialize a record at
    the time of declaration by assigning it another compatible record. In the following
    program, I assign an IN argument record to a local variable. I might do this so that
    I can modify the values of fields in the record:

    PROCEDURE compare_companies
    (prev_company_rec IN company%ROWTYPE)
    IS
    curr_company_rec company%ROWTYPE := prev_company_rec;
    BEGIN
    ...
    END;
    

    In this next initialization example, I create a new record type and record. I then
    create a second record type using the first record type as its single column. Finally,
    I initialize this new record with the previously defined record:

    DECLARE
    TYPE first_rectype IS RECORD (var1 VARCHAR2(100) := 'WHY NOT');
    first_rec first_rectype;
    TYPE second_rectype IS RECORD (nested_rec first_rectype := first_rec);
    BEGIN
    ...
    END;
    

    I can also perform assignments within the execution section, as you might expect.
    In the following example I declare two different rain_forest_history records and
    then set the current history information to the previous history record:

    DECLARE
    prev_rain_forest_rec rain_forest_history%ROWTYPE;
    curr_rain_forest_rec rain_forest_history%ROWTYPE;
    BEGIN
    ... initialize previous year rain forest data ...
    -- Transfer data from previous to current records.
    curr_rain_forest_rec := prev_rain_forest_rec;
    

    I can move data directly from a row in a table to a record in a program by fetching
    directly into a record. Here are two examples:

    DECLARE
    /*
    || Declare a cursor and then define a record based on that cursor
    || with the %ROWTYPE attribute.
    */
    CURSOR cust_sales_cur IS
    SELECT customer_id, customer_name, SUM (total_sales) tot_sales
    FROM cust_sales_roundup
    WHERE sold_on < ADD_MONTHS (SYSDATE, −3)
    GROUP BY customer_id, customer_name;
    cust_sales_rec cust_sales_cur%ROWTYPE;
    BEGIN
    /* Move values directly into record by fetching from cursor */
    OPEN cust_sales_cur;
    FETCH cust_sales_cur INTO cust_sales_rec;
    CLOSE cust_sales_cur;
    

    In this next block, I declare a programmer-defined TYPE that matches the data
    retrieved by the implicit cursor. Then I SELECT directly into a record based on that
    type:

    DECLARE
    TYPE customer_sales_rectype IS RECORD
    (customer_id customer.customer_id%TYPE,
    customer_name customer.name%TYPE,
    total_sales NUMBER (15,2)
    );
    top_customer_rec customer_sales_rectype;
    BEGIN
    /* Move values directly into the record: */
    SELECT customer_id, customer_name, SUM (total_sales)
    INTO top_customer_rec
    FROM cust_sales_roundup
    WHERE sold_on < ADD_MONTHS (SYSDATE, −3)
    GROUP BY customer_id, customer_name;
    

    I can set all fields of a record to NULL with a direct assignment:

    /* File on web: record_assign_null.sql */
    FUNCTION dept_for_name (
    department_name_in IN departments.department_name%TYPE
    )
    RETURN departments%ROWTYPE
    IS
    l_return departments%ROWTYPE;
    FUNCTION is_secret_department (
    department_name_in IN departments.department_name%TYPE
    )
    RETURN BOOLEAN
    IS
    BEGIN
    RETURN CASE department_name_in
    WHEN 'VICE PRESIDENT' THEN TRUE
    ELSE FALSE
    END;
    END is_secret_department;
    BEGIN
    SELECT *
    INTO l_return
    FROM departments
    WHERE department_name = department_name_in;
    IF is_secret_department (department_name_in)
    THEN
    l_return := NULL;
    END IF;
    RETURN l_return;
    END dept_for_name;
    
    • Field-level operations
      When you need to access a field within a record (to either read or change its value), you
      must use dot notation, just as you would when identifying a column from a specific
      database table. The syntax for such a reference is:

    [[schema_name.]package_name.]record_name.field_name

    I create a record based on the rain_forest_history table, populate it with values, and then insert a record into that same table:

    DECLARE
    rain_forest_rec rain_forest_history%ROWTYPE;
    BEGIN
    /* Set values for the record */
    rain_forest_rec.country_code := 1005;
    rain_forest_rec.analysis_date := ADD_MONTHS (TRUNC (SYSDATE), −3);
    rain_forest_rec.size_in_acres := 32;
    rain_forest_rec.species_lost := 425;
    /* Insert a row in the table using the record values */
    INSERT INTO rain_forest_history
    (country_code, analysis_date, size_in_acres, species_lost)
    VALUES
    (rain_forest_rec.country_code,
    rain_forest_rec.analysis_date,
    rain_forest_rec.size_in_acres,
    rain_forest_rec.species_lost);
    ...
    END;
    

    Starting with Oracle9i Database Release 2, you can also perform a record-level insert,
    simplifying the preceding INSERT statement into nothing more than this:

    INSERT INTO rain_forest_history VALUES rain_forest_rec;
    
    • Field-level operations with nested records
      In the following example I declare a record TYPE for all the elements of a telephone number (phone_rectype), and then declare a record TYPE that collects all the phone numbers for a person together in a single structure (contact_set_rectype):
    DECLARE
    TYPE phone_rectype IS RECORD
    (intl_prefix VARCHAR2(2),
    area_code VARCHAR2(3),
    exchange VARCHAR2(3),
    phn_number VARCHAR2(4),
    extension VARCHAR2(4)
    );
    -- Each field is a nested record...
    TYPE contact_set_rectype IS RECORD
    (day_phone# phone_rectype,
    eve_phone# phone_rectype,
    fax_phone# phone_rectype,
    home_phone# phone_rectype,
    cell_phone# phone_rectype
    );
    auth_rep_info_rec contact_set_rectype;
    BEGIN
    

    in the following assignment, sets the fax phone number’s area code to the home phone number’s area code:

    auth_rep_info_rec.fax_phone#.area_code := auth_rep_info_rec.home_phone#.area_code;
    
    • Field-level operations with package-based records
      Finally, here is an example demonstrating references to packaged records (and packagebased record TYPEs). Suppose that I want to plan out my summer reading (for all those days I will be lounging about in the sand outside my Caribbean hideaway). I create a package specification as follows:
    CREATE OR REPLACE PACKAGE summer
    IS
      TYPE reading_list_rt IS RECORD (
        favorite_author VARCHAR2 (100),
        title VARCHAR2 (100),
        finish_by DATE);
     must_read reading_list_rt;
     wifes_favorite reading_list_rt;
    END summer;
    
    CREATE OR REPLACE PACKAGE BODY summer
    IS
      BEGIN -- Initialization section of package
        must_read.favorite_author := 'Tepper, Sheri S.';
        must_read.title := 'Gate to Women''s Country';
      END summer;
    

    With this package compiled in the database, I can then construct my reading list as
    follows:

    DECLARE
      first_book summer.reading_list_rt;
      second_book summer.reading_list_rt;
    BEGIN
      summer.must_read.finish_by := TO_DATE ('01-AUG-2009', 'DD-MON-YYYY');
      first_book := summer.must_read;
      second_book.favorite_author := 'Hobb, Robin';
      second_book.title := 'Assassin''s Apprentice';
      second_book.finish_by := TO_DATE ('01-SEP-2009', 'DD-MON-YYYY');
    END;
    

    I declare two local book records. I then assign a “finish by” date to the packaged mustread book (notice the package.record.field syntax) and assign that packaged record to my first book of the summer record. I then assign values to individual fields for the second book of the summer.

    Comparing Records

    To test for record equality, you must write code that compares each field individually. If a record doesn’t have many fields, this isn’t too cumbersome. For the reading list record, you would write something like this:

    DECLARE
      first_book summer.reading_list_rt := summer.must_read;
      second_book summer.reading_list_rt := summer.wifes_favorite;
    BEGIN
      IF first_book.favorite_author = second_book.favorite_author
      AND first_book.title = second_book.title
      AND first_book.finish_by = second_book.finish_by
    THEN
      lots_to_talk_about;
    END IF;
    END;
    

    There is one complication to keep in mind. If your requirements indicate that two NULL
    records are equal (equally NULL), you will have to modify each comparison to something like this:

    (first_book.favorite_author = second_book.favorite_author
    OR( first_book.favorite_author IS NULL AND
    second_book.favorite_author IS NULL))
    

    Trigger Pseudorecords

    When you are writing code inside database triggers for a particular table, the database
    makes available to you two structures, OLD and NEW, which are pseudorecords. These
    structures have the same format as table-based records declared with %ROWTYPE—
    a field for every column in the table:
    OLD
    This pseudorecord shows the values of each column in the table before the current
    transaction started.
    NEW
    This pseudorecord reveals the new values of each column about to be placed in the
    table when the current transaction completes.
    When you reference OLD and NEW within the body of the trigger, you must preface
    those identifiers with a colon; within the WHEN clause, however, do not use the colon.
    Here is an example:

    TRIGGER check_raise AFTER UPDATE OF salary ON employee
    FOR EACH ROW
      WHEN (OLD.salary != NEW.salary) OR
      (OLD.salary IS NULL AND NEW.salary IS NOT NULL) OR
      (OLD.salary IS NOT NULL AND NEW.salary IS NULL)
    BEGIN
      IF :NEW.salary > 100000 THEN ...
    

    %ROWTYPE and invisible columns (Oracle Database 12c)

    As of 12.1, you can now define invisible columns in relational tables. An invisible column
    is a user-defined hidden column, which means that if you want to display or assign a
    value to an invisible column, you must specify its name explicitly. Here is an example
    of defining an invisible column in a table:

    CREATE TABLE my_table (i INTEGER, d DATE, t TIMESTAMP INVISIBLE)
    

    You can make an invisible column visible with an ALTER TABLE statement, as in:

    ALTER TABLE my_table MODIFY t VISIBLE
    

    The SELECT * syntax will not display an INVISIBLE column. However, if you include
    an INVISIBLE column in the select list of a SELECT statement, then the column will
    be displayed. You cannot implicitly specify a value for an INVISIBLE column in the
    VALUES clause of an INSERT statement. You must specify the INVISIBLE column in
    the column list. You must explicitly specify an INVISIBLE column in %ROWTYPE
    attributes.

    If, however, I make that column visible, Oracle will then create a field for it in a
    %ROWTYPE-declared record. This also means that after you make an invisible column visible, Oracle will change the status of all program units that declare records using
    %ROWTYPE against that column’s table to INVALID.

    相关文章

      网友评论

          本文标题:PL/SQL programing 第六版学习笔记-5

          本文链接:https://www.haomeiwen.com/subject/djshgktx.html