Fragensteller
SQL Syntax Check

Allgemeine Diskussion
-
Hallo liebe Forumgemeinde,
mein Problem ist, dass ich mit einem StringBuilder in einer sehr schnellen Zeit (100ms) eine SQL Query baue und da öfter mal ein Zeichen verrutscht (Klammer, Komma usw.)Dadurch kann ich die Query natürlich nicht in die Datenbank schreiben, da eine Exception auftritt.
Gibt es eine Möglichkeit den Syntax einer SQL Query vor dem reinschreiben in die Datenbank zu checken und ggf. zu reparieren? Warum verrutschen eigentlich die Zeichen bei dem StringBuilder?
strBuilder.Append("INSERT INTO sensors(pid,sid,t,val) VALUES "); for (int i = 0; i < ValueArray.Length; i++) { string sVal = ValueArray.GetValue(i).ToString().Replace(',', '.'); strBuilder.Append("(1,'" + NodeName + "','" + Timestamp + "'," + sVal + "),"); }
Könnt Ihr mir da auf die Sprünge helfen?
Danke ;)
- Typ geändert Aleksander Chalabashiev Freitag, 4. März 2016 08:32 keine Rückmeldung
Alle Antworten
-
Hallo Gerry,
sind es denn immer nur die drei Parameter die du übergibst? Was sind das für Datentypen?
Statt dem Stringbuilder würde ich den SQLCommand aufbauen und dann nur noch die Parameter übergeben. Bsp:
Using con As New SqlConnection("connectionstring") 'Command vorbereiten zum eintragen von neuen Datensätzen Dim cmd As New SqlCommand("INSERT INTO TabelleXYZ (Wert1, Wert2, Wert3) VALUES (@Wert1, @Wert2, @Wert3)", con) 'Parameter definieren cmd.Parameters.Add("@Wert1", SqlDbType.VarChar).Value = pid cmd.Parameters.Add("@Wert2", SqlDbType.VarChar).Value = sid cmd.Parameters.Add("@Wert3", SqlDbType.VarChar).Value = val 'Feuer! cmd.ExecuteNonQuery(); Next End Using
- Bearbeitet David Stania Donnerstag, 4. Februar 2016 14:12
-
Hi, Vielen Dank für deine Antwort.
Sind nur diese 3 (strings) und ein Array (deswegen die Schleife).
Mit deiner Idee bekomme ich eine Exception: Parameter '@Wert1' has already been defined.
Würde zwar klappen, aber in meinem Fall nicht. Ich baue eine SQL Query auf und puffer diese, bis diese 4000 Datensätze beinhaltet. Erst dann schreibe ich in eine DB.
Nachtrag:
Ich nutze eine MySQL DB. Mit der SQLCommand Klasse bekomme ich auch keine Exception, da geht es wohl. Mit MysqlCommand leider nicht :(
- Bearbeitet Gerry1993 Donnerstag, 4. Februar 2016 14:31
-
Hallo Gerry,
auch der MySQL/Connector kennt Parameter und die solltest Du schon aus Geschwindigkeit (und Sicherheitsgründen) verwenden:
http://dev.mysql.com/doc/connector-net/en/connector-net-tutorials-parameters.html.
Wwobei man nicht wie dort mit AddWithValue arbeiten sollte, sondern dies einmalig vorbereiten, wie es Davids Beispiel schön zeigt.
Das Erzeugen von immer neuen Zeichenketten stresst nur den Garbage Collector. Auch kann man so bei den Parametern den richtigen Datentyp angeben - spart das Replace -, anstatt mit Zeichenketten zu jonglieren.
Gruß Elmar
-
Hallo,
wenn es wirklich darum geht 4000 Inserts in weniger als 100 ms zu bewerkstelligen, kommst du um die Methode mit dem StringBuilder kaum herum. Allerdings macht der Stringbuilder normal nicht die beschriebenen Fehler.
Mit Parametern dauert es um den Faktor 10 länger, mithin ca. 400ms:
public long Prepared() { MySqlConnectionStringBuilder csb = new MySqlConnectionStringBuilder(); csb.Server = "localhost"; csb.Port = 3306; csb.Database = "test"; csb.UserID = "root"; csb.Password = ""; MySqlConnection cn = new MySqlConnection(csb.GetConnectionString(true)); try { cn.Open(); Stopwatch sw = new Stopwatch(); sw.Start(); string sql = "insert into sensors(pid, sid, t, val) values (@p1, @p2, @p3, @p4)"; MySqlCommand cmd = new MySqlCommand(sql, cn); cmd.Parameters.Add("@p1", MySqlDbType.VarChar); cmd.Parameters.Add("@p2", MySqlDbType.VarChar); cmd.Parameters.Add("@p3", MySqlDbType.DateTime); cmd.Parameters.Add("@p4", MySqlDbType.Int32); cmd.Prepare(); for (int i = 0 ; i < 4000 ; i++) { cmd.Parameters["@p1"].Value = "1"; cmd.Parameters["@p2"].Value = Guid.NewGuid().ToString(); cmd.Parameters["@p3"].Value = DateTime.Now; cmd.Parameters["@p4"].Value = i; cmd.ExecuteNonQuery(); } sw.Stop(); return sw.ElapsedMilliseconds; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { if (cn != null && cn.State == ConnectionState.Open) cn.Close(); } return -1; }
Du musst beim Stringbuilder darauf achten, gültige Werte zu verwenden. Das da irgendwas "verrutscht" habe ich so noch nicht erlebt.
Die Variante mit dem StringBuilder dauert jedenfalls ca. 40ms:
public long SB() { StringBuilder sb = new StringBuilder(); MySqlConnectionStringBuilder csb = new MySqlConnectionStringBuilder(); csb.Server = "localhost"; csb.Port = 3306; csb.Database = "test"; csb.UserID = "root"; csb.Password = ""; MySqlConnection cn = new MySqlConnection(csb.GetConnectionString(true)); try { cn.Open(); Stopwatch sw = new Stopwatch(); sw.Start(); sb.Append(@"insert into sensors(pid, sid, t, val) values "); for (int i = 0 ; i < 4000 ; i++) { DateTime d = DateTime.Now; string dt = d.Year.ToString() + d.Month.ToString().PadLeft(2, '0') + d.Day.ToString().PadLeft(2, '0') + d.Hour.ToString().PadLeft(2, '0') + d.Minute.ToString().PadLeft(2, '0') + d.Second.ToString().PadLeft(2, '0'); sb.Append(@"('1','" + Guid.NewGuid().ToString() + @"','" + dt + @"','" + i.ToString() + @"'),"); } MySqlCommand cmd = new MySqlCommand(sb.ToString().Substring(0, sb.Length-1), cn); cmd.ExecuteNonQuery(); sw.Stop(); return sw.ElapsedMilliseconds; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { if (cn != null && cn.State == ConnectionState.Open) cn.Close(); } return -1; }
Du musst natürlich sicher sein, dass in du keine ungültigen Werte einbaust. Wenn die Rohdaten aus einer "sicheren" Quelle stammen, finde ich das akzeptabel.
Gruß
-
Hallo Gerry1993,
wenn ich so etwas sehe:
string sVal = ValueArray.GetValue(i).ToString().Replace(',', '.');
dann gruselt es mich. Ich nehme an, dass es eigentlich ein Culture Neutrales Ergebnis werden sollte, das macht man so:
string sVal = ValueArray.GetValue(i).ToString(CultureInfo.InvariantCulture);
Ist auch deshalb zu Empfehlen, weil solche Komma gegen Punkt Konstrukte eine beliebte Fehlerquelle sind.
Gruß
- Florian
-
Hallo,
wenn es wirklich darum geht 4000 Inserts in weniger als 100 ms zu bewerkstelligen, kommst du um die Methode mit dem StringBuilder kaum herum. Allerdings macht der Stringbuilder normal nicht die beschriebenen Fehler.
Mit Parametern dauert es um den Faktor 10 länger, mithin ca. 400ms:
public long Prepared() { MySqlConnectionStringBuilder csb = new MySqlConnectionStringBuilder(); csb.Server = "localhost"; csb.Port = 3306; csb.Database = "test"; csb.UserID = "root"; csb.Password = ""; MySqlConnection cn = new MySqlConnection(csb.GetConnectionString(true)); try { cn.Open(); Stopwatch sw = new Stopwatch(); sw.Start(); string sql = "insert into sensors(pid, sid, t, val) values (@p1, @p2, @p3, @p4)"; MySqlCommand cmd = new MySqlCommand(sql, cn); cmd.Parameters.Add("@p1", MySqlDbType.VarChar); cmd.Parameters.Add("@p2", MySqlDbType.VarChar); cmd.Parameters.Add("@p3", MySqlDbType.DateTime); cmd.Parameters.Add("@p4", MySqlDbType.Int32); cmd.Prepare(); for (int i = 0 ; i < 4000 ; i++) { cmd.Parameters["@p1"].Value = "1"; cmd.Parameters["@p2"].Value = Guid.NewGuid().ToString(); cmd.Parameters["@p3"].Value = DateTime.Now; cmd.Parameters["@p4"].Value = i; cmd.ExecuteNonQuery(); } sw.Stop(); return sw.ElapsedMilliseconds; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { if (cn != null && cn.State == ConnectionState.Open) cn.Close(); } return -1; }
Du musst beim Stringbuilder darauf achten, gültige Werte zu verwenden. Das da irgendwas "verrutscht" habe ich so noch nicht erlebt.
Die Variante mit dem StringBuilder dauert jedenfalls ca. 40ms:
public long SB() { StringBuilder sb = new StringBuilder(); MySqlConnectionStringBuilder csb = new MySqlConnectionStringBuilder(); csb.Server = "localhost"; csb.Port = 3306; csb.Database = "test"; csb.UserID = "root"; csb.Password = ""; MySqlConnection cn = new MySqlConnection(csb.GetConnectionString(true)); try { cn.Open(); Stopwatch sw = new Stopwatch(); sw.Start(); sb.Append(@"insert into sensors(pid, sid, t, val) values "); for (int i = 0 ; i < 4000 ; i++) { DateTime d = DateTime.Now; string dt = d.Year.ToString() + d.Month.ToString().PadLeft(2, '0') + d.Day.ToString().PadLeft(2, '0') + d.Hour.ToString().PadLeft(2, '0') + d.Minute.ToString().PadLeft(2, '0') + d.Second.ToString().PadLeft(2, '0'); sb.Append(@"('1','" + Guid.NewGuid().ToString() + @"','" + dt + @"','" + i.ToString() + @"'),"); } MySqlCommand cmd = new MySqlCommand(sb.ToString().Substring(0, sb.Length-1), cn); cmd.ExecuteNonQuery(); sw.Stop(); return sw.ElapsedMilliseconds; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { if (cn != null && cn.State == ConnectionState.Open) cn.Close(); } return -1; }
Du musst natürlich sicher sein, dass in du keine ungültigen Werte einbaust. Wenn die Rohdaten aus einer "sicheren" Quelle stammen, finde ich das akzeptabel.
Gruß
Danke für deine Antwort. Mit den Parametern wäre es mir auch lieber, aber ich kann leider nicht nach jedem Schleifendurchlauf ein execute machen. Ich würde gerne die Daten "puffern" und anschließend abschicken.
Mit dem Stringbuilder funktioniert es zwar, aber es gibt immer noch "paar" Ausreiser. Manchmal werden zwei Kommas hintereinander eingefügt usw.
Ich habe mal einen kompletten durchlauf geschafft, bis die DB "voll" war. 220k Einträge in 20sek. Kommt vielleicht der StringBuilder mit der Geschwindigkeit nicht zurecht?
Ein BSP:
('1','xyz','2016-02-05 11:03:16.058','5.54932378007813'),'),('1','xyz','2016-02-05 11:03:16.008','805.244402607422')
Hier sieht man so einen Fehlerfall
Als Anhang noch die Methodepublic int AddValues(string x, string y, Array arr) { for (int i = 0; i < arr.Length; i++) { string sVal = Convert.ToString(arr.GetValue(i), CultureInfo.InvariantCulture); strBuilder.Append(@"('1','" + x+ @"','" + y+ @"','" + sVal+ @"'),"); //Zähle Counter hoch strBuilderCounter++; if (strBuilderCounter == iDataSize) { string query = strBuilder.ToString(); query = query.Substring(0, query.Length - 1); cmd = new MySqlCommand(query, db); //Liste ist voll! strBuilder.Clear(); strBuilderCounter = 0; //In DB Schreiben cmd.ExecuteNonQuery(); } } return iRC; }
die iDataSize ist 4000
- Bearbeitet Gerry1993 Freitag, 5. Februar 2016 11:12
-
Hallo Gerry
Dein Code hat schlicht und einfach Fehler! Und nein, einen StringBuilder kannst Du nicht überlasten (ausgenommen Dein Rechner/Speicher hat eine Macke - eher unwahrscheinlich ;)
Davon abgesehen:
"puffern" tut man so etwas indem man eine Transaktion (MySqlTransaction) verwendet. Damit packst Du die gesamten Inserts in eine Transaktion. Kommt es zum Fehler solltest Du einen Rollback durchführen. Zudem kann man das MySqlCommand vorbereiten:
http://dev.mysql.com/doc/connector-net/en/connector-net-programming-prepared-preparing.html
Mehr eine "Mikro-Optimierung": die Parameter zwischenspeichern, als sie jeweils über die Auflistung und ihren Index anzusprechen.
Vom Laufzeitverhalten solltest Du damit nahe an Deinem "Monsterstring" liegen, der irgendwann schon durch seine Größe zum Problem werden kann.
Gruß Elmar
-
Hallo Gerry,
mit dem gezeigten Code kann der von dir genannte Fehler nicht vorkommen. Poste daher bitte den exakten Code, den Du zum Aufbau der StringBuilder Inhalte verwendest.
Ich für meinen Teil würde es aber mal mit folgendem Konstrukt probieren.
string separator = String.Empty; StringBuilder sb = new StringBuilder(); for( int i = 0 ; i < 200000 ; i++ ) { sb.Append( String.Format( @"{0} ( '1', '{1}', {2} ) ", separator, DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss.fff" ), i.ToString() ) ); if( separator == String.Empty ) separator = ","; } ...
Der obige Durchlauf benötigt bei mir (ohne natürlich etwas gegen die Datenbank abzusetzen) ca. 360ms. Wenn man die Zeile mit sb.Append durch folgendes ersetzt:
sb.Append( separator + "( '1', '" + DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss.fff" ) + "', " + i.ToString() + ")" );
spart man ca. 10ms, kommt also auf ca. 350ms. Ich für meinen Teil finde String.Format allerdings meist übersichtlicher als eine Stringverkettung, insbesondere bei mehreren Platzhaltern.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community -
Mit dem Stringbuilder funktioniert es zwar, aber es gibt immer noch "paar" Ausreiser. Manchmal werden zwei Kommas hintereinander eingefügt usw.
Ich habe mal einen kompletten durchlauf geschafft, bis die DB "voll" war. 220k Einträge in 20sek. Kommt vielleicht der StringBuilder mit der Geschwindigkeit nicht zurecht?
Ein BSP:
('1','xyz','2016-02-05 11:03:16.058','5.54932378007813'),'),('1','xyz','2016-02-05 11:03:16.008','805.244402607422')
Hier sieht man so einen Fehlerfall
Als Anhang noch die Methodepublic int AddValues(string x, string y, Array arr) ... string query = strBuilder.ToString(); query = query.Substring(0, query.Length - 1);
die iDataSize ist 4000
Zum "Fehlerfall": Deine Methode "Add..." legt die Vermutung nahe, dass du die wiederholt aufrufst, dein StringBuilder wird irgendwo anders instanziiert (wäre gut, wenn du den Codeabschnitt der Umgebung mal posten würdest).Kann es sein, dass du mehrere StringBuilder verwendest und den hier gezeigten so initialisierst:
Mal rein spekulativ:
StringBuilder sbTemp = new StringBuilder();
StringBuilder strBuilder = new StringBuilder(sbTemp.ToString()); ... AddItems(...)
Wenn dein sbTemp nun z.B. 100 "Values" enthält und du erstellst einen neuen "strBuilder" und arbeitest damit in der Add-Methode, packst du jedesmal ein Komma dazu. Du müsstest dort dann auch jeweils das letzte Zeichen (Komma) entfernen:
StringBuilder strBuilder =
new StringBuilder(sbTemp.ToString().Substring(0, sbTemp.Length - 1));Irgendwie muss das Komma ja dahin kommen. Da hast du irgendeinen kleinen Fehler gemacht. Also, poste mal den Code, wo du den "strBuilder" instanziierst" und wo du deine AddValues-Methode aufrufst. Nebenbei, enthält dein Array eigentlich nur Decimals?
Gruß
- Bearbeitet K. Pater Freitag, 5. Februar 2016 12:23
-
HI K.Pater
Das Array beinhaltet nur decimals und hat immer eine Größe von 40.
private void MonitoredItem_NotificationReceived(object sender, MonitoredItemNotificationEventArgs e) { MonitoredItem monitor = sender as MonitoredItem; MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification; string sDateTime = notification.Value.SourceTimestamp.ToString("yyyy-MM-dd HH:mm:ss.fff"); dbInterface.AddValues(monitor.NodeId.ToString(), monitor.DisplayName, sDateTime, notification.Value.Value as Array);
}
Es gibt 32 Monitored Items. Diese Methode wird im 10 Millisekunden Intervall aufgerufen
Hier wird der StringBuilder instanziiert.
public class DBInterface { StringBuilder strBuilder = new StringBuilder();
public DBInterface()
{strBuilder.Append(@"INSERT INTO sensors(pid,sid,t,val) VALUES ");
}
}
Und wenn ich 4000 Zeilen habe dann,
if (strBuilderCounter == iDataSize) { string query = strBuilder.ToString(); query = query.Substring(0, query.Length - 1); cmd = new MySqlCommand(query, db); //Liste ist voll! strBuilder.Clear(); strBuilder.Append(@"INSERT INTO sensors(pid,sid,t,val) VALUES "); strBuilderCounter = 0; cmd.ExecuteNonQuery(); }
- Bearbeitet Gerry1993 Freitag, 5. Februar 2016 12:43
-
Hallo Gerry,
da kann ich auf Anhieb leider auch nichts erkennen. Falls die Überwachung der 32 Sensoren allerdings jeweils in eigenen Threads läuft, ...
private static readonly object lo = new object(); public int AddValues(string x, string y, Array arr) { lock(lo) { ... } }
wobei dann einiges zusätzlich geändert werden müsste.
Gruß
-
Hallo Gerry,
da kann ich auf Anhieb leider auch nichts erkennen. Falls die Überwachung der 32 Sensoren allerdings jeweils in eigenen Threads läuft, ...
private static readonly object lo = new object(); public int AddValues(string x, string y, Array arr) { lock(lo) { ... } }
wobei dann einiges zusätzlich geändert werden müsste.
Gruß
Okay sehr Schade aber vielen Dank dir!
Mithilfe von Stefan konnte ich die Fehler eingrenzen aber ab und an wird der String immer noch fehlerhaft aufgebaut -
Hallo Gerry,
ohne deinen exakten und vollständigen Code können wir dir nicht sagen, wo der Fehler in deinem Code liegt. Die wichtigsten Teile fehlen leider immer noch.
P.S.: Die Zeile mit sb.Append( String.Format ... ) kann man auch anders schreiben, da die StringBuilder Klasse auch eine Methode AppendFormat hat. Die Zeile würde dann bspw. so aussehen:
sb.AppendFormat( @"{0} ( '1', '{1}', {2} ) ", separator, DateTime.Now.ToString( "yyyy-MM-dd HH:mm:ss.fff" ), i.ToString() );
Eine weitere Möglichkeit wäre, deine Werte in eine List<String> einzutragen und deren Inhalt dann mit
sb.Append( String.Join( ",", Lines ) );
in den StringBuilder zu übernehmen. In meinem obigen Beispiel braucht diese Variante bei 200.000 Durchläufen allerdings ca. 100ms länger, für deine Zwecke wäre das aber evtl. sogar die sinnvollere, weil weniger fehlerbehaftete Variante.
Gruß, Stefan
Microsoft MVP - Visual Developer ASP/ASP.NET
http://www.asp-solutions.de/ - Consulting, Development
http://www.aspnetzone.de/ - ASP.NET Zone, die ASP.NET Community
- Bearbeitet Stefan FalzModerator Freitag, 5. Februar 2016 14:27
-
Hallo Gerry1993,
bist Du zu einer Lösung gekommen?
Gruß
Aleksander
Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.