Object-Relational Features of
Oracle |
CREATE TYPE t AS OBJECT ( list of attributes and methods ); /
CREATE TYPE PointType AS OBJECT ( x NUMBER, y NUMBER ); /An object type can be used like any other type in further declarations of object-types or table-types. For instance, we might define a line type by:
CREATE TYPE LineType AS OBJECT ( end1 PointType, end2 PointType ); /Then, we could create a relation that is a set of lines with ``line ID's'' as:
CREATE TABLE Lines ( lineID INT, line LineType );
DROP TYPE Linetype;However, before dropping a type, we must first drop all tables and other types that use this type. Thus, the above would fail because table Lines still exists and uses LineType.
INSERT INTO Lines VALUES(27, LineType( PointType(0.0, 0.0), PointType(3.0, 4.0) ) );That is, we construct two values of type PointType, these values are used to construct a value of type LineType, and that value is used with the integer 27 to construct a tuple for Lines.
Methods have available a special tuple variable SELF, which refers to the ``current'' tuple. If SELF is used in the definition of the method, then the context must be such that a particular tuple is referred to. There are some examples of applying methods correctly in The Section on Queries and The Section on Row Types.
For example, we might want to add a length function to LineType. This function will apply to the ``current'' line object, but when it produces the length, it also multiplies by a ``scale factor.'' We revise the declaration of LineType to be:
CREATE TYPE LineType AS OBJECT ( end1 PointType, end2 PointType, MEMBER FUNCTION length(scale IN NUMBER) RETURN NUMBER, PRAGMA RESTRICT_REFERENCES(length, WNDS) ); /
All methods for a type are then defined in a single CREATE BODY statement, for example:
CREATE TYPE BODY LineType AS MEMBER FUNCTION length(scale NUMBER) RETURN NUMBER IS BEGIN RETURN scale * SQRT((SELF.end1.x-SELF.end2.x)*(SELF.end1.x-SELF.end2.x) + (SELF.end1.y-SELF.end2.y)*(SELF.end1.y-SELF.end2.y) ); END; END; /
For example, the following query finds the lengths of all the lines in relation Lines, using scale factor 2 (i.e., it actually produces twice these lengths).
SELECT lineID, ll.line.length(2.0) FROM Lines ll;
SELECT ll.line.end1.x, ll.line.end1.y FROM Lines ll;prints the x and y coordinates of the first end of each line.
SELECT ll.line.end2 FROM Lines ll;prints the second end of each line, but as a value of type PointType, not as a pair of numbers. For instance, one line of output would be PointType(3,4). Notice that type constructors are used for output as well as for input.
CREATE TABLE Lines1 OF LineType;It is as if we had defined Lines1 by:
CREATE TABLE Lines1 ( end1 PointType, end2 PointType );but the method length is also available whenever we refer to a tuple of lines1. For instance, we could compute the average length of a line by:
SELECT AVG(ll.length(1.0)) FROM Lines1 ll;
CREATE TABLE Lines2 ( end1 REF PointType, end2 REF PointType );We can use REF to create references from actual values. For example, suppose we have a relation Points whose tuples are objects of type PointType. That is, Points is declared by:
CREATE TABLE Points OF PointType;We could make Lines2 be the set of all lines between pairs of these points that go from left to right (i.e., the x-value of the first is less than the x-value of the second) by:
INSERT INTO Lines2 SELECT REF(pp), REF(qq) FROM Points pp, Points qq WHERE pp.x < qq.x;There are several important prohibitions, where you might imagine you could arrange for a reference to an object, but you cannot.
SELECT ll.end1.x, ll.end2.x FROM Lines2 ll;
a | b | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- |
| ||||||||||||
- |
| ||||||||||||
- |
|
In order to have a relation as a type of some attribute, we first have to define a type using the AS TABLE OF clause. For instance:
CREATE TYPE PolygonType AS TABLE OF PointType; /says that the type PolygonType is a relation whose tuples are of type PointType; i.e., they have two components, x and y, which are real numbers.
Now, we can declare a relation one of whose columns has values that represent polygons; i.e., they are sets of points. A possible declaration, in which polygons are represented by a name and a set of points is:
CREATE TABLE Polygons ( name VARCHAR2(20), points PolygonType) NESTED TABLE points STORE AS PointsTable;The ``tiny'' relations that represent individual polygons are not stored directly as values of the points attribute. Rather, they are stored in a single table, whose name must be declared (although we cannot refer to it in any way). We see this declaration following the parenthesized list of attributes for the table; the name PointsTable was chosen to store the relations of type PolygonType.
Here is a statement inserting a polygon named ``square'' that consists of four points, the corners of the unit square.
INSERT INTO Polygons VALUES( 'square', PolygonType(PointType(0.0, 0.0), PointType(0.0, 1.0), PointType(1.0, 0.0), PointType(1.0, 1.0) ) );We can obtain the points of this square by a query such as:
SELECT points FROM Polygons WHERE name = 'square';It is also possible to get a particular nested relation into the FROM clause by use of the keyword THE, applied to a subquery whose result is a relation; the above query is an example, since it returns a whole nested relation. For instance, the following query finds those points of the polygon named square that are on the main diagonal (i.e., x=y).
SELECT ss.x FROM THE(SELECT points FROM Polygons WHERE name = 'square' ) ss WHERE ss.x = ss.y;In this query, the nested relation is given an alias ss, which is used in the SELECT and WHERE clauses as if it were any ordinary relation.
CREATE TYPE PolygonRefType AS TABLE OF REF PointType; /Next, we need a new relation, similar to Polygons, but with the points of a polygon stored as a nested table of references:
CREATE TABLE PolygonsRef ( name VARCHAR2(20), pointsRef PolygonRefType) NESTED TABLE pointsRef STORE AS PointsRefTable;Remember that the points themselves must be stored in some relation of type PointType; we omit this part of the process of creating and loading data. To query the points in a nested table, as we did for the query above that asked for the points on the main diagonal, we write essentially the same query, except that we must use COLUMN_VALUE to refer to the column of the nested table. The query becomes:
SELECT ss.COLUMN_VALUE.x FROM THE(SELECT pointsRef FROM PolygonsRef WHERE name = 'square' ) ss WHERE ss.COLUMN_VALUE.x = ss.COLUMN_VALUE.y;
For example, suppose we have a relation LinesFlat declared by:
CREATE TABLE LinesFlat( id INT, x1 NUMBER, y1 NUMBER, x2 NUMBER, y2 NUMBER );and this relation contains lines represented in the ``old'' style, that is, an ID and four components representing the x- and y-coordinates of two points. We can copy this data into Lines and give it the right structure by:
INSERT INTO Lines SELECT id, LineType(PointType(x1,y1), PointType(x2,y2)) FROM LinesFlat;Insertion with a SELECT clause into a table with nested relations is tricky. If we simply want to insert into an existing nested relation, we can use THE with specified values. For instance, if we want to insert the point (2.0, 3.0) into the nested relation for the polygon named ``triangle,'' we can write:
INSERT INTO THE(SELECT points FROM Polygons WHERE name = 'triangle' ) VALUES(PointType(2,0, 3.0));Now, suppose we already have a ``flat'' relation representing points of polygons:
CREATE TABLE PolyFlat ( name VARCHAR2(20), x NUMBER, y NUMBER );If the points of a square are represented in PolyFlat, then we can copy them into Polygons by:
INSERT INTO Polygons VALUES('square', CAST( MULTISET(SELECT PointType(x, y) FROM PolyFlat WHERE name = 'square' ) AS PolygonType ) );Even more complex is the way we can copy data from the flat PolyFlat to put all the polygons and their sets of points into Polygons. The following almost works:
INSERT INTO Polygons SELECT pp.name, CAST( MULTISET(SELECT PointType(x, y) FROM PolyFlat qq WHERE qq.name = pp.name ) AS PolygonType ) FROM PolyFlat pp;The problem is that if there are four points, then there are four tuples with name 'square' inserted. Adding DISTINCT after the first SELECT doesn't work. We have to find a way to perform the insertion for each polygon name only once, and a reasonable way is to add a WHERE clause that insists the x and y components of the PolyFlat tuple be lexicographically first. Here is a working insertion command:
INSERT INTO Polygons SELECT pp.name, CAST( MULTISET(SELECT PointType(x, y) FROM PolyFlat qq WHERE qq.name = pp.name ) AS PolygonType ) FROM PolyFlat pp WHERE NOT EXISTS( SELECT * FROM PolyFlat rr WHERE rr.name = pp.name AND rr.x < pp.x OR rr.x = pp.x AND rr.y < pp.y );