Существуют специальные методы-члены — методы map или order — которые мы используем, чтобы указать Oracle Database, как сравнивать два объекта одного и того же типа данных. Эта возможность очень важна, когда мы хотим выполнить тест на равенство в PL / SQL или при сортировке объектов в SQL.
Вам также может понравиться:
Объектно-ориентированный JavaScript
Нет способа сделать это по умолчанию. Другими словами, если я создаю простой тип объекта, добавляю его в виде столбца в таблицу и пытаюсь сравнить или отсортировать, все, что я получаю, — это ошибки. Давайте взглянем. Сначала я создам таблицу с типом объекта в виде столбца и добавлю пару строк.
CREATE TYPE food_ot AS OBJECT
(
name VARCHAR2 (100),
food_group VARCHAR2 (50),
grown_in VARCHAR2 (100)
)
NOT FINAL
/
CREATE TABLE meals
(
served_on DATE,
main_course food_ot
);
/
BEGIN
INSERT INTO meals (served_on, main_course)
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'));
INSERT INTO meals (served_on, main_course)
VALUES (SYSDATE + 1, food_ot ('House Salad', 'VEGETABLE', 'Farm'));
COMMIT;
END;
/
Далее я буду запрашивать содержимое этой таблицы. Обратите внимание, что я могу заказать по атрибуту в пределах значения столбца типа. Я также могу выполнить сравнение равенства между экземплярами типа объекта.
SELECT m.main_course.name name
FROM meals m
ORDER BY m.main_course.name
/
NAME
---------------
House salad
Shrimp Cocktail
SELECT m.main_course.name name
FROM meals m, meals m2
WHERE m.main_course = m2.main_course
ORDER BY m.main_course.name
/
NAME
---------------
House salad
Shrimp Cocktail
Такое сравнение на равенство по умолчанию выполняет сравнение по атрибутам — и это работает только в том случае, если у вас нет столбцов LOB или пользовательских типов. В этих случаях вы увидите ошибку:
CREATE TYPE food_with_clob_ot AS OBJECT
(
name VARCHAR2 (100),
grown_in CLOB
)
NOT FINAL
/
CREATE TABLE meals_with_clobs
(
served_on DATE,
main_course food_with_clob_ot
);
/
SELECT m.main_course.name name
FROM meals_with_clobs m, meals_with_clobs m2
WHERE m.main_course = m2.main_course
ORDER BY m.main_course.name
/
ORA-22901: cannot compare VARRAY or LOB attributes of an object type
Теперь давайте попробуем (а) отсортировать строки с помощью ORDER BY и (б) сделать сравнение на неравенство. Это «не ходи». Поведение по умолчанию больше не доступно для удовлетворения этих запросов.
SELECT m.main_course.name name
FROM meals m
ORDER BY m.main_course
/
ORA-22950: cannot ORDER objects without MAP or ORDER method
SELECT m.main_course.name name
FROM meals m, meals m2
WHERE m.main_course > m2.main_course
/
ORA-22950: cannot ORDER objects without MAP or ORDER method
Что касается сравнения на равенство, то оно работает по умолчанию только в SQL, а не в PL / SQL.
DECLARE
m1 food_ot := food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean');
m2 food_ot := food_ot ('House Salad', 'VEGETABLE', 'Farm');
BEGIN
IF m1 = m1
THEN
DBMS_OUTPUT.put_line ('Equal');
END IF;
IF m1 <> m2
THEN
DBMS_OUTPUT.put_line ('Unequal');
END IF;
END;
/
PLS-00526: A MAP or ORDER function is required for comparing objects in PL/SQL.
Так что же делать разработчику? Прочитайте эти сообщения об ошибках и приступайте к работе!
Если вы «не можете ЗАКАЗАТЬ объекты без метода MAP или ORDER», возможно, вам следует создать метод MAP или ORDER. ?
Метод MAP — это метод-член (прикрепленный к экземпляру типа), который возвращает «отображение» значения объекта на тип данных, который Oracle Database уже знает, как сравнивать, например, число или строку.
Метод члена ORDER сравнивает два разных экземпляра типа и возвращает значение флага, которое указывает их относительное упорядочение.
В определении типа объекта может быть только один метод MAP или ORDER. Они не могут сосуществовать.
Метод MAP
Метод MAP выполняет вычисления атрибутов объекта, чтобы получить возвращаемое значение ny встроенных типов данных Oracle (кроме LOB и BFILE) и типов ANSI SQL, таких как CHARACTER или REAL.
Oracle Database автоматически вызывает этот метод для оценки таких сравнений как obj_1> obj_2 и сравнений, которые подразумеваются в предложениях DISTINCT, GROUP BY, UNION и ORDER BY — поскольку все они требуют сортировки по строкам в таблице.
«Автоматический» означает:
1. Вы никогда не вызываете метод карты непосредственно в своем коде.
2. Предполагая, что тип имеет метод map с именем «mapme», тогда, когда вы пишете сравнение, как это
obj_1> obj_2
он автоматически переводится (незаметно для вас) на:
obj_1.mapme ()> obj_2.mapme ()
Давайте добавим метод карты к типу еды. Я буду держать это простым и глупым. Уровень белков выше, чем жидкости, которые выше, чем углеводы, которые выше, чем овощи. Возьми это число и прибавь к длине названия еды. Затем верните этот номер для сопоставления. Наконец, добавьте несколько строк.
CREATE TYPE food_t AS OBJECT
(name VARCHAR2 (100)
, food_group VARCHAR2 (100)
, grown_in VARCHAR2 (100)
, MAP MEMBER FUNCTION food_mapping
RETURN NUMBER
)
NOT FINAL;
/
CREATE OR REPLACE TYPE BODY food_t
IS
MAP MEMBER FUNCTION food_mapping
RETURN NUMBER
IS
BEGIN
RETURN (CASE self.food_group
WHEN 'PROTEIN' THEN 30000
WHEN 'LIQUID' THEN 20000
WHEN 'CARBOHYDRATE' THEN 15000
WHEN 'VEGETABLE' THEN 10000
END
+ LENGTH (self.name));
END;
END;
/
BEGIN
-- Populate the meal table
INSERT INTO meals
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN', 'Ocean'));
INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Stir fry tofu', 'PROTEIN', 'Wok'));
INSERT INTO meals
VALUES (SYSDATE + 1,
food_ot ('Peanut Butter Sandwich',
'CARBOHYDRATE',
'Kitchen'));
INSERT INTO meals
VALUES (SYSDATE + 1,
food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard'));
COMMIT;
END;
/
Теперь я выполняю ORDER BY для столбца типа объекта, проверяю неравенство между строками, а также выполняю сравнения внутри PL / SQL.
SELECT m.main_course.name name
FROM meals m
ORDER BY main_course
/
NAME
----------------------
Brussels Sprouts
Peanut Butter Sandwich
Stir fry tofu
Shrimp cocktail
SELECT m1.main_course.name name
FROM (SELECT *
FROM meals m
WHERE m.main_course.name LIKE 'S%') m1,
(SELECT *
FROM meals m
WHERE m.main_course.name NOT LIKE 'S%') m2
WHERE m1 > m2
ORDER BY m1.main_course
/
NAME
---------------
Stir fry tofu
Stir fry tofu
Shrimp cocktail
Shrimp cocktail
DECLARE
ot1 food_ot := food_ot ('Eggs benedict', 'PROTEIN', 'Farm');
ot2 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard');
ot3 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE', 'Backyard');
BEGIN
IF ot1 = ot2
THEN
DBMS_OUTPUT.put_line ('equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('not equal - correct');
END IF;
IF ot2 <> ot3
THEN
DBMS_OUTPUT.put_line ('not equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('equal - correct');
END IF;
END;
/
not equal - correct
equal - correct
Обратите внимание, что в запросе, объединяющем m1 и m2, «Stir fry tofu» предшествует «Коктейлю из креветок», потому что в нем меньше символов и, следовательно, небольшое число, возвращаемое функцией map.
Метод ЗАКАЗА
В отличие от методов карты, методы порядка не могут определять порядок ряда объектов. Они просто говорят вам, что текущий объект меньше, равен или больше, чем объект, с которым он сравнивается, в зависимости от используемого критерия.
Метод заказа — это функция для объекта (SELF) с одним объявленным параметром, который является объектом того же типа. Метод должен возвращать либо отрицательное число, ноль, либо положительное число. Это значение означает, что объект (неявный необъявленный параметр SELF) меньше, равен или больше объявленного объекта параметра.
Как и в случае с методами отображения, метод порядка, если он определен, вызывается автоматически всякий раз, когда необходимо сравнить два объекта этого типа.
Методы порядка полезны, когда семантика сравнения может быть слишком сложной, чтобы использовать метод карты.
Давайте построим метод заказа для типа продуктов питания. Давайте начнем со спецификации для типа продуктов, а также создадим иерархию типов:
CREATE TYPE food_ot AS OBJECT
(
name VARCHAR2 (100),
food_group VARCHAR2 (100),
ORDER MEMBER FUNCTION food_ordering (other_food_in IN food_ot)
RETURN INTEGER
)
NOT FINAL;
/
CREATE TYPE dessert_ot UNDER food_ot (
contains_chocolate CHAR (1)
, year_created NUMBER (4)
)
NOT FINAL;
/
CREATE TYPE cake_ot UNDER dessert_ot (
diameter NUMBER
, inscription VARCHAR2 (200)
);
/
Обратите внимание, что я использую ключевое слово ORDER, передаю экземпляр типа, с которым сравнивается SELF. Я возвращаю целое число: либо -1, 0 или 1. А теперь реализация. Вот некоторые примечания, учитывая их сложность:
- Поскольку экземпляром может быть еда, десерт или торт, первое правило заключается в том, что супертип всегда больше, чем подтип. Я использую синтаксис self IS OF (ONLY my_type) для определения типа экземпляра.
- Я использую коллекцию с индексами строк l_order_by_food_group, чтобы установить иерархию упорядочения по группам продуктов. (Мне действительно нравятся коллекции с индексами строк!)
- If after checking for supertype/subtype ordering, I know that other_food_in is of the same type as SELF, then I set return value according to food group.
CREATE OR REPLACE TYPE BODY food_ot
IS
ORDER MEMBER FUNCTION food_ordering (other_food_in IN food_ot)
RETURN INTEGER
/*
Subtypes are always less. Food > Dessert > Cake
If of the same type, same rule AS for MAP:
Vegetable < Carbohydrate < Liquid < Protein
*/
IS
TYPE order_by_food_group_t IS TABLE OF PLS_INTEGER
INDEX BY VARCHAR2 (100);
l_order_by_food_group order_by_food_group_t;
c_self_eq_of CONSTANT PLS_INTEGER := 0;
c_self_gt_of CONSTANT PLS_INTEGER := 1;
c_of_gt_self CONSTANT PLS_INTEGER := -1;
l_ordering PLS_INTEGER := c_self_eq_of;
PROCEDURE initialize
IS
BEGIN
l_order_by_food_group ('PROTEIN') := 1000;
l_order_by_food_group ('LIQUID') := 100;
l_order_by_food_group ('CARBOHYDRATE') := 10;
l_order_by_food_group ('VEGETABLE') := 1;
END initialize;
BEGIN
initialize;
IF self IS OF (ONLY food_ot)
THEN
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY food_ot) THEN c_self_eq_of
ELSE c_self_gt_of
END;
ELSIF self IS OF (ONLY dessert_t)
THEN
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY dessert_t) THEN c_self_eq_of
WHEN other_food_in IS OF (ONLY food_ot) THEN c_of_gt_self
ELSE c_self_gt_of
END;
ELSE
/* It is cake. */
l_ordering :=
CASE
WHEN other_food_in IS OF (ONLY cake_t) THEN c_self_eq_of
ELSE c_of_gt_self
END;
END IF;
IF l_ordering = c_self_eq_of
THEN
/*
Further analysis is needed.
*/
l_ordering :=
CASE
WHEN l_order_by_food_group (self.food_group) =
l_order_by_food_group (other_food_in.food_group)
THEN
c_self_eq_of
WHEN l_order_by_food_group (self.food_group) >
l_order_by_food_group (other_food_in.food_group)
THEN
c_self_gt_of
WHEN l_order_by_food_group (self.food_group) <
l_order_by_food_group (other_food_in.food_group)
THEN
c_of_gt_self
END;
END IF;
RETURN l_ordering;
END;
END;
/
Now I will add rows of various types.
BEGIN
-- Populate the meal table
INSERT INTO meals
VALUES (SYSDATE, food_ot ('Shrimp cocktail', 'PROTEIN'));
INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Stir fry tofu', 'PROTEIN'));
INSERT INTO meals
VALUES (SYSDATE + 1,
dessert_ot ('Peanut Butter Sandwich',
'CARBOHYDRATE',
'N',
1700));
INSERT INTO meals
VALUES (SYSDATE + 1, food_ot ('Brussels Sprouts', 'VEGETABLE'));
INSERT INTO meals
VALUES (SYSDATE + 1,
cake_ot ('Carrot Cake',
'VEGETABLE',
'N',
1550,
12,
'Happy Birthday!'));
COMMIT;
END;
/
All right, then, let’s have some fun! Ordering rows works. SQL comparisons work.
SELECT m.main_course.name name
FROM meals m
ORDER BY main_course
/
NAME
----------------------
Carrot Cake
Peanut Butter Sandwich
Brussels Sprouts
Shrimp cocktail
Stir fry tofu
SELECT m1.main_course.name name
FROM (SELECT *
FROM meals m
WHERE m.main_course.name LIKE 'S%') m1,
(SELECT *
FROM meals m
WHERE m.main_course.name NOT LIKE 'S%') m2
WHERE m1.main_course > m2.main_course
ORDER BY m1.main_course
/
NAME
---------------
Shrimp cocktail
Shrimp cocktail
Shrimp cocktail
Stir fry tofu
Stir fry tofu
Stir fry tofu
And how about in PL/SQL?
DECLARE
ot1 food_ot := food_ot ('Eggs benedict', 'PROTEIN');
ot2 food_ot := food_ot ('Brussels Sprouts', 'VEGETABLE');
ot3 food_ot := dessert_ot ('Brownie', 'SUGAR', 'Y', 1943);
ot4 food_ot := cake_ot (
'Carrot Cake', 'VEGETABLE', 'N', 1550, 12, 'Happy Birthday!');
BEGIN
IF ot1 = ot1
THEN
DBMS_OUTPUT.put_line ('equal - correct');
ELSE
DBMS_OUTPUT.put_line ('not equal - incorrect');
END IF;
IF ot1 = ot2
THEN
DBMS_OUTPUT.put_line ('equal - incorrect');
ELSE
DBMS_OUTPUT.put_line ('not equal - correct');
END IF;
IF ot2 <> ot3
THEN
DBMS_OUTPUT.put_line ('not equal - correct');
ELSE
DBMS_OUTPUT.put_line ('equal - incorrect');
END IF;
IF ot2 > ot3
THEN
DBMS_OUTPUT.put_line ('food > dessert - correct');
ELSE
DBMS_OUTPUT.put_line ('food < dessert - incorrect');
END IF;
IF ot3 > ot4
THEN
DBMS_OUTPUT.put_line ('dessert > cake - correct');
ELSE
DBMS_OUTPUT.put_line ('dessert < cake - incorrect');
END IF;
IF ot3 < ot4
THEN
DBMS_OUTPUT.put_line ('dessert < cake - incorrect');
ELSE
DBMS_OUTPUT.put_line ('dessert > cake - correct');
END IF;
END;
/
equal - correct
not equal - correct
not equal - correct
food > dessert - correct
dessert > cake - correct
dessert > cake - correct
All good!
P.S. Don’t forget that if you’d like to try out all this code for yourself, all you have to do is run this LiveSQL script.
The Series:
- Introduction to Object Types (a.k.a. Classes) Part 1
- Object Types and Inheritance: Part 2
- Object Type Methods, Part 3
- Using Object Types in Relational Tables, Part 4
- Comparison Methods for Object Types, Part 5