Források

DB Az Oracle objektumrelációs lehetőségei


Típusok definiálása

Az Oracle megengedi az SQL típusaihoz hasonló típusok definiálását. A szintaxis:
     CREATE TYPE t AS OBJECT (
         attribútumok és metódusok listája
     );
     /
Például egy két számból álló pont típus definíciója:
     CREATE TYPE PointType AS OBJECT (
         x NUMBER,
         y NUMBER
     );
     /
Egy objektumtípus használható az egyéb típusokhoz hasonlóan az objektum- és táblatípusok definícióiban. Például definiálhatunk így egy vonal típust:
     CREATE TYPE LineType AS OBJECT (
         end1 PointType,
         end2 PointType
     );
     /
Ezután készíthetünk egy relációt, amely azonosítóval ellátott vonalak halmaza:
     CREATE TABLE Lines (
         lineID INT,
         line   LineType
     );


Típusok eldobása

Ha meg akarunk szabadulni pl. a LineType típustól, ezt mondjuk:
     DROP TYPE Linetype;
Azonban egy típus eldobása előtt el kell dobnunk az összes táblát és típust, amely a típust használja. Tehát a fenti művelet nem sikerül, ha létezik a Lines tábla és használja a LineType-ot.


Objektumok konstruálása

A C++-hoz hasonlóan az Oracle biztosítja a beépített konstruktorokat a deklarált típusokhoz, és ezeknek a konstruktoroknak a neve megegyezik az osztály nevével. Tehát egy PointType típusú érték úgy áll elő, hogy a PointType szó után zárójelek között felsoroljuk a megfelelő értékeket. Például így szúrnánk be a Lines táblába egy vonalat a 27-es azonosítóval, amely az origótól a (3,4) pontig tart:
     INSERT INTO Lines
         VALUES(27, LineType(
                         PointType(0.0, 0.0),
                         PointType(3.0, 4.0)
                    )
         );
Azaz konstruálunk két PointType értéket, ezeket az értékeket a LineType, konstruktora használja fel, az így kapott értéket pedig a 27-es egésszel összetéve kapjuk a Lines egy rekordját.


Metódusok delklarálása és definiálása

A típusdeklaráció tartalmazhat a típus értékein értelmezett metódusokat. A metódusokat a CREATE TYPE utasításban MEMBER FUNCTION vagy MEMBER PROCEDURE kulcsszavakkal deklaráljuk, magát a függvényt (a metódus definícióját) egy külön CREATE TYPE BODY utasításban adjuk meg.

A metódusokban elérhető egy speciális rekordváltozó, a SELF, amellyel az aktuális rekordra hivatkozhatunk. Ha a SELF-et használjuk egy metódus definíciójában, akkor a környezetnek olyannak kell lennie, hogy egy konkrét rekordra hivatkozunk. A metódusok alkalmazására példákat találhatunk a lekérdezésekről és a sortípusokól szóló szakaszokban.

Például szeretnénk hozzáadni egy hossz függvényt a LineType-hoz. Ez a metódus a ,,jelenlegi'' vonal objektumra vonatkozik, de amikor kiszámítja a hosszt, megszorozza egy mérettényezővel. Átírjuk a LineType definícióját a következőre:

     CREATE TYPE LineType AS OBJECT (
         end1 PointType,
         end2 PointType,
         MEMBER FUNCTION length(scale IN NUMBER) RETURN NUMBER,
         PRAGMA RESTRICT_REFERENCES(length, WNDS)
     );
     /

A típus minden metódusát egyetlen CREATE BODY utasításban definiáljuk, például:

     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;
     /


Lekérdezések olyan táblákból, amelyek felhasználói típust tartalmaznak

Egy objektum tagjait a pont operátorral érhetjük el. Erre fent már láttunk példát, ahol az end1 pont x attribútumát az end1.x kifejezéssel értük el stb. Általában ha N valamilyen O objektumra hivatkozik, melynek típusa T, és T valamelyik tagja (attribútum vagy metódus) A, akkor N.A erre a tagra hivatkozik az O objektumban.

Például az alábbi lekérdezés meghatározza a Lines reláció vonalainak hosszát 2-es mérettényezővel (azaz valójában a hosszak kétszeresét adja vissza).

    SELECT lineID, ll.line.length(2.0)
     FROM Lines ll;
Itt van még néhány lekérdezés a lines relációból.
     SELECT ll.line.end1.x, ll.line.end1.y
     FROM Lines ll;
Kiírja minden vonal kezdőpontjának x és y koordinátáját.
     SELECT ll.line.end2
     FROM Lines ll;
Kiírja minden vonal végpontját, de PointType típusként, nem számpárként. Például egy vonalra a kimenet lehet PointType(3,4). Figyeljük meg, hogy a konstruktorok megjelenhetnek a kimenetben is, nem csak a bemenetben.


A típus lehet relációséma is

Eddig a típusokat csak mezőtípusként, azaz attribútumok típusaként használtuk. A CREATE TABLE utasításban helyettesíthetjük a zárójelezett sémaelemeket az OF kulcsszóval és egy típus nevével. Ekkor azt mondjuk, hogy a típust sortípusként használjuk. Például ha szeretnénk létrehozni egy relációt, amelynek minden rekordja egy pontpár, akkor mondhatnánk ezt:
     CREATE TABLE Lines1 OF LineType;
Olyan, mint ha így definiáltuk volna Lines1-et:
     CREATE TABLE Lines1 (
        end1 PointType,
        end2 PointType
     );
viszont a length metódus is elérhető, ha lines1 valamelyik rekordjára hivatkozunk. Például kiszámíthatjuk a vonalak átlaghosszát így:
     SELECT AVG(ll.length(1.0))
     FROM Lines1 ll;


Referenciák típusként

Minden t típushoz REF t a típus értékeire vonatkozó referencia (azaz az objektum azonosítója). Ez a típus használható olyan helyeken, ahol típust kell megadni. Például a Lines2 relációt, amelynek rekordjai pont-referenciapárok:
     CREATE TABLE Lines2 (
        end1 REF PointType,
        end2 REF PointType
     );
A REF operátorral kaphatunk referenciát egy létező értékre. Például ha van egy Points relációnk PointType típusú rekordokkal:
     CREATE TABLE Points OF PointType;
Ekkor így készíthetjük el a Lines2 relációt, amelynek elemei a balról jobbra futó vonalak (az első pont x koordinátája kisebb, mint a másodiké):
     INSERT INTO Lines2
         SELECT REF(pp), REF(qq)
         FROM Points pp, Points qq
         WHERE pp.x < qq.x;
Több fontos tiltás is van, amikor úgy tűnik, hogy lehetne referenciát állítani egy objektumra, de valójában nem lehet. A referencia követéséhez használjuk a pont operátort, mintha a hivatkozott érték attribútumai magának a referenciának lennének az attribútumai. Például ez a lekérdezés visszaadja a Lines2 összes vonala végpontjainak x koordinátáját.
     SELECT ll.end1.x, ll.end2.x
     FROM Lines2 ll;


Beágyazott táblák

Az objektumtípusok egy hatásos használata az Oracle-ben az, hogy egy mező típusa lehet egy táblatípus is. Tehát egy rekord egy attribútuma lehet egy egész reláció, mint ahogy az alábbi táblázat is sugallja: az (a,b) sémájú reláció b értékei (x,y,z) sémájú relációk.

ab
-
xyz
---
---
---
-
xyz
---
-
xyz
---
---

Ha szeretnénk egy attribútum típusaként relációt használni, akkor előbb definiálnunk kell egy típust az AS TABLE OF részmondattal. Például:

     CREATE TYPE PolygonType AS TABLE OF PointType;
     /
Ez azt jelenti, hogy a PolygonType típus egy reláció, amelynek rekordjai PointType típusúak, azaz két komponensük van, a valós szám értékű x és y.

Must deklarálhatunk olyan relációt, amelynek mezőinek értékei sokszögek, azaz pontok halmazai. Egy lehetséges deklaráció, amelyben a sokszögeket egy névvel és egy ponthalmazzal ábrázoljuk:

     CREATE TABLE Polygons (
         name   VARCHAR2(20),
         points PolygonType)
         NESTED TABLE points STORE AS PointsTable;
Az ,,apró'' relációkat, amelyek az egyes sokszögeket reprezentálják, nem tároljuk közvetlenül a points attribútumok értékeiként, hanem egyetlen nagy táblában, amelynek nevét deklarálni kell (habár nem tudunk rá hivatkozni). Ezt a deklarációt a tábla zárójelezett attribútumlistája után látható: a PointsTable nevet választottuk a PolygonType típusú relációk tárolására. Ha beszúrunk egy Polygons-hoz hasonló relációba, amelyben egy vagy több mező beágyazott reláció típusú (a példában PolygonType), akkor a beágyazott reláció típus konstruktorával vesszük körül a reláció értékét. A reláció értékét egy megfelelő típusú értékekből álló listával adjuk meg (a példánkban PointType).

Itt egy utasítás, amely egy ,,négyzet'' nevű sokszöget szúr be, amelynek pontjai az egységnégyzet pontjai.

     INSERT INTO Polygons VALUES(
         'négyzet', PolygonType(PointType(0.0, 0.0), PointType(0.0, 1.0),
                               PointType(1.0, 0.0), PointType(1.0, 1.0)
                              )
     );
A négyzet pontjait egy ilyen lekérdezéssel kaphatjuk meg:
     SELECT points
     FROM Polygons
     WHERE name = 'négyzet';
Lehetséges egy beágyazott relációt a FROM részmondatban feltüntetni a THE kulcsszóval, amelyet egy relációt visszaadó alkérdésre alkalmazunk. A fenti lekérdezés egy ilyen példa, mivel egy beágyazott relációt ad vissza. Például az alábbi lekérdezés megkeresi azokat a pontokat a négyzetben, amelyek a főátlón vannak (azaz x=y).
     SELECT ss.x
     FROM THE(SELECT points
              FROM Polygons
              WHERE name = 'négyzet'
             ) ss
     WHERE ss.x = ss.y;
Ebben a lekérdezésben a beágyazott relációnak az ss álnevet adjuk, amelyet a SELECT és WHERE részmondatban használhatunk, mintha közönséges reláció lenne.


Beágyazott relációk és referenciák kombinálása

A dolgok kicsit összekuszálódnak, ha megtesszük a természetes lépést (hogy normalizált legyen az adat) és egy olyan relációt készítünk, amelynek rekordjai referenciák egy másik tábla rekordjaira. Az a baj, hogy a beágyazott tábla attribútumának nincs neve. Az Oracle-ben a COLUMN_VALUE kulcsszót használhatjuk ebben az esetben. Itt egy példa, amely a fenti sokszögábrázolást referenciákból álló beágyazott táblával oldja meg. Először készítünk egy típust, amely egy pontreferenciákból álló beágyazott tábla:
     CREATE TYPE PolygonRefType AS TABLE OF REF PointType;
/
Ezután szükségünk van egy Polygons-szerű relációra, ahol a pontokat referenciatáblában tároljuk:
     CREATE TABLE PolygonsRef (
         name   VARCHAR2(20),
         pointsRef PolygonRefType)
         NESTED TABLE pointsRef STORE AS PointsRefTable;
Emlékezzünk rá, hogy magukat a pontokat egy másik, PointType típusú relációban kell tárolnunk. Ezt a részt most kihagyjuk. A fenti főátlóbeli pontos lekérdezést úgy írhatjuk át a referenciás változatra, hogy a COLUMN_VALUE kulccsszóval hivatkozunk a beágyazott tábla mezőjére. Ilyen lesz a lekérdezés:
     SELECT ss.COLUMN_VALUE.x
     FROM THE(SELECT pointsRef
              FROM PolygonsRef
              WHERE name = 'négyzet'
             ) ss
     WHERE ss.COLUMN_VALUE.x = ss.COLUMN_VALUE.y;


Átalakítás hagyományos relációról objektumrelációra

Ha adataink egy hagyományos relációban vannak (azaz minden attibútum típusa az SQL beépített típusa), és szeretnénk létrehozni egy ekvivalens relációt, amelynek típusa egy felhasználói objektumtípus vagy objektumokat is tartalmazó rekordtípus, akkor használjuk az INSERT utasításnak azt a változatát, amely egy lekérdezés eredményét szúrja be. A lekérdezés megfelelően használhatja a konstruktorokat.

Például legyen egy LinesFlat relációnk, amelynek deklarációja:

     CREATE TABLE LinesFlat(
         id INT,
         x1 NUMBER,
         y1 NUMBER,
         x2 NUMBER,
         y2 NUMBER
     );
és ez a reláció a vonalakat ,,régi'' stílusban tartalmazza, tehát egy azonosítóval és a végponttok koordinátáival. Bemásolhatjuk az adatokat a Lines-ba és helyes szerkezetet adhatunk nekik:
     INSERT INTO Lines
         SELECT id, LineType(PointType(x1,y1), PointType(x2,y2))
         FROM LinesFlat;
Ha beágyazott táblát tartalmazó táblába szeretnénk beszúrni, akkor trükközni kell. Ha egy már létező beágyazott relációba akarunk beszúrni, akkor használhatjuk a THE kulcsszót. Például ha a (2.0, 3.0) pontot szeretnénk beszúrni a ,,háromszög'' nevű sokszög beágyazott táblájába:
     INSERT INTO THE(SELECT points
                     FROM Polygons
                     WHERE name = 'háromszög'
                    )
     VALUES(PointType(2,0, 3.0));
Tegyük fel, hogy van egy ,,lapos'' relációnk, amely egy sokszög pontjait reprezentálja:
     CREATE TABLE PolyFlat (
         name VARCHAR2(20),
         x    NUMBER,
         y    NUMBER
     );
Ha egy négyzet pontjait a PolyFlat reprezentálja, akkor bemásolhatjuk a Polygons-ba az alábbi módon:
  1. Lekérdezzük a PolyFlat-ből a négyzet pontjait.

  2. Átalakítjuk a válasz kollekciót relációvá a MULTISET kulcsszóval.
  3. Átalakítjuk a relációt PolygonType típussá a CAST ... AS PolygonType kifejezéssel.
  4. A 'négyzet' stringet és a 3. pontban felépített értéket felhasználjuk a VALUES kifejezésben.
Íme a parancs:
     INSERT INTO Polygons VALUES('négyzet',
         CAST(
             MULTISET(SELECT PointType(x, y)
                      FROM PolyFlat
                      WHERE name = 'négyzet'
                     )
             AS PolygonType
         )
     );
Még bonyolultabb, ha a lapos PolyFlat-ből az összes sokszöget szeretnénk bemásolni a Polygons-ba. Az alábbi majdnem működik
     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;
Az a baj, hogy ha négy pont van, akkor négy rekordot szúr be a 'négyzet' névvel. Az sem működik, hogy az első SELECT után írjuk a DISTINCT kulcsszót. Olyan módszert kell találnunk, amely egy sokszögnevet csak egyszer szúr be, és ehhez egy ésszerű megoldás, hogy egy WHERE részmondatot adunk hozzá, amely megköveteli, hogy a PolyFlat x és y tagjai lexikografikusan elsők legyenek. Íme egy működő beszúró parancs:
     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
         );

Ezt a dokumentumot eredetileg Jeff Ullman írta a CS145 részére 1998 őszén. Külön köszönet Ian Mizrahinak a COLUMN_VALUE lehetőség felkutatásáért.