Datenbank anlegen und Daten importieren
Auf Atlas, dem Dienst für die MongoDB lege ich mir einen Account zu und schnappe mir die kostenfreie Variante der Datenbank. Ein halbes Gigabyte und eine Million API-Calls darf ich durchbringen und werde nicht einmal nach Zahlungsdaten gefragt. Dieses Vorgehen schafft Vertrauen.
Bevor du auf deine Funktionen und HTTPS-Endpoints zugreifen kannst, musst du deinen Webserver und deinem Test-/ Developmentrechner Zugriff gewähren. Unter „Atlas“ –> „Network Access“ musst du die IP-Adressen aller Systeme eingeben, die einen Zugriff auf die Daten benötigen, das gilt auch für Compass.
In Compass wird eine Verbindung zu Atlas angelegt und dort eine Datenbank angelegt. Meine Datenbank heißt „sample“. Danach lege ich eine Collection „rki_covid“ an. Die Namen lassen sich natürlich an deine Gegebenheiten anpassen.
Um Daten hinzuzufügen, klicke ich auf „Add Data“ und „Import File“. Nun kann ich die frisch heruntergeladene Datei mit den historischen CoViD-Daten vom RKI verarbeiten. Die CSV-Datei wird dabei automatisch erkannt, nur die Datentypen muss ich angeben. Ich wähle also Datum und Nummernformate aus, bestätige das ganze und habe eine überraschend große Datenbank mit allen Datensätzen.
Ausprobieren der Mongo Shell
Die gespeicherten Daten können nach dem Import testweise über die mongosh (MongoDb Shell) abgerufen werden. Diese Shell ist bereits in Compass integriert; um sie zu öffnen, klickst du links unten einfach auf „MONGOSH“.
Zuerst überprüfe, ob die richtige Datenbank ausgewählt ist:
db
Sollte nicht die korrekte Datenbank ausgewählt sein – hier „sample“ – wechsele die Datenbank mit
use sample
Nun können wir die gespeicherten Daten über die Shell abfragen. Mit der Methode find() lassen sich alle gefundenen Ergebnisse in der Shell anzeigen, die Ergebnisse können mittels einer Abfrage eingegrenzt werden.
Die Methode findOne() funktioniert sehr ähnlich der find()-Methode, gibt jedoch nur das erstbeste Ergebnis zurück. Wir wollen die Daten nun erst einmal testweise abfragen, daher reicht es, wenn wir ein einzelnes Ergebnis erhalten. Wir nehmen findOne() um beispielsweise einen Datenpunkt für die Bundesrepublik anzuzeigen.
Wie man die Daten für Bund, Länder und Kreise trennt? Hierzu muss man wissen, dass die Daten in dem Feld „AdmUnitId“ den Bestandteilen des amtlichen Gemeindeschlüssels entsprechen. „5“ oder „05“ wäre daher Nordrhein-Westfalen, „9“ wäre Bayern. Die Bundesrepublik hat keinen eigenen solchen Schlüssel und wird in diesem Falle mit „0“ dargestellt.
Mit dem folgenden Befehl lasse ich mir also einen Datenpunkt für die BRD geben:
db.rki_covid.findOne({AdmUnitId: "0"})
Ziffern- und Zahlencodes wie der „Amtliche Gemeindeschlüssel“ (AGS) begegnen dir in der Statistik und Auswertung häufig. Idealerweise werden die Codes in den Originaldaten mitgeliefert oder wenigsten auf diese verwiesen. Beachte bei solchen Schlüsseln, dass sich diese oft im Detail unterscheiden können. Neben dem AGS können dir zum Beispiel noch NUTS (Nomenclatur of Territorial Units for Statistics) und LAU (Local Administrative Units) begegnen – es ist kompliziert. Grundsätzlich können aber alle Daten in und für Deutschland oft bis auf Kreisebene dargestellt werden.
Das Ergebnis sieht wie folgt aus (bei dir wird möglicherweise ein anderer Datensatz geliefert):
{ Datum: 2020-03-01T00:00:00.000Z,
AdmUnitId: '0',
BundeslandId: '0',
AnzFallMeldung: 11,
AnzFallVortag: 35,
AnzFallNeu: 0,
ObjectId: 1,
_id: ObjectId("62d07b12c735c1ccc5f9ca64"),
AnzFallErkrankung: 224,
KumFall: 170 }
Die Abfragen können über Query-Operators, auch Filter genannt, weiter eingegrenzt werden. Den ersten Filter haben wir bereits oben genutzt, nämlich die AdmUnitID für die Bundesrepublik.
Da die CoViD-Zahlen nun in einer Datenbank stecken erhöht sich ihrer Verfüg- und Auswertbarkeit. Wir können nun die folgende, wilde Frage beantworten: Wann hatte das Saarland zum ersten Mal mehr als einhundert gemeldete Neuerkrankungen?
db.rki_covid.find({AdmUnitId: "10", AnzFallErkrankung: {$gt: 100 }}).sort({"Datum": 1}).limit(1)
Hier wurden mehrere Methoden genutzt und aneinandergekettet. Die Methode, dieses mal find(), liefert etwas zurück, dass sich einen Cursor nennt. Dieser Cursor bietet selbst wieder einige Werkzeuge, um die Daten weiter zu sortieren.
Mit den Suchoperatoren in find() lassen sich alle Ergebnisse für das Saarland, in denen mehr als einhundert Fälle gemeldet wurden, ermitteln. Der Operator $gt steht hier für „greater than“. Diese Ergebnisse werden mittels sort(„Datum“: 1) nach dem Datum sortiert, die 1 bedeutet, dass eine aufsteigende Sortierung genutzt wird. Abschließend nehme ich nur ein Ergebnis ab, das mit dem kleinsten Datum:
{ Datum: 2020-12-14T00:00:00.000Z,
AnzFallVortag: 78,
AnzFallNeu: 0,
_id: ObjectId("62d07b15c735c1ccc5f9eda0"),
AdmUnitId: '10',
BundeslandId: '10',
AnzFallMeldung: 57,
ObjectId: 9021,
KumFall: 16194,
AnzFallErkrankung: 104 }
Dies ist nun die richtige Stelle, um sich eigene Fragestellungen zu überlegen und eigene Abfragen über mongosh zu kreieren. Wichtig ist, dass du ein Gefühl für Query und Operatoren entwickelst.
Finde doch heraus, wie viele Neufälle in deinem Kreis oder in deiner kreisfreien Stadt auftreten. Eine Tabelle mit allen AGS-Schlüsseln findest du übrigens ebenfalls bei Esri.
In MongoDB kann die Menge der Daten nicht nur über Filter eingegrenzt werden. Vielleicht interessieren bei einer Abfrage nur Teile eines Dokuments, in unserem Beispiel Datum und AnzFallErkrankung. Bei großen Abfragen, großen Dokumenten und vielen Besuchenden kommen sehr schnell ein paar GB zusammen. Mit dem find()-Parameter Projection werden nur die Teile des Dokuments zurückgegeben, die wirklich benötigt werden.
Funktionen und Endpoints erstellen
Mit einem Clientprogramm lassen sich alle Abfragen an die MongoDB auch nativ erstellen. Nicht alle Websites unterstützen allerdings die Installation von neuen Bibliotheken für den Zugriff auf MongoDB. Um einen Zugriff von (fast) allen Websites zumindest theoretisch zu ermöglichen, erstellen wir nun eine Funktion in Atlas (dem Onlinedienst für MongoDB). Auf diese Funktion können wir dann auch über HTTPS zugreifen.
Erstellt wird nun eine Funktion, welche alle Daten zu einer geografischen Einheit herausgibt. Diese geografische Einheit wird bereits in den Daten als „AdmUnitId“ verschlüsselt, daher werden wir diesen Schlüssel für die Abfrage verwenden.
Aus meiner Arbeit bei einer Stadtverwaltung bevorzuge ich allerdings den Namen „ags“ – der amtliche Gemeindeschlüssel oder LAU. Über die folgende Abfrage sollen alle Fallzahlen für die Bundesrepublik in chronologischer Reihe ausgegeben werden:
https://data.mongodb-api.com/app/data-bhixo/endpoint/getCovidDataSeries?ags=0
Damit das funktioniert müssen wir aber erst aktiv werden.
Die oben angegebene Abfrage passe bitte noch für deine Atlas-Umgebung an, da sich deine von meinen Endpoints unterscheiden. Die korrekte URL findest du in Atlas, wenn du den Anleitungen weiter unten folgst.
Bevor diese Abfrage etwas zurückliefern kann, muss in Atlas über „App Services“ –> Funktionen zuerst etwas Code geschrieben werden. Den folgenden Code passe hierzu für deine Umgebung an. Auf welchen Cluster du dich befindest, solltest du recht schnell in Atlas sehen, den Datenbanknamen und den Namen der Collection hast du selbst angelegt.
exports = function(rq){
let {ags=0} = rq.query;
const mongo = context.services.get("Cluster0");
const db = mongo.db("sample");
const collection = db.collection("rki_covid");
const query = { "AdmUnitId": ags };
return collection.find(query).sort({Datum: 1}).toArray().then(items => { return items });
};
Zuerst werden die Parameter der HTTP-Anfrage ausgelesen und der Variable ags der entsprechende Wert zugewiesen weden. Wird der Parameter ags nicht über HTTP geliefert, wird ags=0 als Standard vorausgesetzt.
Folgend verbindet sich die Funktion mit dem Cluster/ Server, danach mit der Datenbank und öffnet anschließend die Collection, in welcher sich die CoViD-Daten befinden.
Die query und, tiefer, die find()-Methode dürften dir schon zum Teil bekannt vorkommen, denn diese haben wir weiter oben in der mongosh bereits in einer sehr ähnlichen Form verwendet. Abschließend werden die Dokumente (so heißen die Daten in der MongoDB) noch in ein array verpackt und an den Aufrufer als json-Datei zurückgeliefert.
In den Einstellungen kannst du noch einen Benutzer festlegen, unter welchem die Funktion laufen soll. Um die Funktion zu testen, reicht eine Ausführung unter „System“, das solltest du allerdings zeitnah ändern.
Öffnest du deinen neuen HTTPS-Endpunkt über den Webbrowser, sollte dich das Ergebnis deiner Abfrage freudig als json-Datei anstrahlen.
Das PHP-Skript
Um der DSGVO Rechnung zu tragen, keine Abmahnung zu riskieren, den bürokratischen Aufwand zu miniminieren und sich nicht mit CORS auseinanderzusetzen, sollten keine personenbezogenen Daten an irgendwen/ von irgendwem anders versendet werden, insbesondere wenn dieser Andere nicht in der EU sitzt. Daher ist es besser, die Daten direkt vom eigenen Webserver zu laden. Hierfür bietet sich ein flottes PHP-Skript an, das nichts weiter tut, als die Daten von der MongoDB zu laden.
<?php
header('Content-Type: application/json');
echo file_get_contents("https://data.mongodb-api.com/app/data-bhixo/endpoint/getCovidDataSeries?ags=0");
?>
Das ist die kürzeste Variante, um die Daten der Bundesrepublik aus MongoDB zu laden und ohne jede weitere Bearbeitung direkt an den Browser der Besuchenden weiterzuleiten. Auch hier gilt wieder, dass die URL jeweils auf deine Datenbank und deine Endpoints angepasst werden muss.
Sobald das PHP-Skript erfolgreich und fehlerfrei gespeichert ist, kann es einfach über den Webbrowser – und damit auch über Javascript – aufgerufen werden.
https://deinwebserver.de/dateiname.php
Mit ein bisschen Schnickes (ags als Parameter, ein bisschen Fehlerbehandlung, Vorbereitung für andere Abfragen) kann das Ganze dann schon so aussehen:
<?php
$QueryType = $_GET['QueryType'];
$QueryData = $_GET['QueryData'];
switch($QueryType){
case "RKIHistoric":
header('Content-Type: application/json');
echo file_get_contents("https://data.mongodb-api.com/app/data-bhixo/endpoint/getCovidDataSeries?ags=" . $QueryData);
return;
break;
default:
error("TYPE");
}
function error($qtype) {
header('Content-Type: text/html');
http_response_code(400);
echo "<p>Malformed Request:</p>";
echo "<p> ${qtype} Error" . $QueryType . " Type with Data: " . $QueryData . "</p>";
}
?>
Der Vorteil der längeren Variante ist, dass hier mehrere Abfragen hinzugefügt werden können und insbesondere das ags hinzugefügt werden kann. Zum Beispiel für das Saarland mit der folgenden Abfrage:
https://deinwebserver.de/dateiname.php?QueryType=RKIHistoric&QueryData=10