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

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.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容