(Hinweis zu mehren True-Steps) |
Markierung: 2017-Quelltext-Bearbeitung |
||
(21 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
== | ==Datenbankverbindungen in Kettle-Dateien== | ||
Nach der Installation von Spoon ist folgendes zu beachten: Beim Bearbeiten von Kettle Dateien mit Spoon wird die jeweilige dbconnection der lokalen "eduetl"-Datenbank in den KTR/KJB Dateien gespeichert. Vor dem Commit ins GIT muss diese entfern werden. Dafür gibt es ein Groovy-Script im Kernmodul in superx/scripts/groovy/copy_kettlejob.groovy. Dieses wiederum benötigte groovy im PATH, daher muss man | Nach der Installation von Spoon ist folgendes zu beachten: Beim Bearbeiten von Kettle Dateien mit Spoon wird die jeweilige dbconnection der lokalen "eduetl"-Datenbank in den KTR/KJB Dateien gespeichert. Vor dem Commit ins GIT muss diese entfern werden. | ||
===mit Groovy === | |||
Dafür gibt es ein Groovy-Script im Kernmodul in superx/scripts/groovy/copy_kettlejob.groovy. Dieses wiederum benötigte groovy im PATH, daher muss man | |||
# groovy installieren, z.B. in /home/superx/tools/groovy/groovy-2.4.10 | # groovy installieren, z.B. in /home/superx/tools/groovy/groovy-2.4.10 | ||
Zeile 16: | Zeile 18: | ||
cp -a sap_hr_sva/* $SVA_PFAD/etl/sap_hr | cp -a sap_hr_sva/* $SVA_PFAD/etl/sap_hr | ||
==Grundsätzliches zum Datenflow | === xmlstarlet === | ||
Komfortabler geht es mit xmlstarletAnwendung | |||
Hier ein Script removeConnection.x | |||
<pre> | |||
#!/bin/bash | |||
#sudo apt install xmlstarlet | |||
for file in *.kjb *.ktr; do | |||
[ -f "$file" ] || continue # Überspringt, falls keine Dateien existieren | |||
##durch das && wird bewirkt, dass bei Fehler von xmlstarlet Originaldatei nicht überschrieben wird | |||
## Connection-Knoten, bei denen auch die aufgeführten Unterknoten existieren, werden enfernt | |||
xmlstarlet ed -d "//connection[name and server and type and access and database]" "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" | |||
done | |||
</pre> | |||
=Grundsätzliches zum Datenflow= | |||
Nach einer SQL-Aktion mit dem Löschen von Daten muss man einen Warten Step einbauen.Ab einem bestimmten Step kann man in mehrere folgende Steps verzweigen, denen allen der gesamt Flow zur Verfügung steht. | Nach einer SQL-Aktion mit dem Löschen von Daten muss man einen Warten Step einbauen.Ab einem bestimmten Step kann man in mehrere folgende Steps verzweigen, denen allen der gesamt Flow zur Verfügung steht. | ||
Zeile 29: | Zeile 45: | ||
[[Datei:Kettle_datenflow1.png]] | [[Datei:Kettle_datenflow1.png]] | ||
=Informationen zu einzelnen Steps= | |||
==Update Step== | |||
siehe "Aus Exceldatei in Datenbank schreiben / Aus mehreren Sheets | siehe "Aus Exceldatei in Datenbank schreiben / Aus mehreren Sheets | ||
==Filter Step (ähnlich SQL where)== | |||
siehe unter "Aus Exceldatei in Datenbank schreiben / Erst bestimme Zeilen rausfiltern und dann Update | siehe unter "Aus Exceldatei in Datenbank schreiben / Erst bestimme Zeilen rausfiltern und dann Update | ||
Zeile 56: | Zeile 72: | ||
sap_load_stellen | sap_load_stellen | ||
==Group by Step== | |||
Max/Min Ermitteln - siehe "Aus Exceldatei in Datenbank schreiben / Max(Spalte) ermitteln und nachtragen (Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_austrittsdatum.ktr) | Max/Min Ermitteln - siehe "Aus Exceldatei in Datenbank schreiben / Max(Spalte) ermitteln und nachtragen (Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_austrittsdatum.ktr) | ||
Zeile 66: | Zeile 82: | ||
Standardmäßig müssen Datenzeilen vorher sortiert sein, damit es richtig klappt, man kann "Fehlfunktion" aber nutzen, um z.B. "Zeitscheiben" zu ermitteln, s.u. | Standardmäßig müssen Datenzeilen vorher sortiert sein, damit es richtig klappt, man kann "Fehlfunktion" aber nutzen, um z.B. "Zeitscheiben" zu ermitteln, s.u. | ||
==Select Values (Felder umbenennen/Datentyp ändern/ Feld kopieren)== | |||
Hiermit kann man Feldnamen umbennen oder auch Datentypen ändern im Reiter "Metadata" | Hiermit kann man Feldnamen umbennen oder auch Datentypen ändern im Reiter "Metadata" | ||
Zeile 72: | Zeile 88: | ||
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_habil.ktr | Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_habil.ktr | ||
Felder, die im weiteren benötigt werden, müssen einfach aufgeführt werden. z.B: Einfach "Planstelle" bei "select&alter" unter Fieldname - andere Spalten leer.<br />Es werden nur die aufgeführten Felder weiter im Stream übergeben (vergl. sva/etl/sap_hr/sap_load_stellen.ktr) | |||
Zeile 80: | Zeile 99: | ||
Vergl. sap_load_stamm_bvl | Vergl. sap_load_stamm_bvl | ||
==User Defined Java Expression== | |||
Siehe Beispiel unter "Aus Excel in Datenbank schreiben" / Den kleineren/größeren Wert aus zwei Spalten ermitteln | Siehe Beispiel unter "Aus Excel in Datenbank schreiben" / Den kleineren/größeren Wert aus zwei Spalten ermitteln | ||
Zeile 87: | Zeile 106: | ||
==Get Value from Sequence== | |||
Kann man z.B. für erzeugen einer künstlichen ID gebrauchen, siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen | Kann man z.B. für erzeugen einer künstlichen ID gebrauchen, siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen | ||
Zeile 94: | Zeile 113: | ||
Um die erzeugte ID aus der Datenbank auszulesen nutzt man Database Lookup | Um die erzeugte ID aus der Datenbank auszulesen nutzt man Database Lookup | ||
==Database Lookup== | |||
Kann z..B: verwendet werden, um eine ID aus der Datenbank zu holen. | Kann z..B: verwendet werden, um eine ID aus der Datenbank zu holen. | ||
==Microsoft Excel Input== | |||
Der "Microsoft Excel Input" Step kann Excel Dateien lesen und weiterverarbeiten. | Der "Microsoft Excel Input" Step kann Excel Dateien lesen und weiterverarbeiten. | ||
==Microsoft Excel Input Reiter Files== | |||
Wählen Sie beim "Spread Sheet type (engine)" am besten "Excel 2007 XLSX (Apache POI)". Grund: diese Importfunktion unterstützt sowohl "*.xls" als auch "*.xlsx". | Wählen Sie beim "Spread Sheet type (engine)" am besten "Excel 2007 XLSX (Apache POI)". Grund: diese Importfunktion unterstützt sowohl "*.xls" als auch "*.xlsx". | ||
Zeile 116: | Zeile 135: | ||
findet alle Dateien, die mit "Daten" bzw. "daten" beginnen und mit ".xls" enden. | findet alle Dateien, die mit "Daten" bzw. "daten" beginnen und mit ".xls" enden. | ||
==Microsoft Excel Input Reiter Sheets== | |||
Dann wählen Sie das jew. Sheet aus, bzw. mehrere. Bei "Start row" und "Start column" beachten Sie bitte, dass Kettle bei 0 beginnt, nicht bei 1. | Dann wählen Sie das jew. Sheet aus, bzw. mehrere. Bei "Start row" und "Start column" beachten Sie bitte, dass Kettle bei 0 beginnt, nicht bei 1. | ||
==Microsoft Excel Input Reiter Content== | |||
Content / "Stop on empty row" kann ganz nütlich sein, hatten schon den Fall, dass plötzlich in Zeile 106.234 noch ein verwaister Wert stand, kostet viel Performance/Arbeitsspeicher | Content / "Stop on empty row" kann ganz nütlich sein, hatten schon den Fall, dass plötzlich in Zeile 106.234 noch ein verwaister Wert stand, kostet viel Performance/Arbeitsspeicher | ||
==Microsoft Excel Input Reiter Fields== | |||
Bei Datentyp ggfs. TrimType "both" und für Ganzzahlen die zu Strings werden sollen Format # hilfreich | Bei Datentyp ggfs. TrimType "both" und für Ganzzahlen die zu Strings werden sollen Format # hilfreich | ||
Zeile 136: | Zeile 155: | ||
<span style="color: #000000;">Wenn ein ClassCast Fehler kommt, muss man das logging auf ganz detailliert stellen, dann wird die Zeilennummer angezeigt.<br />-> Datumsfelder dürfen anscheinend nicht vor 1.1.1970 sein, Datum 1.1.1700 kam ClassCastException, 1.1.1999 nicht</span> | <span style="color: #000000;">Wenn ein ClassCast Fehler kommt, muss man das logging auf ganz detailliert stellen, dann wird die Zeilennummer angezeigt.<br />-> Datumsfelder dürfen anscheinend nicht vor 1.1.1970 sein, Datum 1.1.1700 kam ClassCastException, 1.1.1999 nicht</span> | ||
==Table Input== | |||
siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen | siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen | ||
db/module/sva/etl/sap_hr/sap_load_stamm_laufbahn.kjb | db/module/sva/etl/sap_hr/sap_load_stamm_laufbahn.kjb | ||
==Insert / Update Step== | |||
Kann verwendet werden, wenn man für bestehende Datensätze einen Update machen möchte und für noch nicht vorhandene Einträge einen Insert.<br />Aber auch, wenn man gar keine Updates machen will, sondern nur Inserts wenn noch kein Eintrag vorhanden ist. | Kann verwendet werden, wenn man für bestehende Datensätze einen Update machen möchte und für noch nicht vorhandene Einträge einen Insert.<br />Aber auch, wenn man gar keine Updates machen will, sondern nur Inserts wenn noch kein Eintrag vorhanden ist. | ||
Zeile 148: | Zeile 167: | ||
Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_beschstellen.ktr Step Insert sva_inst_neu | Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_beschstellen.ktr Step Insert sva_inst_neu | ||
==Stream Lookup== | |||
Wenn man Daten in Datenbanktabelle hat und ergänzen will um Input aus Exceldatei. | Wenn man Daten in Datenbanktabelle hat und ergänzen will um Input aus Exceldatei. | ||
vergl. [https://wiki.pentaho.com/display/EAI/Stream+Lookup Stream Lookup]<br />Problem kann keine komplexen where Bedingen wie < oder > machen | vergl. [https://wiki.pentaho.com/display/EAI/Stream+Lookup Stream Lookup]<br />Problem kann keine komplexen where Bedingen wie < oder > machen | ||
==Database Join / Set Variable (eine Variable aus Datenbank lesen)== | |||
'''Database Join''' - Dieser Step kann benutzt werden, um eine Variable aus der Datenbank zu lesen und dem Flow hinzuzufügen. | '''Database Join''' - Dieser Step kann benutzt werden, um eine Variable aus der Datenbank zu lesen und dem Flow hinzuzufügen. | ||
Zeile 164: | Zeile 183: | ||
Vergl. sap_load_stamm_laufbahn.ktr | Vergl. sap_load_stamm_laufbahn.ktr | ||
Da jedoch die einzelnen Transformationsschritte evtl. parallel laufen, muss man einen | Da jedoch die einzelnen Transformationsschritte evtl. parallel laufen, muss man einen übergeordneten Job erstellen, der in der ersten Transformation zunächst die Variable liest und dann in der/den nächsten die Variable ausliest. | ||
Vergl. sap_load_stamm_laufbahn.kjb und unten "eine künstliche ID erzeugen" | Vergl. sap_load_stamm_laufbahn.kjb und unten "eine künstliche ID erzeugen" | ||
==Get Variable== | |||
s.auch vorheriges Kapitel<br> | |||
Da jedoch die einzelnen Transformationsschritte evtl. parallel laufen, muss man einen übergeordneten Job erstellen, der in der ersten Transformation zunächst die Variable liest und dann in der/den nächsten die Variable ausliest. | |||
Also in 1. Transformation "Set Variable Step"<br> | |||
Dann in der 2. Transformation z.B. Lesen aus Excel, "Get Variable" und wenn man es zum Schreiben braucht, "Join rows (cartesian product)". | |||
Beispiel | |||
[[Image:kettle_getvar_join_rows.png]] | |||
Beispiel: sos/etl/gewichtung_fach_abschluss_fak/gewichtung_fach_abschluss_fak.kjb | |||
==Calculator (Werte berechnen, z.B. Multiplizieren)== | |||
Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.<br />Beispiel sap_load_pfi | Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.<br />Beispiel sap_load_pfi | ||
==Text FIle Output (CSV-Dateien erzeugen)== | |||
relativ selbsterklärend. | relativ selbsterklärend. | ||
Zeile 182: | Zeile 212: | ||
[[Datei:Textoutput_format_trim.png]] | [[Datei:Textoutput_format_trim.png]] | ||
==Value Mapper (Werte transformieren)== | |||
Beispiel String "wiss. Dienst" -> 1 sonst 0 in sap_load_wiss_adt.ktr | Beispiel String "wiss. Dienst" -> 1 sonst 0 in sap_load_wiss_adt.ktr | ||
==String Cut== | |||
Hiermit kann man einen Ausschnitt von Strings ermitteln, Beispiel ersten zwei Stellen | Hiermit kann man einen Ausschnitt von Strings ermitteln, Beispiel ersten zwei Stellen | ||
[https://wiki.pentaho.com/display/EAI/Strings+cut https://wiki.pentaho.com/display/EAI/Strings+cut] | [https://wiki.pentaho.com/display/EAI/Strings+cut https://wiki.pentaho.com/display/EAI/Strings+cut] | ||
==Execute SQL Script== | |||
SQL-Befehle ausführen.Erfahrung: direkt in Hauptjob sap_hr_sva.ktr nicht durchgelaufen (System hängt). | SQL-Befehle ausführen.Erfahrung: direkt in Hauptjob sap_hr_sva.ktr nicht durchgelaufen (System hängt). | ||
Zeile 196: | Zeile 226: | ||
sap_init_db.ktr | sap_init_db.ktr | ||
==Generate Rows (Werte manuell erzeugen)== | |||
feste Schlüssel z.B. _unb für unbekannt manuell erzeugenBeispiel sap_load_stamm_bvl | feste Schlüssel z.B. _unb für unbekannt manuell erzeugenBeispiel sap_load_stamm_bvl | ||
=Aus Exceldatei in Datenbank schreiben= | |||
==Aus einem Sheet== | |||
Um Daten zu löschen legen man einen Step "Execute SQL script" an. Beispielhafter Inhalt delete from sva_pbl_neu; | Um Daten zu löschen legen man einen Step "Execute SQL script" an. Beispielhafter Inhalt delete from sva_pbl_neu; | ||
Zeile 216: | Zeile 246: | ||
==Aus mehreren Sheets== | |||
Um aus mehreren Sheets zu lesen, muss man pro Sheet eine Transformation anlegen, da sonst im Flow die Felder aus dem ersten Sheet anscheinend nicht vom zweiten Sheet überschrieben werden. | Um aus mehreren Sheets zu lesen, muss man pro Sheet eine Transformation anlegen, da sonst im Flow die Felder aus dem ersten Sheet anscheinend nicht vom zweiten Sheet überschrieben werden. | ||
Zeile 227: | Zeile 257: | ||
Entry to update with following key could not be found: 47[[Datei:Kettle_update_step_mit_lookup.png]] | Entry to update with following key could not be found: 47[[Datei:Kettle_update_step_mit_lookup.png]] | ||
==Max(Spalte) ermitteln und nachtragen== | |||
Im Beispiel soll das austrittsdatum aus einem Excelsheet nachgetragen werden, dazu muss man für jede Personalnummer das max(Ende) ermitteln. | Im Beispiel soll das austrittsdatum aus einem Excelsheet nachgetragen werden, dazu muss man für jede Personalnummer das max(Ende) ermitteln. | ||
Zeile 242: | Zeile 272: | ||
==Calculator (Werte berechnen, z.B. Multiplizieren)== | |||
Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.<br />Beispiel sap_load_pfi | Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.<br />Beispiel sap_load_pfi | ||
==Erst bestimme Zeilen rausfiltern und dann Update== | |||
Im Beispiel soll das Eintrittsdatum aus einem Excelsheet ermittelt werden, dazu muss man für jede Personalnummer nur die Zeilen mit DaArt=01 lesen und dann das min(Datum) bestimmen. | Im Beispiel soll das Eintrittsdatum aus einem Excelsheet ermittelt werden, dazu muss man für jede Personalnummer nur die Zeilen mit DaArt=01 lesen und dann das min(Datum) bestimmen. | ||
Zeile 262: | Zeile 292: | ||
==Bei mehreren Datenzeilen Neuste raussuchen (Gültigkeit)== | |||
Im Beispiel soll zu Personalgrunddaten die aktuelleste Adresse PLZ etc ermittelt werden. Dazu gibt es ein Sheet, das pro Personalnummer mehrere Einträge mit Gültigkeiten (Beginn/Ende) geben kann. | Im Beispiel soll zu Personalgrunddaten die aktuelleste Adresse PLZ etc ermittelt werden. Dazu gibt es ein Sheet, das pro Personalnummer mehrere Einträge mit Gültigkeiten (Beginn/Ende) geben kann. | ||
Zeile 281: | Zeile 311: | ||
==Aus String Jahr "2000" ein Datum machen (1.1.2000)== | |||
Als erstes muss man einen FIlter machen mit der Einstellung nur Datensätze where is not null. | Als erstes muss man einen FIlter machen mit der Einstellung nur Datensätze where is not null. | ||
Zeile 295: | Zeile 325: | ||
==Den kleineren/größeren Wert aus zwei Spalten ermitteln== | |||
Im Beispiel gibt es zwei Felder "Auf Zeit" und "Auf Lebenszeit", das Feld mit dem kleineren Wert in jeder Zeile soll genommen werden. | Im Beispiel gibt es zwei Felder "Auf Zeit" und "Auf Lebenszeit", das Feld mit dem kleineren Wert in jeder Zeile soll genommen werden. | ||
Zeile 307: | Zeile 337: | ||
Als Nächstes ein "User Defined Java Expression" mit der JavaExpression | Als Nächstes ein "User Defined Java Expression" mit der JavaExpression | ||
(lebenszeit | (lebenszeit=null&&auf_zeit=null)?null:(lebenszeit=null&&auf_zeit!=null)?auf_zeit:(lebenszeit!=null&&auf_zeit=null)?lebenszeit:new Long(Math.min(lebenszeit,auf_zeit)) | ||
Zeile 322: | Zeile 352: | ||
==Feld wird zu Decimalzahl 1020,0 transformiert soll aber String sein== | |||
Beim Einlesen von Kostenstellen kam es zu Problem, dass dass Feld zu einer Decimalzahl wie 1020,0 transformiert wurde, umstellen auf Datentyp Integer beim Einlesen oder entsprechender "Select Values" Step mit Meta-Datenänderung auf String brachte nichts. | Beim Einlesen von Kostenstellen kam es zu Problem, dass dass Feld zu einer Decimalzahl wie 1020,0 transformiert wurde, umstellen auf Datentyp Integer beim Einlesen oder entsprechender "Select Values" Step mit Meta-Datenänderung auf String brachte nichts. | ||
Zeile 331: | Zeile 361: | ||
[[Datei:Kettle_adapt_kostenstelle.png]] | [[Datei:Kettle_adapt_kostenstelle.png]] | ||
Die Java Expression Kostenst | Die Java Expression Kostenst=null?null:Kostenst.trim().replaceAll(",0","") entfernte auch Leerzeichen am Anfang. | ||
Beispiel db/module/sva/etl/sap_hr/sap_load_pfi.ktr | Beispiel db/module/sva/etl/sap_hr/sap_load_pfi.ktr | ||
Zeile 337: | Zeile 367: | ||
==Eine Künstliche ID erzeugen== | |||
Für das Excelfeld "Laufbahngruppe" gibt es nur Bezeichnungstext, keinen Schlüssel. | Für das Excelfeld "Laufbahngruppe" gibt es nur Bezeichnungstext, keinen Schlüssel. | ||
Zeile 372: | Zeile 402: | ||
sap_load_stamm_laufbahn2.ktr | sap_load_stamm_laufbahn2.ktr | ||
==Werte nachtragen (Beispiel erst Kosten- dann Beschäftigungsstellen)== | |||
Aufgabe: Zunächst sollen aus einem Sheet alle Kostenstellen eingetragen werden, dann bei den Beschäftigungsstellen diejenigen nachgetragen werden, die noch nicht als Kostenstellen eingetragen wurden. | Aufgabe: Zunächst sollen aus einem Sheet alle Kostenstellen eingetragen werden, dann bei den Beschäftigungsstellen diejenigen nachgetragen werden, die noch nicht als Kostenstellen eingetragen wurden. | ||
Zeile 391: | Zeile 421: | ||
=="Zeitscheiben" ermitteln== | |||
Problem in sva_pbv_neu aus IT0016 gibt es eine Zeitscheibe | Problem in sva_pbv_neu aus IT0016 gibt es eine Zeitscheibe | ||
Zeile 421: | Zeile 451: | ||
vergl. db/module/sva/etl/sap_hr/sap_load_pbv_art_dienstart.ktr | vergl. db/module/sva/etl/sap_hr/sap_load_pbv_art_dienstart.ktr | ||
=Literaturtipps= | |||
* Pentaho Kettle Homepage: http://kettle.pentaho.com/ | |||
* Kettle FAQs: http://wiki.pentaho.com/display/EAI/Frequently+Asked+Questions | |||
* Casters, M., Bouman,R., von Dongen, J. (2010). Pentaho Kettle Solutions. Building Open Source ETL Solutions with Pentaho Data Integration. Indianapolis, IN: Wiley. | |||
* Haneke, U. et al. (2010). Open Source Business Intelligence. Möglichkeiten, Chancen und Risiken quelloffener BI-Lösungen. Wien: Hanser | |||
* Roldan, M.C. (2013). Pentaho Data Integration Second Edition. Beginner's Guide. Birmingham, UK: Packt Publishing | |||
* Javascript Guide des Mozilla Projektes: https://developer.mozilla.org/En/Core_JavaScript_1.5_Guide | |||
* Javascript Anleitung im SelfHTML-Projekt: http://de.selfhtml.org/javascript/index.htm |
Aktuelle Version vom 17. März 2025, 11:42 Uhr
Datenbankverbindungen in Kettle-Dateien
Nach der Installation von Spoon ist folgendes zu beachten: Beim Bearbeiten von Kettle Dateien mit Spoon wird die jeweilige dbconnection der lokalen "eduetl"-Datenbank in den KTR/KJB Dateien gespeichert. Vor dem Commit ins GIT muss diese entfern werden.
mit Groovy
Dafür gibt es ein Groovy-Script im Kernmodul in superx/scripts/groovy/copy_kettlejob.groovy. Dieses wiederum benötigte groovy im PATH, daher muss man
- groovy installieren, z.B. in /home/superx/tools/groovy/groovy-2.4.10
- groovy in den PATH mit aufnehmen:
PATH=$PATH:/home/superx/tools/groovy/groovy-2.4.10/bin export PATH
Dann legt man sich einen lokalen "Arbeitsorder" an, der nicht im git liegt, und erzeugt ein Unterverzeichnis für die temp. Dateien. Hier das Beispiel eines Jobs im SVA Modul mit dem Unterverzeichnis sap_hr_sva
Das Script wird dann so aufgerufen :
groovy ~/git/superx/scripts/groovy/copy_kettlejob.groovy --strip-connections $SVA_PFAD/etl/sap_hr/sap_hr_sva.kjb sap_hr_sva
Die kjb-/ktr Dateien werden aus dem git kopiert ins Unterverzeichnis sap_hr_sva, und dort werden die Connections entfernt. Wenn man die Entwicklung beendet hat, kann man diese Dateien ins richtige git Verzeichnis kopieren und committen:
cp -a sap_hr_sva/* $SVA_PFAD/etl/sap_hr
xmlstarlet
Komfortabler geht es mit xmlstarletAnwendung Hier ein Script removeConnection.x
#!/bin/bash #sudo apt install xmlstarlet for file in *.kjb *.ktr; do [ -f "$file" ] || continue # Überspringt, falls keine Dateien existieren ##durch das && wird bewirkt, dass bei Fehler von xmlstarlet Originaldatei nicht überschrieben wird ## Connection-Knoten, bei denen auch die aufgeführten Unterknoten existieren, werden enfernt xmlstarlet ed -d "//connection[name and server and type and access and database]" "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" done
Grundsätzliches zum Datenflow
Nach einer SQL-Aktion mit dem Löschen von Daten muss man einen Warten Step einbauen.Ab einem bestimmten Step kann man in mehrere folgende Steps verzweigen, denen allen der gesamt Flow zur Verfügung steht.
(Methode "Copy" auswählen, nicht "Distribute").
Komplexes Beispiel
siehe unter Aus Excel in Datenbank schreiben / Werte nachtragen (Beispiel erst Kosten- dann Beschäftigungsstellen)
Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_beschstellen.ktr Step Insert sva_inst_neu
Informationen zu einzelnen Steps
Update Step
siehe "Aus Exceldatei in Datenbank schreiben / Aus mehreren Sheets
Filter Step (ähnlich SQL where)
siehe unter "Aus Exceldatei in Datenbank schreiben / Erst bestimme Zeilen rausfiltern und dann Update
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_eintrittssdatum.ktr
Man kann auch mehrere Bedingungen angeben, dazu klickt man auf das kleine rot umrandete Plus Zeichen.
Man sollte darauf achten, dass bei Verknüpfung zu folgenden Schritten nicht "Main Ouput", sondern "Result is TRUE" ausgewählt wird, dann wird im Screenshot bei "send true to data step" der entsprechende Schritt eingetragen.
Man erkennt auch daran, dass grünes Häkchen an Verbindungslinie.
Wenn ein Filter Rows mit True die Ausgabe an mehrere Schritte liefern sollen, geht das nicht, man kann immer nur an einen Schritt liefern.
Andere Schritte bekommen nicht das "True" Häkchen und machen einfach nichts!
Daher muss man einen "Dummy-Schritt" einfügen:
sap_load_stellen
Group by Step
Max/Min Ermitteln - siehe "Aus Exceldatei in Datenbank schreiben / Max(Spalte) ermitteln und nachtragen (Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_austrittsdatum.ktr)
Kann noch mehr auch ersten/letzen Wert ermitteln, interessant für Gültigkeiten
siehe Bei mehreren Datenzeilen Neuste raussuchen (Gültigkeit) ( Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_wohnort.ktr)
Standardmäßig müssen Datenzeilen vorher sortiert sein, damit es richtig klappt, man kann "Fehlfunktion" aber nutzen, um z.B. "Zeitscheiben" zu ermitteln, s.u.
Select Values (Felder umbenennen/Datentyp ändern/ Feld kopieren)
Hiermit kann man Feldnamen umbennen oder auch Datentypen ändern im Reiter "Metadata"
siehe auch unter "Aus Exceldatei in Datenbank schreiben / Aus String Jahr "2000" ein Datum machen (1.1.2000)
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_habil.ktr
Felder, die im weiteren benötigt werden, müssen einfach aufgeführt werden. z.B: Einfach "Planstelle" bei "select&alter" unter Fieldname - andere Spalten leer.
Es werden nur die aufgeführten Felder weiter im Stream übergeben (vergl. sva/etl/sap_hr/sap_load_stellen.ktr)
Auch Felder können kopiert werden, dazu zweimal aufführen, als Feld selbst und mit neuem Namen,
Beispiel TarifGruppe (TrfGr) soll standardmäßig auch als Name genommen werden (mit einem generateRows Step aber eigener Name für Schlüssel _unb)
Vergl. sap_load_stamm_bvl
User Defined Java Expression
Siehe Beispiel unter "Aus Excel in Datenbank schreiben" / Den kleineren/größeren Wert aus zwei Spalten ermitteln
weiterhin Beispiel unter "Aus Excel in Datenbank schreiben / Feld wird zu Decimalzahl 1020,0 transformiert soll aber String sein
Get Value from Sequence
Kann man z.B. für erzeugen einer künstlichen ID gebrauchen, siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen
db/module/sva/etl/sap_hr/sap_load_stamm_laufbahn.ktr.
Um die erzeugte ID aus der Datenbank auszulesen nutzt man Database Lookup
Database Lookup
Kann z..B: verwendet werden, um eine ID aus der Datenbank zu holen.
Microsoft Excel Input
Der "Microsoft Excel Input" Step kann Excel Dateien lesen und weiterverarbeiten.
Microsoft Excel Input Reiter Files
Wählen Sie beim "Spread Sheet type (engine)" am besten "Excel 2007 XLSX (Apache POI)". Grund: diese Importfunktion unterstützt sowohl "*.xls" als auch "*.xlsx".
Wenn Sie z.B. alle Excel-Dateien in einem Verzeichnis einlesen wollen, geben Sie die Wildcards an:
.*\.xls .*\.xlsx
Damit lädt der "Microsoft Excel Input" Step alle Dateien, die er mit den Wildcards findet.
Andere Beispiele für Wildcards:
(?i)daten.+\.xls
findet alle Dateien, die mit "Daten" bzw. "daten" beginnen und mit ".xls" enden.
Microsoft Excel Input Reiter Sheets
Dann wählen Sie das jew. Sheet aus, bzw. mehrere. Bei "Start row" und "Start column" beachten Sie bitte, dass Kettle bei 0 beginnt, nicht bei 1.
Microsoft Excel Input Reiter Content
Content / "Stop on empty row" kann ganz nütlich sein, hatten schon den Fall, dass plötzlich in Zeile 106.234 noch ein verwaister Wert stand, kostet viel Performance/Arbeitsspeicher
Microsoft Excel Input Reiter Fields
Bei Datentyp ggfs. TrimType "both" und für Ganzzahlen die zu Strings werden sollen Format # hilfreich
Ganz wichtige Erfahrungen:
Wenn Spaltenreihenfolge in ExcelDatei nicht mehr stimmt, merkt das Kettle anscheinend nicht an Bezeichnung in Header.
Arbeitet wohl "stupide" nach Reihenfolge, d.h. Spalte 1 soll im Kettle-Transformation den Namen "gebaeudenr" erhalten, wenn in der Exceldatei plötzlich "geschossnr" als Erstes kommt, merkt das System das nicht, spielt einfach falsch ein!!
Man kann bei Content Startspalte angeben, wenn man die Anfangsspalten nicht braucht. Spalte überspringen/aus LIste rauslöschen, funktionierte nicht, wenn man spätere Spalten noch brauchte, nur wenn man z.B. ab Spalte 6 gar nicht mehr braucht.
Wenn ein ClassCast Fehler kommt, muss man das logging auf ganz detailliert stellen, dann wird die Zeilennummer angezeigt.
-> Datumsfelder dürfen anscheinend nicht vor 1.1.1970 sein, Datum 1.1.1700 kam ClassCastException, 1.1.1999 nicht
Table Input
siehe unter Aus Excel in Datenbank schreiben / Eine Künstliche ID erzeugen
db/module/sva/etl/sap_hr/sap_load_stamm_laufbahn.kjb
Insert / Update Step
Kann verwendet werden, wenn man für bestehende Datensätze einen Update machen möchte und für noch nicht vorhandene Einträge einen Insert.
Aber auch, wenn man gar keine Updates machen will, sondern nur Inserts wenn noch kein Eintrag vorhanden ist.
siehe unter Aus Excel in Datenbank schreiben / Werte nachtragen (Beispiel erst Kosten- dann Beschäftigungsstellen)
Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_beschstellen.ktr Step Insert sva_inst_neu
Stream Lookup
Wenn man Daten in Datenbanktabelle hat und ergänzen will um Input aus Exceldatei.
vergl. Stream Lookup
Problem kann keine komplexen where Bedingen wie < oder > machen
Database Join / Set Variable (eine Variable aus Datenbank lesen)
Database Join - Dieser Step kann benutzt werden, um eine Variable aus der Datenbank zu lesen und dem Flow hinzuzufügen.
Beispiel: select nvl(max(apnr::integer)+1,1) as laufbahn_max_apnr from sva_cifx where key=607;
Number of rows to return: 1
Dann "Set Variable Step" (Fieldname: laufbahn_max_apnr, Variable name: LAUFBAHN_MAX_APNR.
Vergl. sap_load_stamm_laufbahn.ktr
Da jedoch die einzelnen Transformationsschritte evtl. parallel laufen, muss man einen übergeordneten Job erstellen, der in der ersten Transformation zunächst die Variable liest und dann in der/den nächsten die Variable ausliest.
Vergl. sap_load_stamm_laufbahn.kjb und unten "eine künstliche ID erzeugen"
Get Variable
s.auch vorheriges Kapitel
Da jedoch die einzelnen Transformationsschritte evtl. parallel laufen, muss man einen übergeordneten Job erstellen, der in der ersten Transformation zunächst die Variable liest und dann in der/den nächsten die Variable ausliest.
Also in 1. Transformation "Set Variable Step"
Dann in der 2. Transformation z.B. Lesen aus Excel, "Get Variable" und wenn man es zum Schreiben braucht, "Join rows (cartesian product)".
Beispiel
Beispiel: sos/etl/gewichtung_fach_abschluss_fak/gewichtung_fach_abschluss_fak.kjb
Calculator (Werte berechnen, z.B. Multiplizieren)
Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.
Beispiel sap_load_pfi
Text FIle Output (CSV-Dateien erzeugen)
relativ selbsterklärend.
Bei Zahlwerten kann Format 0.00 wichtig sein, damit amerikanisches und nicht deutsches Trennzeichen genutzt wird.
Bei Zahlen die Strings sind ggfs. auch Format # und Trim Type Both
Value Mapper (Werte transformieren)
Beispiel String "wiss. Dienst" -> 1 sonst 0 in sap_load_wiss_adt.ktr
String Cut
Hiermit kann man einen Ausschnitt von Strings ermitteln, Beispiel ersten zwei Stellen https://wiki.pentaho.com/display/EAI/Strings+cut
Execute SQL Script
SQL-Befehle ausführen.Erfahrung: direkt in Hauptjob sap_hr_sva.ktr nicht durchgelaufen (System hängt).
In einzelnen Job gepackt und Option "execute for each row" aktiviert, dann gings
sap_init_db.ktr
Generate Rows (Werte manuell erzeugen)
feste Schlüssel z.B. _unb für unbekannt manuell erzeugenBeispiel sap_load_stamm_bvl
Aus Exceldatei in Datenbank schreiben
Aus einem Sheet
Um Daten zu löschen legen man einen Step "Execute SQL script" an. Beispielhafter Inhalt delete from sva_pbl_neu;
Als Nächstes "Block until steps finishes"
- Dann "Microsoft Excel Input" -wichtig, Datentypen kontrollieren, S. unter Informationen zu einzelnen Steps / Microsoft Excel Input
Weiterhin war die Erfahrung, dass man bei Fields "Get Fields from Header row" Alle Felder drin lassen sollte, nicht nur die tatsächlich benötigten.
Andernfalls schien Kettle durcheinander zu kommen und meldete z.B. für Tarifart (String) falscher Datentyp (Date), weil er die falsche Spalte gelesen hat.
- Dann Table Output
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd.ktr
Aus mehreren Sheets
Um aus mehreren Sheets zu lesen, muss man pro Sheet eine Transformation anlegen, da sonst im Flow die Felder aus dem ersten Sheet anscheinend nicht vom zweiten Sheet überschrieben werden.
Beispiel db/module/sva/etl/sap_hr/sap_load_pbl.ktr und sap_load_pbl2.ktr.
Die Informationen aus dem zweiten Sheet werden per "Update Step" nachgetragen.
Wenn in dem zweiten Excelsheet nicht definitiv alle primary keys enthalten sind, die im ersten sind, muss man "Skip Lookup" aktivieren, sonst kommt eine Fehlermeldung, dass z.B. für eine Personalnummer keine Amts-/Dienstbzeichnung gefunden wurden.
Entry to update with following key could not be found: 47
Max(Spalte) ermitteln und nachtragen
Im Beispiel soll das austrittsdatum aus einem Excelsheet nachgetragen werden, dazu muss man für jede Personalnummer das max(Ende) ermitteln.
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_austrittsdatum.ktr
Dies geht in dem man zunächst die Exceldatei einliest.
Dann ist für das "Group by" zunächst ein "Sort Rows" nötig.
Anschließend kommt ein "Group By"-Step
Und zum Schluss ein "Update"-Step - hier wieder "Skip lookup" aktivieren, siehe unter Aus Exceldatei in Datenbank schreiben / Aus mehreren Sheets
Calculator (Werte berechnen, z.B. Multiplizieren)
Calculator Step, Funktion A*B, Field A "VAE" vorher Konstante c100 mit Wert 100 hinzugefügt als Wert B, um statt Prozentwert 0,27 eine 27 zu erhalten.
Beispiel sap_load_pfi
Erst bestimme Zeilen rausfiltern und dann Update
Im Beispiel soll das Eintrittsdatum aus einem Excelsheet ermittelt werden, dazu muss man für jede Personalnummer nur die Zeilen mit DaArt=01 lesen und dann das min(Datum) bestimmen.
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_eintrittssdatum.ktr
Dies geht in dem man zunächst die Exceldatei einliest.
Dann folgt ein "Filter Rows" auf DatAr=01
Dann ist für das "Group by" zunächst ein "Sort Rows" nötig.
Anschließend kommt ein "Group By"-Step
Und zum Schluss ein "Update"-Step - hier wieder "Skip lookup" aktivieren, siehe unter Aus Exceldatei in Datenbank schreiben / Aus mehreren Sheets
Bei mehreren Datenzeilen Neuste raussuchen (Gültigkeit)
Im Beispiel soll zu Personalgrunddaten die aktuelleste Adresse PLZ etc ermittelt werden. Dazu gibt es ein Sheet, das pro Personalnummer mehrere Einträge mit Gültigkeiten (Beginn/Ende) geben kann.
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_wohnort.ktr
Vorgehen:
Excelsheet auslesen
Im Beispiel zusätzlich Filter auf Art=1 (ständiger Wohnsitz, nicht Zweitwohnsitz)
Sort Rows nach PersNr und Ende
Group By - Step mit Type "Last Value"
Dann wieder "Update Step"
Aus String Jahr "2000" ein Datum machen (1.1.2000)
Als erstes muss man einen FIlter machen mit der Einstellung nur Datensätze where is not null.
Dann "Add Constants" erster_erster 01/01/
Dann einen Concat step, bei der man die Felder erster_erster und das Zielfeld zusammenfügt.
Dann einen "Select Values" hier wählt man Felder aus, die im weiteren Flow berücksichtigt werden müssen und für das umzuwandelnde Feld, beim Reiter Metadata den Datentyp Date mit Format dd/MM/yyyy.
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_habil.ktr
Den kleineren/größeren Wert aus zwei Spalten ermitteln
Im Beispiel gibt es zwei Felder "Auf Zeit" und "Auf Lebenszeit", das Feld mit dem kleineren Wert in jeder Zeile soll genommen werden.
Dazu nutzt man einen "User Defined Java Expression" Step.
Damit Java die Variablen verarbeiten kann, zunächst einen "Select Values" Step und ändert die Feldnamen auf Namen ohne Leerzeichen, PersNr muss auch übergeben werden, da für Update benötigt.
Als Nächstes ein "User Defined Java Expression" mit der JavaExpression
(lebenszeit=null&&auf_zeit=null)?null:(lebenszeit=null&&auf_zeit!=null)?auf_zeit:(lebenszeit!=null&&auf_zeit=null)?lebenszeit:new Long(Math.min(lebenszeit,auf_zeit))
Also wenn beide Werte null sind, dann null, wenn einer der beiden null ist, dann den anderen und wenn beide gefüllt sind, dann das Minimum.
Wichtig ist, noch dass man bei dem langen Feld für Java Expression nicht übersieht, einen Datentyp auszuwählen.
Anschließend kann wie gewohnt, ein Update Step genutzt werden.
Beispiel db/module/sva/etl/sap_hr/sap_load_pgd_habil.ktr
Feld wird zu Decimalzahl 1020,0 transformiert soll aber String sein
Beim Einlesen von Kostenstellen kam es zu Problem, dass dass Feld zu einer Decimalzahl wie 1020,0 transformiert wurde, umstellen auf Datentyp Integer beim Einlesen oder entsprechender "Select Values" Step mit Meta-Datenänderung auf String brachte nichts.
1. Lösung: beim "Microsoft Excel Input Step" Reiter "Fields" geben Sie bei "Type" den Wert "String" und bei "Pattern" den Wert "#" an. Damit werden auch führende 0en beibehalten.
2. Lösung: ein "User Defined Java Step".
Die Java Expression Kostenst=null?null:Kostenst.trim().replaceAll(",0","") entfernte auch Leerzeichen am Anfang.
Beispiel db/module/sva/etl/sap_hr/sap_load_pfi.ktr
Eine Künstliche ID erzeugen
Für das Excelfeld "Laufbahngruppe" gibt es nur Bezeichnungstext, keinen Schlüssel.
Man kann so vorgehen:
FIlter Values - keine Nulls
Sort Values - by Laufbahngruppe
Unique Rows - Laufbahngruppe
Get Value from Sequence - Laufbahngruppe
In Datenbank schreiben.
Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_laufbahn.ktr
Um diese ID dann bei der Verarbeitung der Rohdaten wieder auszulesen nutzt man "Database Lookup"
Beispiel db/module/sva/etl/sap_hr/sap_load_pbl.ktr.
Das ganze ist aber noch komplexer.
Nach dem ersten Durchlauf bzw. einiger Zeit existieren z.B. in sva_cifx (key=607) die Schlüssel 1-10 für verschd. Laufbahngruppen. Es könnte jedoch sein, dass irgendwann nicht mehr alle Daten übertragen werden, sondern z.B. nach Archivierung nur noch Personal ab 2019 oder so. In den Daten für 2019 ist vielleicht Laufbahnschlüssel 5 nicht mehr drin.
Darum sicherheitshalber folgendes Vorgehen.
Ein Job sap_load_stamm_laufbahn.kjb - erste Transformation liest max(apnr) aus der cifx und speichert in eine Variable (s. auch oben einzelne Schritte Table Input / Set Variable).
Zweite Transformation sap_load_stamm_laufbahn2.ktr liest die Laufbahngruppen aus Exceldatei , dann wird zusätzlich apnr_artifical hinzugefügt.
"Get Value from Sequence" - start at Value ${LAUFBAHN_MAX_APNR}
mit "Database Value Lookup" wird geprüft, ob es schon einen Eintrag in sva_cifx gibt, (wichtig: Do not pass row if lookup fails - darf nicht aktiviert sein).
Dann kann mit "Filter Rows" geprüft werden, ob schon eine Apnr in sva_cifx gefunden wurde, falls nein, neue mit der höheren Wert apnr_artifical in sva_cifx_neu einfügen,
falls ja trotzdem noch mal in sva_cifx_neu einspielen, damit die Bewegungsdaten einen vollständigen Lookup Database Value auf sva_cifx_neu machen können.
Hier ist auch Beispiel für Verzweigung True/False
sap_load_stamm_laufbahn2.ktr
Werte nachtragen (Beispiel erst Kosten- dann Beschäftigungsstellen)
Aufgabe: Zunächst sollen aus einem Sheet alle Kostenstellen eingetragen werden, dann bei den Beschäftigungsstellen diejenigen nachgetragen werden, die noch nicht als Kostenstellen eingetragen wurden.
Dazu kann man einen Insert / Update Step verwenden.
Es sollen keine Updates gemacht werden, die Maske ist zunächst etwas verwirrend. Man wählt "Don't perform any updates" aus.
Bei "Keys to look up" die key_apnr und das Feld im Stream (hier kostenstellenStr).
Dann steht da zwar "Update Fields", aber diese werden eingefügt, wenn noch kein Eintrag für die apnr in der Tabelle sva_inst_neu gefunden wurde.
Sicherheitshalber auch UPdate=N ausgewählt.
Beispiel db/module/sva/etl/sap_hr/sap_load_stamm_beschstellen.ktr Step Insert sva_inst_neu
"Zeitscheiben" ermitteln
Problem in sva_pbv_neu aus IT0016 gibt es eine Zeitscheibe
IT 0016 Personalnr 6333 01.08.2015- 31.07.2017
in IT0001 wird Mitarbeiterkreis und Dienstart nachgeschaut, dort gibt es aber zwei
6333 01.08.2015 31.10.2015 60
6333 01.11.2015 31.07.2017 60
--> keine Eindeutige zuordnung möglich mit einfachen it0016.Beginn<=it0001.Beginn and it0016.Ende>=it0001.Ende möglich
theoretisch wäre auch noch weitere Scheiben mit zwischenzeitlichen Makrs-Wechsel möglich
6333 1.8.2017 31.12.2018 50
6333 1.1.2019 31.12.2019 60
Lösung, Fehlfunktion von "Group by" nutzen, wenn man nicht ordentlich sortiert.
Es kommt immer der Hinweis
"The group by function needs the input to be sorted on the specific keys. If you don't sort in the input the results may not be correct".
Ergo: Sort by-Step nur nach Personalnr und Beginn
Dann "Group by" -> Group Fields PersNr, MAKrs und Aggregates "minBeginn Beginn Minimum" u. "maxEnde Ende Maximum".
Ergebnis Datenzeilen
Persnr minBeginn maxEnde Makrs
6333 1.8.2015 31.7.2017 60
6333 1.8.2017 31.12.2018 50
6333 1.1.2019 31.12.2019 60
Das wollen wir ja - hehe!!
vergl. db/module/sva/etl/sap_hr/sap_load_pbv_art_dienstart.ktr
Literaturtipps
- Pentaho Kettle Homepage: http://kettle.pentaho.com/
- Kettle FAQs: http://wiki.pentaho.com/display/EAI/Frequently+Asked+Questions
- Casters, M., Bouman,R., von Dongen, J. (2010). Pentaho Kettle Solutions. Building Open Source ETL Solutions with Pentaho Data Integration. Indianapolis, IN: Wiley.
- Haneke, U. et al. (2010). Open Source Business Intelligence. Möglichkeiten, Chancen und Risiken quelloffener BI-Lösungen. Wien: Hanser
- Roldan, M.C. (2013). Pentaho Data Integration Second Edition. Beginner's Guide. Birmingham, UK: Packt Publishing
- Javascript Guide des Mozilla Projektes: https://developer.mozilla.org/En/Core_JavaScript_1.5_Guide
- Javascript Anleitung im SelfHTML-Projekt: http://de.selfhtml.org/javascript/index.htm