Schlagwort-Archive: MySQL

Java: Erstellen großer CSV-Dateien zum Datenbank-Import

Anstatt Millionen von INSERT oder UPDATE-Befehlen an die Datenbank zu schicken, um eine Tabelle zu aktualisieren, bietet es sich bei großen Datenmengen an, eine CSV-Datei zu erstellen, welche dann über LOAD DATA INFILE mit extrem hoher Geschwindigkeit eingelesen wird.

Erstellen von CSV-Dateien mit Java

In Java bietet sich zum Erstellen von CSV-Dateien die Klasse Super CSV an. Um eine Datei zu erzeugen, benötigt man als erstes einen CSVMapWriter:
[code lang=’java‘]CsvMapWriter writer = new CsvMapWriter(new OutputStreamWriter(new FileOutputStream(„C:\meineDatei.csv“, true),“UTF-8″), CsvPreference.EXCEL_PREFERENCE);[/code]
Anstatt mit einem OutputStreamWriter zu arbeiten, kann man auch einen FileWriter verwenden. Da man bei letzterem aber kein Encoding („UTF-8“) angeben kann und in der Datenbank die Zeichen UTF-8-kodiert sind, müssen wir den Stream verwenden.
Interessant ist der zweite Parameter des FileOutputStream, der angibt, dass die Daten, die eingefügt werden, angehängt werden und nicht vorher gelöscht werden.
Die Einstellung CsvPreference.EXCEL_PREFERENCE legt fest, welche Trennzeichen in der CSV-Datei verwendet werden – wenn man andere Trennzeichen verwenden möchte, muss der später behandelte LOAD DATA INFILE-Befehl angepasst werden.

Als nächstes werden die Textfelder für die CSV-Datei definiert:
[code lang=’java‘]final String[] header = new String[] { „id“, „name“, „adressfeld“ };[/code]
Unsere Datensätze bestehen also aus 3 Feldern.

Um die CSV-Datei mit Daten zu füllen sind folgende Zeilen notwendig:
[code lang=’java‘]final HashMap data = new HashMap();
data.put(header[0], this.id);
data.put(header[1], this.name);
this.adressfeld = this.adressfeld.replace(„\\“, „\\\\“);
data.put(header[2], this.adressfeld);[/code]
Die Daten werden in einer HashMap gespeichert und per data.put eingefügt. Was mich etwas überraschte war, dass man das Backslash maskieren muss, ansonsten geht es in der Verarbeitung verloren. Eigenartig, weil man diese Funktionalität sicherlich problemlos in Super CSV hätte integrieren können.

Abschließend werden die Daten dem CSVMapWriter übergeben und dieser wieder geschlossen:
[code lang=’java‘]writer.write(data, header);
writer.close();[/code]

Und mehrere Datensätze?

Obiges Beispiel ist für einen einzelnen Datensatz, der in einer CSV-Datei gespeichert wird. Wenn man mehrere Datensätze einfügen möchte, müssen sämtliche Schritte (Anlegen eines CSVMapWriter, Daten vorbereiten, Daten einfügen) für jeden Datensatz wiederholt werden.

Den CSVMapWriter 1x anzulegen und am Ende per .close zu schließen, funktioniert nicht – meine Versuche mit der Super CSV-Klasse haben gezeigt, dass dann Datensätze verloren gehen bzw. abgeschnitten werden.

Einlesen der CSV mit LOAD DATA INFILE

Um die so erstellte Datei mit LOAD DATA INFILE in die Datenbank zu importieren, muss folgender Befehl eingetragen werden:
[code lang=“sql“]LOAD DATA [LOCAL] INFILE ‚C:\\meineDatei.csv‘
[REPLACE]
INTO TABLE meine_tabelle
FIELDS TERMINATED BY ‚,‘
OPTIONALLY ENCLOSED BY ‚\“‚
LINES TERMINATED BY ‚\n‘
(id, name, adressfeld);[/code]

Vorab: Der Befehl wird so nicht funktionieren, da die Bestandteile innerhalb der eckigen Klammern optional sind:

  • LOCAL wird dann angegeben, wenn die CSV-Datei vom Clientprogramm auf dem Clienthost gelesen und an den MySQL-Server geschickt wird. Fehlt LOCAL, liegt die Datei auf dem MySQL-Server
  • REPLACE nutzt man, wenn die eingelesene Datei nicht nur neue Datensätze importiert, sondern auch bereits vorhandene überschreibt – also UPDATES durchführt

Alle/Weitere Optionen zu dem LOAD DATA INFILE-Befehl gibt es im MySQL-Handbuch.

Performancevergleich

Ein Performancevergleich ist hier zu finden.

Komplizierteste SQL-Query ever

Es gibt so ein tolles Zitat:

“Was nicht auf einer einzigen Manuskriptseite zusammengefasst werden kann, ist nicht durchdacht und nicht entscheidungsreif.”

Wenn man das auf meine SQL-Abfrage überträgt, muss ich wohl eingestehen, dass diese Abfrage undurchdacht wäre. Ist sie aber nicht! Ich habe 3 Tage an dieser Abfrage zugebracht, damit sie das Ergebnis ausgibt, welches sie jetzt ausgibt.

Die Abfrage ist für ein Wettkampf, der aus mehreren Einzeldisziplinen besteht, die an unterschiedlichen Tagen veranstaltet werden (zeitgleich und hintereinander). Der Wettkampf ist für Teams. Für jede Einzeldisziplin ist die DisziplinId, TeamId, Platz, Datum, Punktestand vorhanden. Allerdings wird für die Gesamtwertung nicht der Punktestand in der Einzeldisziplin genommen, sondern inder Gesamtwertung werden neue Punkte anhand der Platzierung in der Einzeldisziplin vergeben – z.B. so: Platz 1 – 100 Punkte, Platz 2 – 90 Punkte, usw.

Es klingt schon so natürlich sprachlich kompliziert und ehe ich hier weitere Einzelheiten versuche zu erklären, platziere ich hier einfach die 117 Zeilen lange SQL-Abfrage:
[code lang=’sql‘]
SELECT
@anzTeam := (SELECT COUNT(*) FROM pentathlon_team WHERE freigeschaltet=’1′),
@seq := @anzTeam + 1,
@rank := @anzTeam,
@letzteDatum := (SELECT DISTINCT SUBSTR(MIN(zeitpunkt),1,10) FROM pentathlon_score WHERE pentathlon_credits != 0),
@letztePunkte := 999999;
SELECT
gesamt.teamid,
@seq := IF(@letzteDatum = gesamt.zeitpunkt, @seq – 1, @anzTeam) AS seq, /*Sequenz …,3,2,1 je Datum*/
@rank := IF(@letztePunkte = gesamt.punkte, @rank, @seq) AS rank, /*Der Rank eines Teams (wenn gleiche Punktzahl gleicher Rank)*/
@letztePunkte := gesamt.punkte AS punkte,
@letzteDatum := gesamt.zeitpunkt AS zeitpunkt
FROM
(SELECT
einzelgesamt.teamid,
SUM(einzelgesamt.punkte) AS punkte,
einzelgesamt.zeitpunkt AS zeitpunkt
FROM
(SELECT DISTINCT
normale_daten.zeitpunkt,
normale_daten.teamid,
normale_daten.disziplinid,
IF(normale_daten.punkte IS NOT NULL,
normale_daten.punkte,
IF((SELECT von FROM pentathlon_disziplin WHERE disziplinid = normale_daten.disziplinid) > normale_daten.zeitpunkt,
0, /*Disziplin noch nicht gestartet*/
(SELECT DISTINCT /*Disziplin ist beendet: Punkte des letzten Stands*/
IF (pentathlon_score.rank > 25,
0, /*Keine Punkte mehr ab Platz 25*/
pentathlon_punkte.punkte
) AS punkte
FROM
pentathlon_score
LEFT JOIN
pentathlon_punkte
ON
pentathlon_score.rank = pentathlon_punkte.rank
WHERE
pentathlon_score.disziplinid = normale_daten.disziplinid AND
pentathlon_score.teamid = normale_daten.teamid AND
pentathlon_score.zeitpunkt = (SELECT DISTINCT
MAX(zeitpunkt)
FROM pentathlon_score
WHERE disziplinid = normale_daten.disziplinid)
)
)
) AS punkte,
IF(normale_daten.rank IS NOT NULL,
normale_daten.rank,
IF((SELECT von FROM pentathlon_disziplin WHERE disziplinid = normale_daten.disziplinid) > normale_daten.zeitpunkt,
0, /*Disziplin noch nicht gestartet*/
(SELECT DISTINCT /*Disziplin ist beendet: Punkte des letzten Stands*/
pentathlon_score.rank
FROM
pentathlon_score
WHERE
pentathlon_score.disziplinid = normale_daten.disziplinid AND
pentathlon_score.teamid = normale_daten.teamid AND
pentathlon_score.zeitpunkt = (SELECT DISTINCT
MAX(zeitpunkt)
FROM pentathlon_score
WHERE disziplinid = normale_daten.disziplinid)
)
)
) AS rank
FROM
(SELECT DISTINCT
alle_daten.zeitpunkt,
alle_daten.teamid,
alle_daten.disziplinid,
pentathlon_score.rank,
IF (pentathlon_score.rank > 25,
0, /*Keine Punkte mehr ab Platz 25*/
pentathlon_punkte.punkte
) AS punkte
FROM
(SELECT DISTINCT
SUBSTR(pentathlon_score.zeitpunkt,1,10) as zeitpunkt,
pentathlon_team.teamid,
pentathlon_disziplin.disziplinid,
pentathlon_teamdisziplin.webrpc
FROM
pentathlon_score
JOIN
pentathlon_team
JOIN
pentathlon_disziplin
INNER JOIN
pentathlon_teamdisziplin
ON pentathlon_disziplin.disziplinid = pentathlon_teamdisziplin.disziplinid
WHERE
pentathlon_score.pentathlon_credits != 0 AND
pentathlon_team.freigeschaltet = ‚1‘) AS alle_daten
LEFT JOIN
pentathlon_score
ON
alle_daten.teamid = pentathlon_score.teamid AND
alle_daten.disziplinid = pentathlon_score.disziplinid AND
IF(SUBSTR(alle_daten.webrpc,1,33) = ‚http://www.worldcommunitygrid.org‘,
alle_daten.zeitpunkt = SUBSTR(pentathlon_score.zeitpunkt,1,10),
alle_daten.zeitpunkt = DATE_ADD(SUBSTR(pentathlon_score.zeitpunkt,1,10), INTERVAL -1 DAY) AND
SUBSTR(pentathlon_score.zeitpunkt,12,2)=’01‘
)

LEFT JOIN
pentathlon_punkte
ON
pentathlon_score.rank = pentathlon_punkte.rank) AS normale_daten

) AS einzelgesamt
GROUP BY
einzelgesamt.zeitpunkt,
einzelgesamt.teamid
) AS gesamt
ORDER BY
gesamt.zeitpunkt,
gesamt.punkte DESC
[/code]
Natürlich hätte ich das auch alles durch einfache Abfragen lösen können, die ich dann in PHP zusammenfüge, aber in PHP ist das einfügen und sortieren der Daten so ineffizient.

Die @variable sind User-Variablen in MySQL, mit denen man die Platzierung in der Abfrage berechnen kann.

Jetzt würde ich meine Fähigkeiten in SQL mit einer 1,2 benoten.. ;)

MySQL: Order By – NULL-Werte

Die Tabelle ‚verzeichnis‘ sieht so aus:

id | reihenfolge | name
——————————————
1  | 2                | Klaus
2  | 1                | Rolf
3  | NULL          | Annika
4  |3                 | Jürgen

Nun möchte man per SQL die id und name in der angegebenen Reihenfolge ausgeben. Es stellt sich allerdings heraus, dass diese Abfrage:
[code lang=’sql‘]
SELECT
id,
name
FROM
verzeichnis
ORDER BY
reihenfolge ASC
[/code]
Nicht zum gewünschten Ergebnis führt, denn der Datensatz ohne Angabe einer Reihenfolge (NULL) wird als erstes ausgegeben, er soll aber als letztes ausgegeben werden. Um NULL-Werte hinter anderen Datensätzen auszugeben, muss dieser SQL-Ausdruck genutzt werden:
[code lang=’sql‘]
SELECT
id,
name
FROM
verzeichnis
ORDER BY
IF(ISNULL(reihenfolge),1,0) ASC,
reihenfolge ASC
[/code]
Warum gibt dieser Ausdruck genau das zurück, was wir haben wollen?
Das erste Sortierkriterium IF(ISNULL(reihenfolge),1,0) ASC ist dann eine 1, wenn reihenfolge NULL ist und sonst 0. Da wir aufsteigend sortieren, ist 0 (die Datensätze, deren reihenfolge nicht NULL sind) vor 1 (die Datensätze, deren reihenfolge NULL sind). Innerhalb derer wird mit dem zweiten Sortierkriterium reihenfolge ASC wiederum sortiert, so dass die gewünschte Reihenfolge ausgegeben wird.
Rock’n’Roll Neandertaler hat einen noch eleganteren Ausdruck:
[code lang=’sql‘]
SELECT
id,
name
FROM
verzeichnis
ORDER BY
reihenfolge IS NULL,
reihenfolge
[/code]