Was nützt ein riesiger und strukturierter Datenbestand, wenn man nicht flexibel aber dennoch effektiv darin suchen kann? Als eines von zahlreichen positiven Beispielen sei hier die Autobörse http://www.autoscout24.de/ genannt. Dem Benutzer stehen Suchkriterien von A bis Z, quasi der Automarke bis zum Zulassungsdatum, zur Eingrenzung seiner Suche zur Verfügung. Seine Wunschkriterien kann jeder Benutzer sogar speichern und bei neuen Einträgen wird er automatisch informiert.
Mit steigender Anzahl von Suchkriterien wächst die Komplexität der Suchabfrage. Wenn man sich das SQL Statement vorstellt, welches sich über diese Seite generieren lässt, kann einem Entwickler durchaus schwindelig werden. Ein wenig Abhilfe schafft der Oracle Expression Filter, ein Feature innerhalb der Datenbank.
Der Expression Filter benötigt eine Definition aller möglichen Attribute, den sogenannten Attributesets, also unseren Suchkriterien. Um schnell ein solches Set zu erzeugen, definieren wir einen eigenen Typ, der alle möglichen Attribute besitzt.
CREATE OR REPLACE TYPE AutoEigenschaften AS OBJECT ( Marke VARCHAR2(100), Modell VARCHAR2(100), Farbe VARCHAR2(30), Baujahr NUMBER(4), Zylinder NUMBER(2), Hubraum NUMBER(3,1) ); /
Das folgende Statement erzeugt das Set aus den Properties unseres Typs:
BEGIN
DBMS_EXPFIL.CREATE_ATTRIBUTE_SET(
attr_set => 'AutoEigenschaften',
from_type => 'YES');
END;
/
CREATE TABLE Kunden (
Kundennummer NUMBER PRIMARY KEY NOT NULL,
Name VARCHAR2(100) NOT NULL,
Wunsch VARCHAR2(4000) NOT NULL
);
Um unsere Benutzer und ihre Wünsche zu speichern genügt eine einfache Tabelle. (Hier natürlich sehr vereinfacht) Die Spalte WUNSCH soll später die Suchkriterien der Benutzer enthalten und ist als großer VARCHAR2 definiert. Mit dem nächsten Schritt beginnt der Zauber. Wir sagen dem Expression Filter, dass die Spalte WUNSCH in der Tabelle KUNDEN einen Ausdruck für den Typ AutoEigenschaften aufnehmen soll.
BEGIN
DBMS_EXPFIL.ASSIGN_ATTRIBUTE_SET (
attr_set => 'AutoEigenschaften',
expr_tab => 'Kunden',
expr_col => 'Wunsch'
);
END;
/
Durch die Zuweisung passiert ein wenig Zauberei im Hintergrund. Zum Beispiel legt Oracle einen Trigger im Schema EXFSYS an, dem Schemaeigener des Expression Filters.
CREATE OR REPLACE TRIGGER EXFSYS.EXF$VALIDATE_1
BEFORE INSERT OR UPDATE OF "WUNSCH" ON "HABELADM"."KUNDEN"
FOR EACH ROW
declare
caller VARCHAR2(32);
isvalid NUMBER := 1;
code VARCHAR2(1) := 'C';
begin
select user into caller from dual;
if (caller != 'HABELADM') then
if (UPDATING) then code:='U'; end if;
if (code = 'U' or :new."WUNSCH" is not null) then
exfsys.exf$check_privilege(code,'HABELADM','KUNDEN','WUNSCH',caller);
end if;
end if;
if (:new."WUNSCH" is not null) then
isvalid := exfsys.exf$expisvalid (68,'HABELADM','AUTOEIGENSCHAFTEN', 'SELECT
/*+ EXPR_CORR_CHECK USE_WEAK_NAME_RESL */ 1 FROM
TABLE(CAST(null AS "EXF$NTT_61818")) exf$_eqast
WHERE '||:new."WUNSCH");
end if;
exception when others then raise;
end;
Wirklich kümmern müssen wir uns um den kryptischen Code nicht. Viel wichtiger ist seine Auswirkung:
INSERT INTO Kunden VALUES ( 1, 'Max Mustermann', 'x = 3' );
*
ERROR at line 1:
ORA-00904: "X": invalid identifier
ORA-06512: at "EXFSYS.EXF$VALIDATE_1", line 18
ORA-04088: error during execution of trigger 'EXFSYS.EXF$VALIDATE_1'
Das INSERT Statement ist fehlgeschlagen, da der Ausdruck “x = 3” ungültig ist. X ist unbekannt, da es keinem der definierten Attribute entspricht. Gültig wäre zum Beispiel folgendes:
INSERT INTO Kunden VALUES ( 1, 'Max Mustermann', 'Baujahr > 2000 AND Zylinder >= 4' ); INSERT INTO Kunden VALUES ( 2, 'Peter Mueller', 'Marke <> ''Audi'' AND Hubraum > 2');
Was unserem Datenmodell selbstverständlich noch fehlt, ist die Tabelle mit den angebotenen Autos. Ich habe mich hier entschieden alle Attribute direkt in einer Objektspalte abzulegen. Das ist zwar nicht notwendig, macht aber das Beispiel ein wenig einfacher.
CREATE TABLE Autos (
ID NUMBER PRIMARY KEY NOT NULL,
VerkaeuferID NUMBER NOT NULL,
Eigenschaften AutoEigenschaften NOT NULL
);
INSERT INTO Autos VALUES (1, 99, AutoEigenschaften('Ford', 'Fokus', 'Schwarz', 1999, 4, 1.9 ));
INSERT INTO Autos VALUES (2, 100, AutoEigenschaften('Audi', 'A3', 'Grau', 2004, 6, 3.2 ));
Was wir nun von der Datenbank wollen ist schnell gesagt: Welches Auto ist für welchen Kunden interessant? Das Statement ist ähnlich einfach:
SELECT KUNDENNUMMER, NAME , EIGENSCHAFTEN AS AUTO
FROM Kunden
JOIN Autos
ON EVALUATE(Kunden.Wunsch, AnyData.convertObject(Autos.Eigenschaften)) = 1;
KUNDENNUMMER NAME AUTO(MARKE, MODELL, FARBE, BAUJAHR, ZYLI
------------- ---------------- ----------------------------------------
1 Max Mustermann AUTOEIGENSCHAFTEN('Audi', 'A3', 'Grau',
2004, 6, 3,2)
Die Funktion EVALUATE welche wir in der JOIN Bedingung nutzen vergleicht nun die gespeicherten Ausdrücke mit den Eigenschaften unserer Autos. Passt der Ausdruck mit den Eigenschaften zusammen, liefert die Funktion eine 1 zurück und wodurch das Auto für diesen User als relevant zurückgegeben wird.
Schlagworte: EVALUATE, Expression Filter, JOIN, Oracle, SQL, Trigger