Ulf Wendel

MaxDB: “geheime” Symbolische Konstanten

Es gibt Momente, da liebe ich meinen Job. Mittwoch war einer dieser Tage. Alles was ich wollte, war per Stored Procedure ein paar Zufallsdaten innerhalb der Datenbank erzeugen. Das Thema Zufallsdaten scheiterte an vielen Dingen. Heute berichte ich nur über RANDOM()/RAND(), NOW() und TIMESTAMP in MaxDB. Mehr halten meine Nerven nicht aus.

Inhalt

RAND()/RANDOM() per Stored Function simulieren

MaxDB stellt keine Funktion bereit, um Zufallszahlen zu erzeugen. RAND() oder RANDOM() existieren nicht. Offensichtlich konnte man sich bei der Implementierung niemand vorstellen, daß RAND()/RANDOM() und Verwandte dazu benutzt werden können, um Tabellen mit Zufallsdaten zu befüllen oder jeden Tag eine andere, zufällige Auswahl von Tagessprüchen zu präsentieren, ohne hierfür eine Programmiersprache zu bemühen.

Eine Programmiersprache wollte ich nicht bemühen, um Zufallsdaten zu erzeugen. Also mußte ich RAND()/RANDOM() in MaxDB per benutzerdefinierter Funktion mit CREATE FUNCTION RANDOM() simulieren. Mit den Zufallszahlen kann ich dann auch zufällige Strings von zufälliger Länge erzeugen.

So machte ich mich schlau wie Zufallszahlen erzeugt werden. Naja ich versuchte es, und am Ende fand ich etwas über Lineare Kongruenzgeneratoren. Hinter dem Wortungetüm verbirgt sich eine einfache Rechenanweisung die Zahlenreihenfolgen erzeugt, deren Werte “zufällig” erscheinen. Da sich die Werte in einer solchen Zahlenreihenfolge, in einem solchen Generator irgendwann wiederholen, wählt man Generatoren mit einer sehr großen Periode. Im Normalfall wird der Generator einmal mit einem Wert initialisiert. Anschließend berechnet man aus diesem den Folgewert, erzeugt aus dem Folgewert einen Folge-Folgewert und so weiter und so fort.

Ich wollte jedoch nicht die Folgewerte speichern, weshalb ich kurzerhand immer wieder neue initiale Werte verwendet habe. Das mag sich negativ auf die Wertestreung und “Zufälligkeit” auswirken, aber ich wollte jetzt endlich weiterkommen. Doch welchen “zufälligen” Anfangswert kann eine Datenbank liefern? Eine Datenbank liefert kaum eine zufällige, physikalische Größen außer der Zeit. Diese wollte ich dann auch verwenden in Form von Mikrosekunden, die der MySQL Server bis heute leider immer noch nicht zur Verfüngung stellt. Aber MaxDB kann das. Es blieb also nur die Frage, wie man in MaxDB an einen Timestamp kommt.

NOW(), TIMESTAMP() und TIMESTAMP

Ich wußte, daß es DEFAULT TIMESTAMP gibt. Ich zitiere aus dem Handbuch.


Specifications of Values (extended_value_spec) Locate the document in the library structure

Values are specified (extended_value_spec) using one of the keywords DEFAULT or STAMP or by specifying the values (value spec: Literals, parameter specifications, key words and so on).
Syntax

<extended_value_spec> ::= DEFAULT | STAMP | <value_spec>

</value_spec><value_spec> ::= <literal>
| <parameter_spec>
| NULL
| USER | USERGROUP
| SYSDBA | CURRENT_SCHEMA | UID
| [<schema_name>.]<sequence_name>.NEXTVAL
| [<schema_name>.]<sequence_name>.CURRVAL
| <table_name>.CURRVAL
| DATE | TIME | TIMESTAMP
| UTCDATE | TIMEZONE | UTCDIFF
| TRUE | FALSE
| TRANSACTION
Explanation
DEFAULT

DEFAULT identifies the default value for the column in a CREATE TABLE statement or ALTER TABLE statement. If one of these values is not defined, DEFAULT cannot be used to specify values.

The DEFAULT keyword can be used in the following SQL statements: INSERT statement, UPDATE statement

The DEFAULT keyword can be used in a DEFAULT predicate.

Und ich wußte, daß es eine Funktion TIMESTAMP() gibt. Und natürlich gibt es auch noch einen Datentyp TIMESTAMP, aber der liefer mir ja keinen Wert, den ich in einer benutzerdefinierten Funktion hätte benutzen können.


TIMESTAMP(a,b) Locate the document in the library structure

TIMESTAMP(a,b) is a function (extraction) that calculates from the expressions a and b a time stamp value comprising a date value, time value, and 0 microseconds.
	

Result of TIMESTAMP(a,b) Function

TIMESTAMP(a)
	

a must be a time stamp value or an alphanumeric value that matches the current time stamp format

The result is this time stamp value.

TIMESTAMP(a,b)
	

a must be a date value and b a time value (or alphanumeric value that matches the current format for date and time values).

The result is a time stamp value calculated from a date value, time value, and 0 microseconds.

a or b is the NULL value
	

NULL Value

Hmm, TIMESTAMP taucht nur in Verbindung mit DEFAULT im Handbuch auf und TIMESTAMP() ist nur in Verbindung mit Parametern beschrieben. Wie komme ich denn jetzt an einen Timestamp in einer benutzerdefinierten Funktion ohne einen Datensatz in einer Tabelle einzufügen?

Irgendwann probierte ich NOW() aus, wie von ott vorgeschlagen. NOW() ist nicht in der Liste der Datenbankfunktionen von MaxDB enthalten. Es steht dort nicht, weil es nur im Rahmen des ODBC-/SQLDBC-Interface implementiert ist. Es ist mir schleierhaft wieso man diese Funktion nicht auf dem Level des Kerns implementiert hat. Aber mir war es gleich, ich benutzte das SQL Studio, welches (wie sqlcli) per ODBC mit MaxDB kommuniziert. Meine Implementierung von RAND()/RANDOM() war jetzt endlich möglich unter Benutzung von NOW() und einer Stored Function.


CREATE FUNCTION SHOP.RANDOM RETURNS FIXED(38, 37) AS
  VAR randval FIXED(38, 37);
  DECLARE randresult CURSOR FOR 
     SELECT ((397204094 * MICROSECOND(NOW())) MOD 2147483647) / 2147483646 
       FROM DBADMIN.DUAL;
  FETCH FIRST randresult INTO :randval;
  CLOSE randresult;
  RETURN randval; 

Moooooment – wieso funktioniert das? Wenn NOW() eine ODBC-/SQLDBC-Funktion ist, wieso kann man es in einer benutzerdefinierten Funktion verwenden? Eine benutzerdefinierte Funktion (Stored Function) wird innerhalb der Datenbank gespeichert und ausgeführt. An der Ausführung des Funktionskörpers ist kein Client beteiligt. Nun, da sind ja noch die symbolischen Konstanten von MaxDB, die im Handbuch gekonnt unter dem [E]BNF-Syntaxbaustein extended_value_spec versteckt werden. NOW() läßt sich in TIMESTAMP übersetzen und das steht zur Verfügung.


SELECT 
  NULL,
  USER,
  USERGROUP,
  SYSDBA,
  CURRENT_SCHEMA,
  UID,
  DATE,
  TIME,
  TIMESTAMP,
  UTCDATE,
  TIMEZONE,
  UTCDIFF,
  TRUE,
  FALSE,
  TRANSACTION
FROM
  DUAL

Ich gebe ja gerne zu, daß ich das Handbuch nicht verstanden habe und das Feature dokumentiert ist. Aber ich fürchte, ich bin nicht anders als der Durschnittsbenutzer. Und vielleicht hat der Durschnittsbenutzer ähnliche Probleme wie ich.

Kleiner Nachtrag vom 23.11.: NOW() wird bei der semantischen Prüfung akzeptiert, weil es eine ODBC-Funktion ist. Im Katalog der Datenbank wird die angelegte Funktion mitsamt dem NOW() Aufruf so gespeichert, wie es definiert wurde. Bei der ersten Ausführung der Funktion wird NOW() gewandelt und das Compilat gecacht.

Variablen vs. CURSORS in MaxDB

Doch weiter im Text. Ich hatte mich jetzt bis zu dem Punkt durchgekämpft bei dem ich für jeden Aufruf eines SELECT eine “Zufallszahl” erhielt. Im obigen Beispiel habe ich einen Cursor verwendet, um die Zufallszahl abzugreifen. Man kann jedoch alternativ auch SELECT ... INTO ... FROM verwenden.


DROP FUNCTION RANDOM
//
CREATE FUNCTION RANDOM RETURNS FIXED(38,37) AS
  VAR randval FIXED(38,37);
  SELECT ((134775818 * MICROSECOND(NOW()) + 387) MOD 231) / 230 
     INTO :randval 
     FROM DBADMIN.DUAL;
  RETURN randval;
//  
SELECT NULL, RANDOM() FROM DUAL

AK Catalog information not found

Damit wäre mein Tag fast gerettet gewesen, wenn das Logfile des SQL Studio nicht gelegentlich folgendes gezeigt hätte.



++++ Execute +++++++++++++++++++++++++++++++
Auto Commit: On, SQL Mode: Internal, Isolation Level: Committed
DROP FUNCTION RANDOM
Statement successfully executed. No Result
Execution Time:  20:36:30.965 - 20:36:30.965  (00.000 sec)
++++ Execute +++++++++++++++++++++++++++++++
Auto Commit: On, SQL Mode: Internal, Isolation Level: Committed
CREATE FUNCTION RANDOM RETURNS FIXED(38,37) AS
  VAR randval FIXED(38,37);
  SELECT 
    ((134775818 * MICROSECOND(NOW()) + 387) MOD 231) / 230 
    INTO :randval 
    FROM DBADMIN.DUAL;
  RETURN randval;
Statement successfully executed. No Result
Execution Time:  20:36:30.995 - 20:36:31.004  (00.009 sec)
---- Error -------------------------------
Auto Commit: On, SQL Mode: Internal, Isolation Level: Committed
 General error;-9205 POS(1) System error: AK Catalog information not found:45000000000004F0001800
SELECT NULL, RANDOM() FROM DUAL

Was soll ich sagen? Nach der Ursache wird gerade gesucht. Bitte keine Bugreports einschicken. Ich werde bei Gegelenheit vom Ausgang berichten.

Nachtrag: http://www.sapdb.org/webpts?wptsdetail=yes&ErrorType=0&ErrorID=1136664 beschreibt den -9205 Fehler und schlägt einen Workaround vor. Gelöst in 7.6.00.15.

English language version?

If you’re looking for an english language summary of this blog entry, leave a comment.