Um ein mehrzeiliges Textfeld zu befüllen, mußte ich in RapidMiner eine Zeilenschaltung (Line feed) von irgendwo herbekommen. Die offensichtliche Möglichkeit, aus einem Editor eine Zeilenschaltung rauszukopieren und im RapidMiner-Studio-Textfeld einzufügen, funktioniert allerdings nicht, stattdessen erscheint ein Leerzeichen.
Der nächste Versuch war ein Replace-Operator mit der Regular-Expression-Schreibweise „\n“. Leider funktioniert auch das nicht, auch nicht mit zwei Backslashes.
Natürlich ging das Auslesen aus einer Datenbank, aber ich wollte die Lösung allgemein halten und mich somit auf RapidMiner-Operatoren beschränken.
Die Lösung, die ich gefunden habe, nutzt die Web-Mining-Extension, die zu den meistinstallierten und auch nützlichsten Erweiterungen gehört. In der Extension ist der Operator „Decode URLs“ enthalten, mit dem verschiedene nichtdruckbare Zeichen (also auch Tabulator, Backspace usw., wenn benötigt) erzeugt werden können.
Der Prozess ist einfach: Mit „Generate Data by User Specification“ wird eine Textvariable mit dem Inhalt „%0A“ erzeugt. Diese wird dann mit Decode URLs aus der URL-Kodierung in ASCII umgewandelt. Das Ergebnis ist die Zeilenschaltung.
Für einfache Nutzung habe ich die Variable in einen Macro kopiert, den ich dann einfach mit der normalen Makro-Syntax %{LINEFEED} weiter verwenden konnte.
Generating a line feed character in RapidMiner
For filling a multi-line text field, I needed a line feed character in RapidMiner. The obvious way of copying one from a text editor and pasting it in RapidMiner Studio doesn’t work; RapidMiner sanitizes the input by changing it to a space.
The next attempt was using the Replace operator with the regular expression syntax „\n“. But this doesn’t work either, neither with one nor with two backslashes.
Reading the character from a database query worked like a charm, but I wanted a generic solution using only RapidMiner operators.
The solution I found uses the Web Mining extension, which is one of the most installed and most useful extensions. The extension contains an operator Decode URLs that can create all kinds of non-printable characters (also tabulator, backspace etc. if needed).
The process is easy: a variable with the contents „%0A“ is created with „Generate Data by User Specification“. This is decoded from the URL encoding to ASCII, resulting in the line feed character.
For simple usage afterwards, I put the variable into a macro which could be used with the common macro syntax %{LINEFEED}.
Im Pentaho BI Server ist eine sehr flexible Karten-Komponente enthalten, die leider nicht gerade ausführlich dokumentiert ist. Ich möchte hier eine kurze Anleitung für einen erfolgreichen Start geben.
Zuerst braucht man Positionsdaten. Diese liegen entweder als echte Geodaten (z. B. aus einer GIS-Datenbank wie PostGIS) oder als Breiten- und Längengrad (latitude/longitude) vor. Im Dashboard brauchen wir die lat/lon-Darstellung.
Als erstes legen wir im Layout-Panel des Dashboard-Editors einen Bereich für die Karte an, z. B. eine Zeile und darin eine Spalte, in diesem Beispiel mit einer Breite von 10 Elementen (Medium Devices). Diese Spalte benennen wir so, daß die Verbindung zum Map-Element erkennbar ist, z. B. MapDiv.
Datenquelle
Die Datenquelle für die Punkte auf der Karte definieren wir im Datasources Panel. Hier legen wir eine Abfrage namens MapQuery an, die uns die Geokoordinaten liefert. Diese Spalten müssen „Latitude“ und „Longitude“ heißen (die Klein- oder Großschreibung ist egal).
Hat man echte Geodaten in einer GIS-fähigen Datenbank, sind die Punkte eventuell in einer eigenen Spalte zusammengesetzt gespeichert. Das läßt sich leicht in die benötigten Koordinaten aufspalten:
SELECT ST_X(ST_Transform(geo, 4326)) as longitude,
ST_Y(ST_Transform(geo, 4326)) as latitude
FROM gis_table
In diesem Beispiel ist „geo“ der Name der Geodaten-Spalte. Mit ST_Transform(geo, 4326) konvertiert PostGIS die Koordinaten aus der in der Datenbank verwendeten Projektion in Länge- und Breitengrad. ST_X und ST_Y extrahieren aus dem konvertierten Objekt die einzelnen Koordinaten.
Karten-Komponente
Jetzt kann das Karten-Element im Components Panel angelegt werden: Custom: NewMapComponent.
Als Name wählen wir in diesem Beispiel einfach „Map“. Als Datasource tragen wir den Namen der Datenquelle (MapQuery) ein, und als HtmlObject den Namen des angelegten Bereichs (MapDiv).
Wenn wir das Dashboard speichern und ausführen, stellen wir fest, daß keine Karte angezeigt wird. Das liegt daran, daß die Karten im Gegensatz zu anderen Elementen selbst keinen Platz beanspruchen. Wir müssen also Breite und Höhe des enthaltenden Elements festlegen, entweder im Layout Panel mit Height, oder in einem Stylesheet.
Das Ergebnis der Mühen ist eine Karte, in der die Punkte aus der Datenbankabfrage mit farbigen Standard-Symbolen markiert sind.
Standardmäßig zeigt die Karte die ganze Erde an. Meist will man aber eine kleinere Region anzeigen. Das geht mit den Optionen Center Latitude und Center Longitude sowie Default Zoom Level.
Die beiden Center-Koordinaten kann man von der Karte ablesen, wenn man mit der Maus über sie fährt. Der Zoom Level hängt von der Größe der Kartendarstellung und dem darzustellenden Gebiet ab. Für eine Großstadt kann der Zoom ca. 12 betragen, Österreich paßt bei Zoom 7 ganz gut, Deutschland braucht schon Zoom 6.
Anpassung der Marker
Die Standard-Markierungen der Punkte auf der Karte passen nicht zu jeder Anwendung. Es besteht die Möglichkeit, eigene Symbole anzuzeigen: dazu gibt man in der Datenquelle die URL zum Symbol (ganz normale Bilddatei) in der Spalte „marker“ aus. Damit lassen sich zum Beispiel unterschiedliche Zustände der dargestellten Objekte anzeigen.
Popups
Zu jedem Marker können wir Zusatzinformationen anzeigen lassen. Wenn die Datenquelle eine Spalte „popupContents“ enthält, wird deren Inhalt (optional HTML-formatiert) angezeigt, wenn der Benutzer ein Marker-Symbol anklickt. Mit Popup Width und Popup Height läßt sich die Größe des Popups an den erwarteten Inhalt anpassen.
Mit diesen Optionen läßt sich schon ganz ohne Programmierung viel machen. Mit etwas JavaScript läßt sich noch viel mehr erreichen.
Automatische Aktualisierung der Karteninhalte
Im Gegensatz zu vielen anderen Dashboard-Komponenten bietet NewMapComponent keine Funktion zum automatischen Aktualisieren des Inhalts in festgelegten Abständen. In einigen Fällen möchte man jedoch die Positionen beweglicher Objekte darstellen und die Karte von Zeit zu Zeit automatisch aktualisieren. Glücklicherweise läßt sich das mit ein wenig JavaScript-Code erreichen.
In den Advanced Properties der Karten-Komponente geben wir in der Eigenschaft Post Execution eine Funktion ein:
//Sets up a timer to refresh the map after a minute
function () {
this.timerId = window.setTimeout(
function() {
render_Map.update();
}
, 60000);
}
60000 ist die Anzahl der Millisekunden, nach denen der Timer laufen soll. render_Map ist der Name der Karten-Komponente, mit „render_“ davor – so benennt das Community Dashboard Framework das JavaScript-Objekt.
Da diese Funktion immer nach dem Aktualisieren der Karte ausgeführt wird, ist gleich der nächste Timer nach der gleichen Periode aktiviert. Die Timer-ID, die in JavaScript zum Abbrechen des Timer genutzt werden kann, wird dabei im render_Map-Objekt gespeichert.
Es gibt nur ein Problem: Verschiebt der Benutzer den Kartenausschnitt oder zoomt hinein oder hinaus, setzt die Aktualisierung der Karte den Ausschnitt und den Zoom-Wert auf die Standardwerte. Die Karte kehrt also zum Ausgangspunkt zurück. Um das zu vermeiden, können wir vor dem Update noch schnell die aktuelle Sicht abspeichern, wodurch die Karte nicht mehr springt.
Folgende Funktion gehört in die Pre Execution-Eigenschaft:
//Before reloading the map automatically, save the current zoom level and position
function () {
if (this.timerId != null) {
//Get current map center and zoom level
center = this.mapEngine.map.center;
zoom = this.mapEngine.map.zoom;
//Transform to WGS84 coordinates
center.transform(this.mapEngine.map.projection, "EPSG:4326");
this.defaultZoomLevel = zoom;
this.centerLongitude = center.lon;
this.centerLatitude = center.lat;
}
}
Zuerst überprüfen wir, ob die timerId schon gesetzt ist. Beim ersten Anzeigen der Karte (wenn das Dashboards geöffnet wird) ist das noch nicht der Fall.
Ist die Karte schon fertig dargestellt worden, ist timerId beim nächsten Durchlauf nicht mehr leer, dann können wir also die Position und den Zoom-Level der Karte abfragen und in die Standardeinstellung der Komponente hineinschreiben.
Bei der nächsten automatischen Aktualisierung der Karte tritt zwar ein systembedingtes Flackern auf, aber der vom Benutzer gewählte Bildausschnitt bleibt bestehen.
Die Map-Komponente bietet noch andere Möglichkeiten, so wie das Thema der GIS-Daten ein fast unerschöpfliches ist. Ich werde sicher noch weitere Beiträge in diesem Themenkreis schreiben.
Displaying maps with the Pentaho Dashboard Framework
Pentaho BI Server contains a map component that’s very versatile but unfortunately quite sparsely documented. I’d like to give you a simple introduction into using the component.
First, we need position data. These are either stored in a real geographic database like PostGIS or separated into latitude and longitude. The dashboard map needs the lat/lon form.
First we create an area for the map in the Layout Panel of the Dashboard Editor. This can be a row and a column in it, for example with a width of 10 elements (enter it in the Medium Devices setting). We name this column MapDiv to state that it belongs to the map.
Data source
We need to specify the data source of the points in the Datasources Panel. Here, we enter a query called MapQuery that returns the coordinates. The columns must be called „Latitude“ and „Longitude“ (capitalization doesn’t matter).
Geograpic data in a GIS-capable database stores the points in a single column. It is easy to separate the two coordinates:
SELECT ST_X(ST_Transform(geo, 4326)) as longitude,
ST_Y(ST_Transform(geo, 4326)) as latitude
FROM gis_table
In this example, „geo“ is the name of the geodata column. In PostGIS, ST_Transform(geo, 4326) converts the coordinates from the geographic reference system used in the database to the classic Earth latitude and longitude values. ST_X and ST_Y extract the longitude and latitude from the compound object.
Map component
Now we create the Map element in the Components Panel by adding a Custom: NewMapComponent.
The name is simply „Map“ in this example. The Datasource is the name of our data source (MapQuery) and the HtmlObject is the name of the created row (MapDiv).
After saving and executing the dashboard, no map is shown. The reason is that the Map component itself doesn’t specify a content height, as other components do. So we need to set the width and height ourselves, either in the Layout Panel (Row Height) or in a stylesheet.
The result is a map that visualizes points from the database query with standard symbols in different colors.
By default, the map shows the entire Earth. We usually want to restrict the displayed area: this is done with the options Center Latitude/Longitude and Default Zoom Level.
The Center coordinates are displayed on the map when moving the mouse over it. The zoom level depends on the size of the map and the area to be shown. For a large city, the zoom can be around 12, Austria fits well with zoom = 7, and Germany requires zoom = 6.
Changing the markers
The default markers of map points aren’t suitable for some requirements. It is possible to use our own symbols: just return the URL of the symbol (a normal image file) in the column called „marker“ in the map data source. This allows us to visualize different object states.
Popups
For each marker we can display additional information in a popup area. If the data source contains a column named „popupContents“, its text (optionally HTML formatted) is displayed when the user clicks on a marker symbol. Popup Width and Popup Height can be changed to match the size of the content to display.
Using these options, we can already do a lot, entirely without programming. A bit of JavaScript can do a lot more.
Periodic updates of the map contents
In contrast to many other dashboard components, the NewMapComponent doesn’t have a setting for periodic updates. Sometimes we have to visualize objects that can move and automatically refresh the map from time to time. Fortunately, only a few lines of simple JavaScript are necessary to achieve this.
We enter the following function in the Advanced Properties of the Map component, in the property Post Execution:
//Sets up a timer to refresh the map after a minute
function () {
this.timerId = window.setTimeout(
function() {
render_Map.update();
}
, 60000);
}
60000 is the number of milliseconds after the timer executes the function. The map component is named render_Map in this example: the Community Dashboard Framework always prefixes the specified names with „render_“ to create the JavaScript object in the running dashboard.
This function is executed after each update of the map, so it also sets up the next automatic update after the same period. The timer ID stored in the render_Map object could be used to cancel the timer if desired.
However, there’s one usability problem: If the user moves the map or zooms in, the automatic update resets the map to the values defined in the dashboard properties and the map returns to the initial view. We can avoid this by storing the properties of the current view before the map update, so the map doesn’t jump around anymore.
The following function is entered into the Pre Execution property:
//Before reloading the map automatically, save the current zoom level and position
function () {
if (this.timerId != null) {
//Get current map center and zoom level
center = this.mapEngine.map.center;
zoom = this.mapEngine.map.zoom;
//Transform to WGS84 coordinates
center.transform(this.mapEngine.map.projection, "EPSG:4326");
this.defaultZoomLevel = zoom;
this.centerLongitude = center.lon;
this.centerLatitude = center.lat;
}
}
The function checks if the timerId is set. If the map is displayed the first time (when opening the dashboard), this is not yet the case.
In a map that is already displayed, the timerId has a value, so we can store the map’s position and zoom level in the properties of the map object.
The map update causes a visible flicker of the dashboard, but at least the map area selected by the user stays as it is.
The map component offers a lot more, and GIS data is an almost infinite topic. This blog post isn’t the last one about maps, that’s for sure.
Ein- bis zweimal im Jahr erscheint eine neue Version der Pentaho-Software mit sinnvollen Neuigkeiten, die man gerne auf den eigenen Servern anwenden möchte. Die Art des Releases (Programm, Daten und Konfiguration gemischt) ist zwar für einen schnellen Start mit der Software gut geeignet, aber ein Update auf die neue Version kann schwierig sein, wenn man die Konfiguration am Anfang nicht sauber herausgetrennt hat.
Mein Ansatz dazu ist folgender:
Im Hauptverzeichnis (z. B. /opt/pentaho) liegen die Verzeichnisse des Servers mit Versionierung (z. B. biserver-ce-5.3). Zusätzlich die Verzeichnisse configuration, repository und jdbc.
Die aktuell in Produktion verwendete Version wird ohne Versionsnummer (biserver-ce) verlinkt. Somit verweist der Startskript immer auf den richtigen Server, und bei Problemen mit einer neuen Version könnte man leicht zur älteren zurückkehren.
Diese Lösung erleichtert den Umstieg auf eine neue Version und verringert die Backup-Datenmenge (von 600 MB auf einige KB), weil die biserver-ce-Verzeichnisse nicht mitgesichert werden müssen, da nichts an eigener Arbeit in ihnen liegt.
Repository
Die aktuellen Releases des BI-Servers verwenden ein Jackrabbit-File-Repository in biserver-ce/pentaho-solutions/system/jackrabbit. Hierin liegt die Konfigurationsdatei sowie das Verzeichnis „repository“, das ich wie beschrieben ins Pentaho-Verzeichnis (raus aus der biserver-ce-Verzeichnisstruktur) verschiebe und mit einem Symlink verlinke. Vor einem Update auf eine neue Pentaho-Version sollte man dieses Verzeichnis zusätzlich sichern, da eine neuere Jackrabbit-Version ein neues Format einführen könnte, das mit älteren Versionen inkompatibel ist.
Zusätzlich deaktiviere ich in tomcat/webapps/pentaho/WEB-INF/web.xml den Start der hibernate- und quartz-Datenbanken mit HSQLDB. Die Sampledata-Datenbank wird von mitgelieferten Komponenten des Servers benötigt und bleibt daher aktiviert, in der vom Server mitgelieferten Version.
Diese vier Konfigurationsdateien gehören auch ins herausgelöste Konfigurationsverzeichnis verschoben und aus dem Server verlinkt. Die Dateien in pentaho-solutions können auch mit Symlinks verbunden werden, jene in Tomcat müssen aber Hardlinks sein.
Anpassung des Login-Fensters
Im Produktivbetrieb hat man die Default-Paßwörter geändert und die mitgelieferten User deaktiviert oder gelöscht. Deswegen ist die standardmäßig eingeschaltete Möglichkeit, als User oder Admin mit einem voreingestellten Paßwort einzuloggen, nicht mehr notwendig.
Das läßt sich in pentaho-solutions/system/pentaho.xml abschalten (login-show-users-list, login-show-sample-users).
Zusätzlich kann man das Login-Fenster mit Änderungen am mitgelieferten Stylesheet (pentaho-solutions/system/common-ui/resources/themes/crystal/globalCrystal.css) an die eigenen Vorstellungen anpassen.
Update strategy for Pentaho BI Server
Pentaho releases one or two new versions of their open source software each year, with interesting new functionality or bugfixes. The way the release is done is well suited for a new evaluation installation, but updating to the new version can be difficult if one didn’t separate out the configuration after the setup.
This is my approach to solving the issue:
There’s a main directory (e.g. /opt/pentaho). In it, the BI server’s files are unzipped and versioned (e. g. biserver-ce-5.3). In addition to the server directories, there’s also configuration, repository and jdbc.
The current production version is symlinked without a version number (e. g. biserver-ce). This causes the start script to always point to the correct server, but switching versions is still easy if necessary.
With this solution, it’s much easier to update to a new version. Backups are much smaller, only the few kilobytes of the configuration instead of the whole BI server (600 MB).
Repository
Current releases of Pentaho BI Server use a Jackrabbit file repository which is in biserver-ce/pentaho-solutions/system/jackrabbit. In this directory there’s a configuration file and a „repository“ directory which gets moved into the main Pentaho directory (out of the biserver-ce structure) and symlinked in „jackrabbit“. It’s a good idea to backup this directory before updating to a new release as Jackrabbit could possibly update the format so that it’s not compatible with older Pentaho releases anymore.
Database configuration
If you changed your database configuration from HSQLDB to another database, the following files are to be moved into configuration:
In addition, in tomcat/webapps/pentaho/WEB-INF/web.xml I deactivated the hibernate and quartz HSQL databases. The sampledata database is required by some components of the server, so I keep it active.
When moving these files into the separate config directory, be careful when linking. Tomcat doesn’t like symlinks so they need to be hardlinked.
Changing the login screen
On a production system the default passwords are hopefully changed and the sample users are deactivated or removed. So the default mechanism for logging as user or Admin in with predefined passwords is not necessary anymore.
This can be switched off in pentaho-solutions/system/pentaho.xml (login-show-users-list, login-show-sample-users).
In addition, the login screen can be further customized by changing the stylesheet (pentaho-solutions/system/common-ui/resources/themes/crystal/globalCrystal.css) for your own requirements.
(English version)
Komplexe Lösungen bestehen häufig aus mehr als einer Datenbank, manchmal sogar unterschiedlichen Typs. Im Fall von Sco2t haben wir eine „geerbte“ MySQL-Datenbank, während neue Daten wegen der erweiterten GIS-Funktionalität in PostgreSQL liegen. Natürlich müssen gewisse Daten zwischen beiden Systemen synchronisiert werden.
Pentaho Data Integration ist dafür eine sehr gute Lösung und wird auch intensiv verwendet. Allerdings möchte ich in einigen Fällen sofort auf Änderungen reagieren und nicht erst eine Minute später. (Und auch nicht ständig Java-Prozesse starten, die die meiste Zeit keine Änderungen finden.) Dafür bieten sich PostgreSQL-seitig Trigger an, die Änderungen an einer Tabelle in der MySQL-Datenbank nachvollziehen.
PostgreSQL 9.1 hat die erste Version der Foreign Data Wrappers genannten Funktionalität bekommen, seit 9.3 können diese sogar in fremde Datenbanken schreiben. Ein FDW-Modul für den Zugriff auf andere PostgreSQL-Server ist in 9.4 im PostgreSQL-Contrib-Bereich dabei, Module für viele andere Datenbanken (auch NoSQL und Hadoop!) sind verfügbar.
(Linked Server sind schon seit Längerem in vielen Datenbanksystemen verfügbar, PostgreSQL hatte dafür „dblink“. Diese Funktionalität hat sich aber großteils auf Verbindungen zum eigenen Datenbanktyp beschränkt.)
EnterpriseDB hat ein FDW-Modul für MySQL realisiert. Derzeit muß man es selbst kompilieren, da in den üblichen Repositories keine fertigen Pakete enthalten sind. Das ist aber nicht schwierig.
Installation auf einem Debian-System:
git clone https://github.com/EnterpriseDB/mysql_fdw.git
apt-get install make gcc libmysqlclient-dev postgresql-server-dev-9.4
cd mysql_fdw
make USE_PGXS=1
make USE_PGXS=1 install
Damit ist das Modul übersetzt und als PostgreSQL-Extension installiert. Der Rest passiert als PostgreSQL-User mit Superuser-Rechten in SQL:
CREATE EXTENSION mysql_fdw;
CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '127.0.0.1', port '3306');
CREATE USER MAPPING FOR username SERVER mysql_server
OPTIONS (username 'myuser', password 'mypassword');
CREATE FOREIGN TABLE mysqltabelle (
-- Felder aus der MySQL-Tabelle mit ihrem Datentyp
)
SERVER mysql_server
OPTIONS (dbname='datenbankname', table_name='OriginalTabelleName');
Damit ist die Fremdtabelle „mysqltabelle“ in der PostgreSQL-Datenbank angelegt und kann wie eine normale Tabelle angesprochen, gelesen und beschrieben werden.
CREATE EXTENSION registriert das Modul in der aktuellen Datenbank. (Verwendet man mehrere Datenbanken auf einem Server, muß CREATE EXTENSION in jeder, die die Funktionalität braucht, ausgeführt werden.)
CREATE SERVER erzeugt die Verbindung zum fremden Datenbankserver und gibt die Verbindungsparameter an.
CREATE USER MAPPING ist notwendig, um PostgreSQL-seitige User mit einem User in der fremden Datenbank zu verknüpfen. Es können mehrere Mappings für unterschiedliche User angelegt werden.
CREATE FOREIGN TABLE definiert dann die Fremdtabelle und gibt ihren Namen in der fremden Datenbank an. Die Datentypen der Felder müssen in PostgreSQL existieren. Es ist möglich, Constraints wie NOT NULL oder Default-Werte schon PostgreSQL-seitig anzugeben.
Foreign Tables eröffnen ganz neue Möglichkeiten der Datenintegration: Daten müssen nicht mehr hin und her kopiert werden, stattdessen sind sie in der Master-Datenbank und die anderen Datenbanken arbeiten immer mit der aktuellen Kopie. Datenübernahmeprozesse können direkt in der Datenbank, ohne Zwischenschaltung einer ETL-Software ausgeführt werden. Ich bin gespannt, welche Anwendungsmöglichkeiten mir noch einfallen.
Accessing foreign databases in PostgreSQL
Many complex solutions use more than one database, sometimes even different database systems. At Sco2t we „inherited“ a MySQL database, but new data are stored in PostgreSQL because of the better GIS functionality. So some data need to be kept in sync between the two systems.
Pentaho Data Integration is a great solution for this and it’s already in heavy use. In some situations, however, I’d like to react to changes instantly instead of waiting a minute. (Also, I dont’t want to start Java processes frequently that don’t have to process any changes most of the time.) In PostgreSQL, we can use triggers for updating data in other tables, now even in the MySQL database.
PostgreSQL 9.1 was the first release with Foreign Data Wrappers functionality. With 9.3, even writing into foreign databases is supported. In 9.4 there is a FDW module for accessing other PostgreSQL servers in the Contrib area. Modules for accessing other database types (even NoSQL and Hadoop!) available.
(Linked server functionality has been available in many database systems for a long time. PostgreSQL had „dblink“. However, linked servers usually had to be of the same type.)
There is a FDW module for MySQL from EnterpriseDB. Currently we need to compile it ourselves, as there are no binary packages in the usual repositories. But it’s not hard.
This is the installation on a Debian system:
git clone https://github.com/EnterpriseDB/mysql_fdw.git
apt-get install make gcc libmysqlclient-dev postgresql-server-dev-9.4
cd mysql_fdw
make USE_PGXS=1
make USE_PGXS=1 install
After this, the module is compiled and registered as a PostgreSQL extension. The rest of the procedure has to be executed (with database superuser rights) in SQL:
CREATE EXTENSION mysql_fdw;
CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '127.0.0.1', port '3306');
CREATE USER MAPPING FOR username SERVER mysql_server
OPTIONS (username 'myuser', password 'mypassword');
CREATE FOREIGN TABLE mysqltable (
-- Fields of the MySQL table, including their type
)
SERVER mysql_server
OPTIONS (dbname='databasename', table_name='OriginalTableName');
The result of this script is a foreign table „mysqltable“ in the PostgreSQL database. This table can be used like any normal table, you can read and write it.
CREATE EXTENSION registers the compiled module in the current database. (When using multiple databases on a server, CREATE EXTENSION must be executed in each database that will contain foreign tables.)
CREATE SERVER creates the link to the foreign database server and defines connection parameters.
CREATE USER MAPPING is necessary to link a PostgreSQL user to a user in the foreign database. Multiple mappings can be created for different users.
CREATE FOREIGN TABLE defines the foreign table and its name in the foreign database. The field types must exist in PostgreSQL. It is possible to define constraints like NOT NULL or give default values on the PostgreSQL side.
Foreign tables open entire new worlds of possibilities for data integration. Master data don’t need to be copied to multiple databases: other databases can work directly on the original which is always current. Data integration processes run in the database, without a separate ETL software. It’ll be interesting to explore further ideas and usage scenarios.
In Studio wurden die unterstützenden Vorschläge für neue User weiter verbessert. Früher hat Studio nur Operatoren vorgeschlagen, die zu den aktuellen passen; jetzt zeigt es auch an, welche Einstellungen häufig verwendet werden. Außerdem wurde der Excel-Import wesentlich beschleunigt, indem die relevanten Teile neu implementiert wurden, statt wie bisher eine Library zu verwenden.
Am Server debütieren HTML5-Diagramme und -Karten, auf die ich mich besonders freue. Die HTML5-Diagramme sollen mittelfristig die bisherigen Flash-basierten Diagrammformate ablösen, die ja unter anderem die mobile Nutzung der Dashboards verhindern.
Ein lang erwartetes Feature am Server war die Versionierung der Prozesse. Dies ist endlich möglich.
Für Big-Data-Umgebungen wurden weitere Verbesserungen eingeführt, unter anderem Kerberos-Authentifizierung in Hadoop-Clustern und die Unterstützung von Apache Spark für eine schnellere Verarbeitung vieler Aufgaben.
RapidMiner kann auf der Homepage heruntergeladen werden.
Pentaho BI Platform 5.3
Die Neuerungen in der Pentaho-Plattform sind einerseits dem Bereich Big Data zuzuordnen, andererseits inkrementelle Verbesserungen und Bugfixes. Auch ein Patch von mir für die Unterstützung von Sequenzen in Netezza-Datenbanken ist in Data Integration aufgenommen worden.
Pentaho Community Edition ist auf der Community-Seite herunterzuladen.
A week of updates
Two of the most popular tools of data scientists – which I work with most frequently – received updates in this week.
RapidMiner Studio 6.3 and Server 2.3
Studio got improvements for new users in the operator recommendation functionality. In earlier versions, it recommended only useful or frequently used operators matching the current process. Now, also sensible settings for the operators are shown. Also, import of Excel files was improved by implementing the needed functionality in RapidMiner instead of using an existing library.
On the server, HTML5 charts and maps are available for the first time. They will replace the older Flash based charts which were problematic in mobile and open source environments.
A long-awaited feature, versioning of processes on the server, is finally available.
There are further improvements for Big Data environments: Kerberos authentication for Hadoop clusters and support for Apache Spark for faster processing of many workloads.
RapidMiner is available for download on the homepage.
Pentaho BI Platform 5.3
The news in this release are mainly in the Big Data area and also incremental improvements as well as bugfixes. I also contributed a patch for sequence support in Netezza databases that went into Data Integration.
Pentaho Community Edition is available on the Community page.
Da man immer wieder auf Datenquellen im JSON-Format stößt, möchte ich auch zeigen, wie man sie mit RapidMiner verarbeitet.
Früher gab es nur den Operator „Json to XML“. Dieser versucht, die JSON-Datenstruktur in XML abzubilden, sodaß man sie dann mit dem vorhandenen Read XML-Operator verarbeiten kann. Leider schlägt das in vielen Fällen fehl, da JSON wesentlich weniger strukturiert sein kann als XML.
Mit der Version 6.2 wurde aber ein genialer Operator eingeführt, der die Verarbeitung extrem erleichtert und elegant macht: Json to Data. Im Prinzip bildet er die Struktur des JSON-Dokuments mit der kompletten Hierarchie inkl. Arrays in Spalten ab, die nach dem „JSON-Pfad“ zum Element benannt sind.
Natürlich können da sehr breite Datenstrukturen rauskommen, aber damit hat RapidMiner kein Problem. Mit einem simplen Trick kann man diese „unbequeme“ Datenstruktur bearbeiten: Der Transpose-Operator transponiert die Spalten in Zeilen, und erzeugt damit eine viel simplere Tabelle, die leicht weiterzuverarbeiten ist.
So sieht der Prozess zur Verarbeitung des JSON-Dokuments aus dem letzten Betrag aus:
Open File öffnet die URL und gibt ein File-Objekt aus. Read Document liest den Inhalt des File-Objekts und gibt ihn als Document (unstrukturierter Text) aus. JSON To Data konvertiert das Dokument in eine RapidMiner-Tabelle (in diesem Fall mit 24.000 Spalten und einer Zeile). Transpose klappt die Zeilen und Spalten dann um.
Es ist sinnvoll, einen Haltepunkt auf Transpose zu setzen (F7, Breakpoint after) und die Daten anzuschauen. Es gibt eine ID-Spalte, die den Pfad zu den Daten (in diesem Fall in Array-Syntax: [x][y]) enthält, und eine Attribut-Spalte, die die Daten selbst enthält. Alles wurde in Strings konvertiert.
Beispiel-Ausgabe:
id
att_1
[0][0]
194.0
[0][10]
Conglomerates
[0][11]
Minnesota
Da wir die zwei Dimensionen des Arrays in zwei separaten Spalten brauchen, kopieren wir die ID-Spalte, und transformieren sie und ihre Kopie mit regulären Ausdrücken. Das Ergebnis sieht so aus:
id
id2
att_1
0
0
194.0
0
10
Conglomerates
0
11
Minnesota
Das id-Feld ist die Gruppe und das id2-Feld der Spaltenindex. Diese Struktur wird mit dem Pivot-Operator so konvertiert, daß id die Zeile definiert und id2 die Spalten. Diese brauchen dann nur mehr umbenannt zu werden. Danach werden noch die String-Spalten in Zahlen konvertiert und das Ergebnis nach dem Sort-Feld sortiert.
Fertig! Elegant und intuitiv, wie von RapidMiner gewohnt.
Processing JSON documents in RapidMiner
Data scientists frequently have the requirement to process JSON documents. I’d like to show how one can process them in RapidMiner.
Until recently, the only way was using the operator „Json to XML“. This operator tries to convert the original structure of the JSON document to XML so it can be processed using Read XML. Unfortunately, JSON is usually less structured than XML so this fails quite often.
A great operator was introduced in version 6.2 that makes processing JSON much easier and more elegant: Json to Data. It converts the structure of the JSON document to columns named by the „JSON path“ of the element.
This can lead to very broad data structures with hundreds or thousands of columns, but this is not a problem for RapidMiner. Using a simple trick, this unusual data representation can be changed: the Transpose operator flips columns to rows, so the table becomes much simpler.
This is a process for loading an example JSON array from the web and converting it into a tabular structure with metadata:
Open File loads the contents from the URL and returns a File object. Read Document reads the contents of the file and returns an unstructured Document object. JSON to Data takes the document and converts it into a RapidMiner example set. (In this case, the table has 24,000 columns and one row.) Transpose changes the columns into rows.
You could set a breakpoint on Transpose (F7, Breakpoint after) and look at the data. There’s an ID column which contains the JSON path of the attribute value (in this case, in array syntax: [x][y]), and an attribute value column with the data. Everything was converted to strings.
Example:
id
att_1
[0][0]
194.0
[0][10]
Conglomerates
[0][11]
Minnesota
We need both dimensions of the array as separate columns, so we copy the ID column and transform both copies using regular expressions. This is the result:
id
id2
att_1
0
0
194.0
0
10
Conglomerates
0
11
Minnesota
The id field is the group and id2 the column index. This structure is processed with the Pivot operator by using id as the row identifier and id2 as the column. Then we rename the columns, convert string columns to numbers and sort the results by the value of the Sort field.
That’s it! Elegant and intuitive, thanks to RapidMiner.
Owncloud ist eine sehr nützliche Software, um Dateien auf dem eigenen Server abzulegen, für andere freizugeben und von mehreren Rechnern aus zu nutzen. Technisch findet der Dateizugriff entweder über den Browser oder übers WebDAV-Protokoll statt.
Als Berater bin ich oft in fremden Netzwerken unterwegs, die alle möglichen Einschränkungen haben, z. B. auch häufig die großen Cloud-Speicher-Anbieter blockieren. Ein eigener Server, auf den man über HTTPS zugreifen kann, ist da sehr nützlich.
Unter Linux ist der Aufwand, die Inhalte ins Dateisystem einzubinden, dank davfs2 nicht sehr groß. Die Konfiguration ist gut beschrieben und schnell erledigt.
Windows ist ein anderes Thema. Die Anleitung von Owncloud selbst funktioniert nicht unter allen Windows-Versionen; man müßte in der Registry Einstellungen setzen, damit es geht. Ohne Admin-Rechte ist das zum Scheitern verurteilt.
Vor einiger Zeit habe ich CarotDAV entdeckt und bin schnell ein Fan davon geworden. Neben der Installationsversion existiert auch eine „Portable“-Version, die keine Installation und somit keine Administratorrechte braucht – sie läuft auch von einem USB-Stick.
Neben WebDAV für Owncloud beherrscht CarotDAV auch weitere Anbieter wie Dropbox, Google Drive, OneDrive usw.
Der Clou ist, daß man unter Tool/Setting/DAV Server einen Laufwerksbuchstaben einrichten kann, unter dem alle konfigurierten Zugänge (Owncloud und andere) als Verzeichnisse zur Verfügung stehen. Somit kann man sie ohne Zusatzsoftware und Installation einfach in allen Windows-Programmen verwenden.
Es ist eine gute Idee, ein „Master Password“ zu setzen. Damit wird die Konfiguration verschlüsselt, die Paßwörter der einzelnen Dienste und Server sind also geschützt. Beim Starten von CarotDAV muß das Paßwort eingegeben werden; wenn man den Computer verläßt, kann man den Desktop sperren und/oder CarotDAV beenden.
Mounting Owncloud as a drive
Owncloud is a great software for storing files on your own server, sharing with others and using them from many locations. Data access is done in the browser or using the WebDAV protocol.
As a consultant I’m frequently working in foreign corporate networks with a variety of limitations. The big cloud storage providers are frequently blocked there. Having my own server with HTTPS access is very useful in these situations.
On Linux, mounting the remote service is easy using davfs2. The configuration is easy and well documented.
Windows is another topic, however. The Owncloud tutorial doesn’t work on many Windows versions or requires changes in the registry that can be only performed with admin rights.
Fortunately I found CarotDAV and quickly became a fan. In addition to the installable version there’s also a „Portable“ one that doesn’t require administrator rights and also runs from a USB stick.
CarotDAV supports Owncloud access over WebDAV, but also other providers like Dropbox, Google Drive, OneDriver and more.
A great feature is the ability to specify a drive letter in Tool/Setting/DAV Server. Each configured account (Owncloud and others) is displayed as a subdirectory of this drive. This means that all Windows programs are able to work with the files stored on all your cloud storage providers.
It’s a good idea to set a Master Password. This encrypts the configuration and protects the passwords of the cloud services. When starting CarotDAV, the password must be entered; if you leave the computer, you can lock the desktop or end CarotDAV.
Neulich mußte ich eine JSON-Datenstruktur aus einer Web-Quelle holen und in die Datenbank einfügen. Das Besondere an diesen Daten ist, daß sie einfach in einem zweidimensionalen Array liegen, die Webseite interpretiert die Daten selbst. Es sind also keine Feldnamen im JSON.
In R ging es recht einfach:
require(rjson)
# JSON-Array laden, List daraus machen
global2k_lst <- fromJSON(file="http://www.forbes.com/ajax/load_list/?type=organization&uri=global2000&year=2014");
# Die Liste in einen eindimensionalen Vektor konvertieren
global2k <- unlist(global2k_lst)
# Durch die Angabe der Dimensionen wird eine Matrix daraus
dim(global2k) <- c(12,2000)
# Aus der Matrix dann ein Data Frame machen und die Felder benennen
global2k <- as.data.frame(t(global2k))
names(global2k) <- c("rank", "urlname", "name", "logoname", "sort",
"sales", "profits", "assets", "marketvalue",
"country", "industry", "us_state")
write.csv2(global2k, "/tmp/forbes2000.csv", row.names=FALSE)
Andererseits hat PostgreSQL seit Version 9.3 viele JSON-Funktionen. Wie funktioniert es also direkt in der Datenbank?
Erst mal habe ich den ganzen JSON-String in eine Tabelle ins Feld „forbes2k_json“ eingelesen. (PostgreSQL hat ja auch mit 244 kByte Daten in einem Feld kein Problem.) Der Inhalt des Arrays läßt sich dann mit PostgreSQL-Mitteln mit etwas Aufwand als Tabelle ausgeben:
-- JSON-Array in 2000 Zeilen aufspalten
with lines as (
select json_array_elements(forbes2k_json::json) as company
from forbes2k_json
),
-- Die Elemente der Zeilen in jeweils 12 Zeilen aufspalten, die Original-Zeilen nummerieren
entries as (
select json_array_elements(company)::text as entry,
row_number() over () as entrynr
from lines
),
-- Die Felder nummerieren
fields as (
select *,
row_number() over (partition by entrynr) as fieldnr
from entries
)
-- Mit 12 self joins die Tabellenstruktur aufbauen und die Felder konvertieren
select f1.entry::integer as rank,
regexp_replace(f2.entry::text, '^"(.*)"$', '\1') as urlname,
regexp_replace(f3.entry::text, '^"(.*)"$', '\1') as company,
regexp_replace(f4.entry::text, '^"(.*)"$', '\1') as logoname,
f5.entry::integer as sort,
regexp_replace(regexp_replace(f6.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as sales,
regexp_replace(regexp_replace(f7.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as profits,
regexp_replace(regexp_replace(f8.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as assets,
regexp_replace(regexp_replace(f9.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as marketvalue,
regexp_replace(f10.entry::text, '^"(.*)"$', '\1') as country,
regexp_replace(f11.entry::text, '^"(.*)"$', '\1') as industry,
regexp_replace(f12.entry::text, '^"(.*)"$', '\1') as us_state
from fields f1
inner join fields f2 on f2.entrynr = f1.entrynr and f2.fieldnr = 2
inner join fields f3 on f3.entrynr = f1.entrynr and f3.fieldnr = 3
inner join fields f4 on f4.entrynr = f1.entrynr and f4.fieldnr = 4
inner join fields f5 on f5.entrynr = f1.entrynr and f5.fieldnr = 5
inner join fields f6 on f6.entrynr = f1.entrynr and f6.fieldnr = 6
inner join fields f7 on f7.entrynr = f1.entrynr and f7.fieldnr = 7
inner join fields f8 on f8.entrynr = f1.entrynr and f8.fieldnr = 8
inner join fields f9 on f9.entrynr = f1.entrynr and f9.fieldnr = 9
inner join fields f10 on f10.entrynr = f1.entrynr and f10.fieldnr = 10
inner join fields f11 on f11.entrynr = f1.entrynr and f11.fieldnr = 11
inner join fields f12 on f12.entrynr = f1.entrynr and f12.fieldnr = 12
where f1.fieldnr = 1
order by rank, sort
Kompliziert ist es nicht, nur viel zu tippen (und copy/paste-en). Da die Daten selbst keine Feld-Metadaten wie Name und Typ enthalten, muß man sie selbst festlegen.
Processing a JSON array in PostgreSQL
I needed to get a JSON data structure from the web and put its contents into a database. These data were a bit special in the sense that the JSON only contained only a two dimensional array interpreted by the website, without any field names.
In R it was quite easy:
require(rjson)
# Load JSON array and convert to a list
global2k_lst <- fromJSON(file="http://www.forbes.com/ajax/load_list/?type=organization&uri=global2000&year=2014");
# Convert to a one-dimensional vector
global2k <- unlist(global2k_lst)
# Change to a matrix by specifying the dimensions
dim(global2k) <- c(12,2000)
# Convert the matrix to a data frame and specify the field names
global2k <- as.data.frame(t(global2k))
names(global2k) <- c("rank", "urlname", "name", "logoname", "sort",
"sales", "profits", "assets", "marketvalue",
"country", "industry", "us_state")
write.csv2(global2k, "/tmp/forbes2000.csv", row.names=FALSE)
On the other hand, PostgreSQL has lots of JSON functions since release 9.3. So how to do it directly in the database?
The first step is reading the entire JSON string into a table in a field called „forbes2k_json“. (PostgreSQL can easily store 244 kBytes of data in a single field.) A not too complex query converts the array contents into a normal table:
-- JSON array into 2000 lines
with lines as (
select json_array_elements(forbes2k_json::json) as company
from forbes2k_json
),
-- Existing lines converted into 12 lines each, with an additional row number for
-- the original lines
entries as (
select json_array_elements(company)::text as entry,
row_number() over () as entrynr
from lines
),
-- Row numbers of the entry lines
fields as (
select *,
row_number() over (partition by entrynr) as fieldnr
from entries
)
-- 12 self joins for building the table structure; convert field types
select f1.entry::integer as rank,
regexp_replace(f2.entry::text, '^"(.*)"$', '\1') as urlname,
regexp_replace(f3.entry::text, '^"(.*)"$', '\1') as company,
regexp_replace(f4.entry::text, '^"(.*)"$', '\1') as logoname,
f5.entry::integer as sort,
regexp_replace(regexp_replace(f6.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as sales,
regexp_replace(regexp_replace(f7.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as profits,
regexp_replace(regexp_replace(f8.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as assets,
regexp_replace(regexp_replace(f9.entry::text, ',', ''), '^"(.*)"$', '\1')::numeric(10,3) as marketvalue,
regexp_replace(f10.entry::text, '^"(.*)"$', '\1') as country,
regexp_replace(f11.entry::text, '^"(.*)"$', '\1') as industry,
regexp_replace(f12.entry::text, '^"(.*)"$', '\1') as us_state
from fields f1
inner join fields f2 on f2.entrynr = f1.entrynr and f2.fieldnr = 2
inner join fields f3 on f3.entrynr = f1.entrynr and f3.fieldnr = 3
inner join fields f4 on f4.entrynr = f1.entrynr and f4.fieldnr = 4
inner join fields f5 on f5.entrynr = f1.entrynr and f5.fieldnr = 5
inner join fields f6 on f6.entrynr = f1.entrynr and f6.fieldnr = 6
inner join fields f7 on f7.entrynr = f1.entrynr and f7.fieldnr = 7
inner join fields f8 on f8.entrynr = f1.entrynr and f8.fieldnr = 8
inner join fields f9 on f9.entrynr = f1.entrynr and f9.fieldnr = 9
inner join fields f10 on f10.entrynr = f1.entrynr and f10.fieldnr = 10
inner join fields f11 on f11.entrynr = f1.entrynr and f11.fieldnr = 11
inner join fields f12 on f12.entrynr = f1.entrynr and f12.fieldnr = 12
where f1.fieldnr = 1
order by rank, sort
Vor kurzem wurde ich gefragt, wie aufwändig es ist, das Ergebnis eines ETL-Jobs (also aus Pentaho Data Integration) im BA Server auf einem Dashboard auszugeben. Es ist gar nicht kompliziert und geht sehr schnell.
Als erstes erstellen wir eine Transformation und merken uns den Namen des Schrittes, dessen Ergebnis im BA Server ausgegeben werden soll. In diesem Beispiel lese ich den RSS-Feed von pentaho.com aus und selektiere daraus die Spalten Titel, Text und Datum.
Diese Transformation muß dann auf den Server geladen werden. Wenn man in Kettle ohne Repository oder mit einem Datei-Repository arbeitet, ist die Datei schon vorhanden; aus einem Datenbank-Repository müßte man sie exportieren (File/Export/To XML).
Die .ktr-Datei wird dann im BA Server unter Browse Files ins gewünschte Verzeichnis raufgeladen (Upload…).
Danach geht’s ans Erstellen des Dashboards. Im Hauptmenü Create New, dann CDE Dashboard. (Wenn dieser Menüpunkt nicht erscheint, muß man im Marketplace „Community Dashboard Editor“ installieren und den Server neu starten.)
Der erste Weg führt zu den Einstellungen des Dashboards:
Interessant hier ist die Auswahl des Dashboard-Typs: blueprint oder bootstrap. Blueprint ist (noch?) Standard, aber viele neue Dashboards entstehen mit dem moderneren Bootstrap-Framework. Hier arbeiten wir mit dem Standard, blueprint.
In diesem Beispiel brauchen wir keine komplexe Dashboard-Struktur (die auch möglich wäre). Deswegen fügen wir nur eine Zeile und innerhalb der Zeile eine Spalte ein. Wir geben der Zeile einen Namen (RSSTableHTML) und stellen Span size auf 24, weil bei Blueprint die ganze Breite auf 24 Spalten aufgeteilt ist.
Als nächstes kommt die Datenquelle. Dazu wählen wir unter Datenquellen aus den „KETTLE Queries“ den Eintrag „kettle over kettleTransFromFile“ und stellen die Eigenschaften ein.
Name: RSSFeedResult
Kettle Transformation File: (die hochgeladene Datei wählen)
Kettle Step name: Der Schritt, dessen Ergebnis ausgegeben werden soll
Wir können Cache=True belassen, um das Ergebnis für eine Stunde (3600 Sekunden) zu cachen.
Jetzt bleibt nur mehr die Tabelle. Unter Komponenten wählen wir Others und Table Component. Wir benennen die Komponente (RSSTable), wählen die Datenquelle (RSSFeedResult) und das HTML-Objekt, in dem die Tabelle angezeigt wird (RSSTableHTML). Fertig!
Wer mit dem Aussehen der Tabelle nicht zufrieden ist, kann unter Advanced Properties noch einiges umstellen und z. B. Style auf Classic umstellen.
Das Ergebnis der Bemühungen ist eine schöne Tabelle mit den Inhalten des RSS-Feeds.
Natürlich können die Kettle-Transformationen und die Dashboards beliebig komplex werden. Es ist nur zu beachten, daß der Datenerfassungsprozess nicht zu lang dauert, sonst warten die Benutzer des Dashboards zu lange.
Revolution Analytics pflegt eine für Unternehmen optimierte kommerzielle Version der Open-Source-Analytik-Sprache R. Die Sprache selbst basiert auf einer früheren namens S.
Da R ein waschechtes Open-Source-Projekt mit sehr aktiver Community ist, sind die Berührungspunkte mit Microsoft auf den ersten Blick nicht klar.
Jedenfalls zeigt Microsoft damit, es mit Analytik sehr ernst zu meinen. Data science ist ja ein heißes Thema.
Was könnte aus dieser Kooperation entstehen?
Vielleicht zusätzliche Ressourcen auch für die Open-Source-R-Entwicklung, eventuell eine grafische Entwicklungsumgebung? Integration von R in MS SQL Server (andere Datenbanken haben das ja längst) und Excel? Ein neuer R-Kern in .net, zur Integration in andere Programme und zur Entwicklung nativer Windows-Applikationen? Es wird jedenfalls spannend.