Anfängerfrage CSV in MySQL

kevin9r

Aktives Mitglied
Hallo,
da ich einige Aufgaben die ich öfters am PC erledigen muss automatisieren möchte steige ich in die Java Programmierung ein. Ich bin absoluter Neuling auf dem Gebiet. Ich habe bereits Kenntnisse mit der Programmiersprache C allerdings mehr im Microcontroller bereich, sodass ich doch einige Schwierigkeiten mit der OOP habe. Aber ich mache langsam aber sicher Fortschritte. Da ich allerings auf die schnelle ein kleines Problem lösen muss benötige ich etwas Hilfe.
Ich benötige ein Programm, welches mir eine CSV Datei in eine MySQL Datenbank schreibt. Ich habe im Internet recherchiert und bin auf den unten aufegführten Code gestoßen. Ich muss leider zugeben, dass ich nicht alle Schritte nachvollziehen kann, aber der Code fast das macht was ich möchte (ich weiß das ist falsch, aber wenn ich dieses Problem erstmal gelöst habe kann ich viel mehr Zeit in das lernen von Java aufbringen).

Also zu meinem Problem. In meiner CSV und meiner Datenbank haben die Spaltennamen leerstellen. Dies kann ich leider auch nicht ändern, da ich die CSV von einer Schnittstelle so vorgegeben erhalte, sodass ich dann per Hand immer die Spaltennamen abändern müsste. Dies würde die arbeit dann auch nicht wirklich erleichtern. Der Code kzeptiert die Leerstellen nicht . Ich erhalten einen Syntaxerror Fehler und weiß nicht wirklich weiter...

Danke schonmal!

Java:
package net.viralpatel.java;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Date;

import org.apache.commons.lang.StringUtils;

import au.com.bytecode.opencsv.CSVReader;

/**
*
* @author viralpatel.net
*
*/
public class CSVLoader {

    private static final String SQL_INSERT = "INSERT INTO ${table}(${keys}) VALUES(${values})";
    private static final String TABLE_REGEX = "\\$\\{table\\}";
    private static final String KEYS_REGEX = "\\$\\{keys\\}";
    private static final String VALUES_REGEX = "\\$\\{values\\}";

    private Connection connection;
    private char seprator;

    /**
     * Public constructor to build CSVLoader object with
     * Connection details. The connection is closed on success
     * or failure.
     * @param connection
     */
    public CSVLoader(Connection connection) {
        this.connection = connection;
        //Set default separator
        this.seprator = ',';
    }
   
    /**
     * Parse CSV file using OpenCSV library and load in
     * given database table.
     * @param csvFile Input CSV file
     * @param tableName Database table name to import data
     * @param truncateBeforeLoad Truncate the table before inserting
     *             new records.
     * @throws Exception
     */
    public void loadCSV(String csvFile, String tableName,
            boolean truncateBeforeLoad) throws Exception {

        CSVReader csvReader = null;
        if(null == this.connection) {
            throw new Exception("Not a valid connection.");
        }
        try {
           
            csvReader = new CSVReader(new FileReader(csvFile), this.seprator);

        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("Error occured while executing file. "
                    + e.getMessage());
        }

        String[] headerRow = csvReader.readNext();

        if (null == headerRow) {
            throw new FileNotFoundException(
                    "No columns defined in given CSV file." +
                    "Please check the CSV file format.");
        }

        String questionmarks = StringUtils.repeat("?,", headerRow.length);
        questionmarks = (String) questionmarks.subSequence(0, questionmarks
                .length() - 1);

        String query = SQL_INSERT.replaceFirst(TABLE_REGEX, tableName);
        query = query.replaceFirst(KEYS_REGEX, StringUtils.join(headerRow, ","));
        query = query.replaceFirst(VALUES_REGEX, questionmarks);

        System.out.println("Query: " + query);

        String[] nextLine;
        Connection con = null;
        PreparedStatement ps = null;
        try {
            con = this.connection;
            con.setAutoCommit(false);
            ps = con.prepareStatement(query);

            if(truncateBeforeLoad) {
                //delete data from table before loading csv
                con.createStatement().execute("DELETE FROM " + tableName);
            }

            final int batchSize = 1000;
            int count = 0;
            Date date = null;
            while ((nextLine = csvReader.readNext()) != null) {

                if (null != nextLine) {
                    int index = 1;
                    for (String string : nextLine) {
                        date = DateUtil.convertToDate(string);
                        if (null != date) {
                            ps.setDate(index++, new java.sql.Date(date
                                    .getTime()));
                        } else {
                            ps.setString(index++, string);
                        }
                    }
                    ps.addBatch();
                }
                if (++count % batchSize == 0) {
                    ps.executeBatch();
                }
            }
            ps.executeBatch(); // insert remaining records
            con.commit();
        } catch (Exception e) {
            con.rollback();
            e.printStackTrace();
            throw new Exception(
                    "Error occured while loading data from file to database."
                            + e.getMessage());
        } finally {
            if (null != ps)
                ps.close();
            if (null != con)
                con.close();

            csvReader.close();
        }
    }

    public char getSeprator() {
        return seprator;
    }

    public void setSeprator(char seprator) {
        this.seprator = seprator;
    }

}
 
Alle Spaltennamen mit [] escapen, dann gehen Leerzeichen.

Aber ehrlich gesagt, ersetz lieber die Leereichen mit Unterstrichen oder so, das macht alles leichter und ist nicht wirklich Aufwand...
 
Hm ok das ist schonmal ein Anfang. Muss jetzt nur die Stelle finden. Ich verstehe z.B. die Definition der Variablen oben überhuapt nicht.

private static final String KEYS_REGEX = "\\$\\{keys\\}";

Was bedeutet dieses keys mit den ganzen Backslashes und dem Dollarzeichen? Er schreibt ja da dann die Feldnamen für den SQL Befehl hinein..

java.sql.BatchUpdateException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Night

Vor dem Night steht eigentlich "First Night". Wenn ich die Leerzeichen in der CSV lösche dann funktioniert alles nur dann gibt der mir natürlich den Fehler aus, dass er die Spalten nicht finden kann.
 
private static final String KEYS_REGEX = "\\$\\{keys\\}";

Was bedeutet dieses keys mit den ganzen Backslashes und dem Dollarzeichen? Er schreibt ja da dann die Feldnamen für den SQL Befehl hinein..
Das ist ein regulärer Ausdruck. Da \ in Java besondere Bedeutung hat, muss man es escapen, der wirkliche String ist also \&\{keys\}, was als auf ${keys} matcht, den Teil, der ja ersetzt werden soll.

java.sql.BatchUpdateException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Night

Vor dem Night steht eigentlich "First Night". Wenn ich die Leerzeichen in der CSV lösche dann funktioniert alles nur dann gibt der mir natürlich den Fehler aus, dass er die Spalten nicht finden kann.

Leerzeichen muss man escapen, deshalb um jeden Spaltennamen [] drum 😉
Dazu müsstest du einfach die Strings in headerRow anpassen
 
Also müsste er statt "First Night", "[First Night]" bekommen?
String[] headerRow = csvReader.readNext();
Hier erhält er ja die Werte.

Bedeutet ich müsste nach dieser Anweisung eine Schleife bauen, welche jedes Element in der Schleife gegen das Element mit [.....] ersetzten?

Bin ich da auf dem richtigen Weg?
 
Das hat irgendwie nicht richtig geklappt, er hat dann richtig [First Night] übbergeben, was ja dann auch nicht richtig war..? Aber habe jetzt einfach die Leerzeichen gegen Unterstriche in einer Schleife getauscht und die Spalten dementsprechend in der Datenbank abgeändert. Jetzt läuft alles super.
 
Also er hat dann folgenden Query erstellt: INSERT INTO bla([First Night],....)
Und dann hat er natülich [First Night] mit den eckigen Klammern in der Datenbank nicht gefunden.
So langsam verstehe ich auch den Code 😎
 
Hallo, ja das mit den [] hat nicht geklappt, aber die Leerzeichen weg zu machen war eh besser. Jetzt habe ich noch eine Frage... Wenn ich die CSV importiere passiert beim Datum etwas ganz komisches.. jedes Datum ist Minus einen Tag in der Datenbank. Als würde er immer abrunden. Verstehe aber überhaupt nicht wieso.

Java:
public class DateUtil {
    // List of all date formats that we want to parse.
    // Add your own format here.
    private static List<SimpleDateFormat>
            dateFormats = new ArrayList<SimpleDateFormat>() {
        private static final long serialVersionUID = 1L;
        {
            add(new SimpleDateFormat("M/dd/yyyy"));
            add(new SimpleDateFormat("dd.M.yyyy"));
            add(new SimpleDateFormat("M/dd/yyyy hh:mm:ss a"));
            add(new SimpleDateFormat("dd.M.yyyy hh:mm:ss a"));
            add(new SimpleDateFormat("dd.MMM.yyyy"));
            add(new SimpleDateFormat("dd-MMM-yyyy"));
            add(new SimpleDateFormat("yyyy-MM-dd"));
            add(new SimpleDateFormat("yyyy-M-dd"));
            add(new SimpleDateFormat("dd MMM yyyy"));
        }
    };
    /**
     * Convert String with various formats into java.util.Date
     *
     * @param input
     *            Date as a string
     * @return java.util.Date object if input string is parsed
     *          successfully else returns null
     */
    public static Date convertToDate(String input) {
        Date date = null;
        if(null == input) {
            return null;
        }
        for (SimpleDateFormat format : dateFormats) {
            try {
                format.setLenient(false);
                date = format.parse(input);
            } catch (ParseException e) {
                //Shhh.. try other formats
            }
            if (date != null) {
                break;
            }
        }
        return date;
    }

Hier macht der aus dem String nen Datum. Ist hier vielleicht ein Fehler?
 
Hey, ja an sowas habe ich auch schon gedacht und deswegen habe ich die CSV mal über Navicat importiert und da klappt es ganz normal.
 
Also ernsthaft. Du hast eine CSV in der ein Datum in Form eines Strings steht. Kopierst dir dann einen Universal umformatierer aus dem Internet und fragst uns dann warum das nicht geht....

Man sieht doch ganz genau in welchem Format der String in der CSV steht und muss dann lediglich einen SimpleDateFormatter mit dem entsprechenden Format erstellen. Ist das wirklich so schwer?

Da du uns nicht sagst wie das Format in der CSV aussieht werden wir dir auch nicht helfen können
 
Ja von irgendwas muss man anfangen, durch das lösen eines bestimmten Problems und das dadurch nötige einarbeiten in den Code lerne ich mehr als würde ich stundenlang in Büchern lesen... Ich hatte auch schon mehrere andere Probleme die ich gelöst habe aber jetzt weiß ich einfach nicht weiter.

Ich habe zwei verschiedene Formate zur Auswahl in der CSV, eine Spalte ist
01 Jan 2016 und eine andere ist 2016-01-01.
Aber er wandelt beide Strings ja um aber leicht falsch...
 
Du schreibst oben das Datum steht in der Datenbank falsch. Hast du das schon nachgeschaut ob es nur in der Datenbank falsch ist oder ist es nach dem convertToDate(...) falsch ?
 
Das zweite Format ist bereits SQL DateTime. Du kannst sie also einfach so in die DB schreiben ohne irgendwas konvertieren zu müssen.
 
Ich habe dein DateUtil mal getestet und das ergibt beim convertToDate bei mir das richtige Datum.

Edit:
Das hier
Code:
        System.out.println("Datum: "+convertToDate("17 Sep 2016"));
        System.out.println("Datum: "+convertToDate("2016-09-17"));
ergibt das hier:
run:
Datum: Sat Sep 17 00:00:00 CEST 2016
Datum: Sat Sep 17 00:00:00 CEST 2016
 
Schonmal vielen Dank für die Bemühungen!

Das ist irgendwie komisch. In der CSV sind die Daten so angordnet:
"19 Dec 2015","23 Dec 2015","24 Dec 2015","2015-12-19","2015-12-23","2015-12-24",
(erster Datensatz in der CSV)
in der Datenbank sieht der Datensatz so aus:
19 Dec 2015, 23 Dec 2015, 24 Dec 2015, 2015-12-18, 2015-12-22, 2015-12-23

Dann gibt es Datensätz die in der Datenbank dann z.b. so aussehen :
31 Dec 2015, 2016-01-01, 2016-01-02, 2015-12-30, 2016-01-01, 2016-01-02
Und manchmal so:
20 Dec 2015, 20 Dec 2015, 21 Dec 2015, 2015-12-19 2015-12-19 2015-12-20

Öffne ich die CSV mit Excel werden mir die Daten auch so gemischt angezeigt. Mal in dem Format, mal in dem Format, aber die Daten sind richtig (also nicht minus einen Tag), öffne ich die Datei mit Notepad++ zeigt er mir alles korrekt an? Manche Daten in der Datenbank sind auch korrekt.
Habe die CSV auf 3 Datensätze begrenzt um den Fehler zu suchen, und mir die umgewandelten Daten anzusehen. So sehen Sie in Excel aus:
19 Dec 2015; 23 Dec 2015; 24 Dec 2015; 19.12.2015; 23.12.2015; 24.12.2015
29 Dec 2015; 30 Dec 2015; 31 Dec 2015; 29.12.2015; 30.12.2015; 31.12.2015
01 Dec 2015; 01. Jan 16; 02. Jan 16; 01.12.2015; 01.01.2016; 02.01.2016
So in Notepad++:
"19 Dec 2015","23 Dec 2015","24 Dec 2015","2015-12-19","2015-12-23","2015-12-24"
"29 Dec 2015","30 Dec 2015","31 Dec 2015","2015-12-29","2015-12-30","2015-12-31"
"01 Dec 2015","01 Jan 2016","02 Jan 2016","2015-12-01","2016-01-01","2016-01-02"

Und so die Ausgabe von DateUtil:
Sat Dec 19 00:00:00 CET 2015
Wed Dec 23 00:00:00 CET 2015
Thu Dec 24 00:00:00 CET 2015
Fri Sep 16 00:00:00 CEST 2016 ?????????????
Tue Dec 29 00:00:00 CET 2015
Wed Dec 30 00:00:00 CET 2015
Thu Dec 31 00:00:00 CET 2015
Fri Sep 16 00:00:00 CEST 2016 ???????
Fri Jan 01 00:00:00 CET 2016
Sat Jan 02 00:00:00 CET 2016
Tue Dec 01 00:00:00 CET 2015
Fri Jan 01 00:00:00 CET 2016
Sat Jan 02 00:00:00 CET 2016
Fri Sep 16 00:00:00 CEST 2016 ?????????

Was ist das für ein 16 September immer?
Dies sind auch 14 Daten, normal müssten es doch 18 sein.

In der Datenbank:
19 Dec 2015; 23 Dec 2015; 24 Dec 2015; 2015-12-18; 2015-12-22; 2015-12-23
01 Dec 2015; 2015-12-31; 2016-01-01; 2015-11-30; 2015-12-31; 2016-01-01;
29 Dec 2015; 30 Dec 2015; 31 Dec 2015; 2015-12-28; 2015-12-29; 2015-12-30
 
Und wie sieht der Input für DateUtil aus ? Geht beim einlesen der CSV-Datei was daneben ?
Du musst mal noch ein paar println einbauen oder debuggen.
 
Hier habe ich Ausgaben eingebaut und folgendes Ergebnis:
Java:
for (String string : nextLine) {
             System.out.println("vor: "+string);
             date = DateUtil.convertToDate(string);
             System.out.println("nach: "+string);
vor: 19 Dec 2015
nach: 19 Dec 2015
vor: 23 Dec 2015
nach: 23 Dec 2015
vor: 24 Dec 2015
nach: 24 Dec 2015
vor: 2015-12-19
Sat Dec 19 00:00:00 CET 2015
nach: 2015-12-19
vor: 2015-12-23
Wed Dec 23 00:00:00 CET 2015
nach: 2015-12-23
vor: 2015-12-24
Thu Dec 24 00:00:00 CET 2015
nach: 2015-12-24

Jetzt nur für den ersten Datensatz.

Ich verstehe nicht richtig wo die CSVLoader Klasse den entgültigen SQL Query erstellt und ausführt... Dieser wäre mal interessant zu sehen..
 
Java:
for (String string : nextLine) {
                        System.out.println("string: "+string);
                        date = DateUtil.convertToDate(string);
                        System.out.println("Date: "+date);
                        if (null != date) {
                            ps.setDate(index++, new java.sql.Date(date
                                    .getTime()));

string: 2015-02-02
Date: Mon Feb 02 00:00:00 CET 2015
string: 2015-02-02
Date: Mon Feb 02 00:00:00 CET 2015
string: 2015-02-02
Date: Mon Feb 02 00:00:00 CET 2015

Wandelt genau an der richtigen Stelle das Datum richtig um...
 
Ich verstehe nicht richtig wo die CSVLoader Klasse den entgültigen SQL Query erstellt und ausführt...
Das passiert hier:
Code:
            while ((nextLine = csvReader.readNext()) != null) {

                if (null != nextLine) {
                    int index = 1;
                    for (String string : nextLine) {
                        date = DateUtil.convertToDate(string);
                        if (null != date) {
                            ps.setDate(index++, new java.sql.Date(date
                                    .getTime()));
                        } else {
                            ps.setString(index++, string);
                        }
                    }
                    ps.addBatch();
                }

Er liest eine Zeile als String-Array ein und geht dann die einzelnen Felder durch und probiert sie erst mal in ein Datum umzuwandeln. Wenn convertToDate null zurückliefert macht er diesen ps.setString
 
Ich weiss nicht ob es funktioniert, aber vielleicht hat PreparedStatement ja eine sinnvolle toString-Methode.
Ich würde einfach vor den beiden ps.executeBatch(); mal ein System.out.println(ps); einbauen
 
Das hier:
Code:
                        date = DateUtil.convertToDate(string);
                        if (null != date) {
                            ps.setDate(index++, new java.sql.Date(date
                                    .getTime()));
bewirkt natürlich dass er erst mal jedes Feld in deiner CSV-Datei versucht in ein Datum umzuwandeln. Wenn also Inhalte drin sind die auf convertToDate passen dann wird daraus ein Datum.
 
Ahh so langsam dämmert das ganze! Du, ich bin fast 50 Einträge durchgegangen, er hat die alle richtig umgewandelt.
Aber pass auf, ich habe, wie du gesagt hast vor die ps.executeBatch() Ausgaben eingebaut, und habe nur den letzten?! Query erhalten.
Hier gibt der in der Tat die Daten falsch durch. Im Query sind genau die 3 Daten die er oben korrekt umgewandelt hat, falsch.
 
ps.setDate(index++, new java.sql.Date(date
.getTime()));
Dann ist der Fehler wohl in diesem Umfeld. .
Ich würde das mal wie folgt ändern:

Code:
                        date = DateUtil.convertToDate(string);
                        if (null != date) {
                            java.sql.Date mySqlDate = new java.sql.Date(date.getTime());
                            System.out.println("SQL-Date: "+mySqlDate+"  converted Date: "+date.getTime());
                            ps.setDate(index++, new java.sql.Date(date
                                    .getTime()));
 
string: 2015-12-19
SQL-Date: 2015-12-19 converted Date: 1450479600000
string: 2015-12-23
SQL-Date: 2015-12-23 converted Date: 1450825200000
string: 2015-12-24
SQL-Date: 2015-12-24 converted Date: 1450911600000
 
string: 2015-12-31
SQL-Date: 2015-12-31 converted Date: 1451516400000
string: 2016-01-01
SQL-Date: 2016-01-01 converted Date: 1451602800000
string: 2016-01-02
SQL-Date: 2016-01-02 converted Date: 1451689200000

so in der Ausgabe & so aus dem Query kopiert:
'2015-12-30','2015-12-31','2016-01-01'

Edit: ganz zum Schluss in einem Datensatz ist auch noch ein Datum, daher kam oben auch immer der 16. September. Und in der Ausgabe ist dieses ebenfalls so:
string: 16 Sep 2016 06:44
SQL-Date: 2016-09-16 converted Date: 1473976800000

und im Query so:
'2016-09-15'
 
Und du erhältst deshalb nur den letzten Query ausgegeben weil er ja immer versucht den ps.executeBatch() für Pakete von 1000 Zeilen auszuführen, du aber sicher weniger Zeilen hast.
 
Hm, ich gerade auch keine Erklärung. Wenn deine Daten alle um einen Tag falsch wären hätte ich ja auch schon lange auf ein Problem mit der richtigen Timezone getippt. Aber ich verstehe nicht warum nur ein paar Einträge falsch sind ? Mal drüber schlafen und vielleicht fällt den Mitlesern ja was ein
 
string: 2015-12-31
SQL-Date: 2015-12-31 converted Date: 1451516400000
Wenn man das mal in Tage umrechnet, ergibt sich folgendes:
Code:
1451516400000/1000/60/60/24=16799,95833
Bildet man die Differenz zur nächsten ganzen Zahl und rechnet das Ergebnis in Stunden um, bekommt man folgendes Ergebnis:
Code:
16800-16799,95833=0,041666667
0,041666667*24=1
Es ergibt sich also genau eine Stunde Abweichung. Da ist der Verdacht naheliegend, dass es etwas mit dem Unterschied zwischen CET und GMT oder mit der Sommerzeit zu tun hat.
 
Ich habe auch noch einmal die Datensätze geprüft und ich habe mich vertan... es sind alle Daten falsch... sobald er das Datum umwandelt ist es immer -1 Tag.

Aber erst im Query...

Das passt ja auch, wenn er das Datum z.b. auf den 28.09.2016 setzt und die Zeit auf 00:00:00 setzt und davon eine Stunde zu wenig umwandelt haben wir den 27.

Also stimmt irgendwas mit der Timezone bei der umwandlung nicht?
 
Zuletzt bearbeitet:
Probiere doch testweise mal aus, was in der Datenbank ankommt, wenn du die Zeitzone des SimpleDateFormat-Objekts auf GMT änderst:
Java:
format.setTimeZone(TimeZone.getTimeZone("GMT"));

EDIT: Etwas zeitgemäßer wäre wohl UTC statt GMT. Müsste für den Test aber egal sein.
 
Zuletzt bearbeitet:
Wow, ich glaub es nicht, es hat geklappt! Es lag an der Zeitzone...

Ich hatte schon die Spalten in der Datenbank auf varchar umgestellt weil ich vielleicht dachte das es doch an der Datenbank liegt... Aber es hat jetzt geklappt.

Sorry JStein, hätte ich sofort richtig geguckt wäre sofort klar gewesen das es an der Zeitzone liegt... Nur manchmal hat der die Datumspalten, die nur Strings anzeigen auch umgewandelt und dann eben auch falsch und ich dachte die Strings wären richtig, daher dachte ich (weil die datensätze gleich waren) das er ab und zu richtig einträgt.

Vielen, vielen Dank! Ihr habt mir sehr geholfen!
 
Das war aber nur ein Test. Bin nicht sicher, ob es für den praktischen Einsatz wirklich der richtige Weg ist, einfach die Zeitzone im SimpleDateFormat anzupassen. Das kommt mir eigentlich seltsam vor. Ich würde mich lieber erkundigen, was da best practice ist. Falls du jetzt TIMESTAMP-Felder hast, könntest du noch prüfen, ob man mit einfachen DATE-Feldern dem Problem ausweichen kann. Du scheinst ja keine Uhrzeit zu brauchen und der Umgang damit kann ganz schön tückisch sein.
 
Sorry JStein, hätte ich sofort richtig geguckt wäre sofort klar gewesen das es an der Zeitzone liegt
Guten Morgen. Macht doch nichts. Jetzt hast du gelernt dass man genau hingucken muss um einen Fehler zu finden, hast was über die Arbeitsweise deines CSV-Lesers gelernt und über Zeitformate. Hätte alles sofort funktioniert dann hättest du dir diesen Code wahrscheinlich gar nicht so genau angeguckt.
 

Zurück
Oben