[.NET 2.0] MSSQL 2005-->Aggregation-->Access 2003

Beantwortet [.NET 2.0] MSSQL 2005-->Aggregation-->Access 2003

  • Freitag, 6. Juli 2012 11:06
     
      Enthält Code
    

    Hallo Community, für mein aktuelles Projekt gelten folgende Anforderugen: Es gibt einen MS SQL Server (2005) mit einer stetig wachsenden Reporting-Datenbank im Netz. Von dieser möchte ich mir 1. zyklisch das Delta relevanter Daten laden, 2. meinen Anforderungen entsprechend aggregieren (Differenzen bilden etc.) und anschliessend 3. zu meiner Access (2003) Datenbank hinzufuegen.

    FRAGE:

    Über einen SqlDataAdapter habe ich Daten von einer DB auf dem SQL-Server geladen.

    Wie kann ich die die Daten in einem gefüllten DataSet nun in die Access-DB schreiben (INSERT ...).

    Ich vermute, dass ich eine weitere Instanz vom Typ SqlDataAdapter brauche, mit dem entsprechenden

    query- und connectionstring. Aber wie stelle ich die Beziehung zwischen dem zweiten SqlDataAdapter

    und dem bestehenden, bereits gefüllten DataSet her??

    MfG Flux89

    • Bearbeitet Flux89 Freitag, 6. Juli 2012 11:08
    •  

Alle Antworten

  • Samstag, 7. Juli 2012 20:13
     
      Enthält Code

    using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Data; using System.Data.Common; using System.Data.SqlClient; //SQL-Provider using System.Data.OleDb; //OleDB-Provider using System.IO; //brauch ich das? namespace SQL2DS2ACCESS_Program { class Program { static void Main(string[] args) { TestClass tc = new TestClass(); tc.runSample(); } } class TestClass { #region Connection & Query Strings #region SQL-Server const string strSQLcon = @"Data Source = .; Initial Catalog = erl_test; User ID = smt_rep; pwd = x5=ghq2M"; //"." == localhost und Standard-Instanz (MSSQLSER)//Pooling = true; Min Pool Size=5; Max Pool Size=10//Passwort auslagern oder egal, da lokal gespeichert? als Hash? const string READSQLTXT = @"C:\Users\JB\Documents\Visual Studio 2005\Projects\Import\SQL2ACCESS\READSQL.txt"; //Textdatei mit SQL-Befehlen string[] SQLcmds; //String-Array fuer die SQL-Befehl static int SQLRecordCount = 0; //Wird vom StatementCompleted-EventHandler gesetzt und gibt die Anzahl der zurueckgelieferten Zeilen dar #endregion #region Access const string strOleDBcon = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Users\JB\Documents\Bachelorarbeit\5.Implementierung\2. MS Access Datenbank\MS Access Test DB\mhm_test.mdb"; const string WRITEACCESSTXT = @"C:\Users\JB\Documents\Visual Studio 2005\Projects\Import\SQL2DS2ACCESS\WRITEACCESS.txt"; //Textdatei mit SQL-Befehlen string[] ACCESScmds; //String-Array fuer die SQL-Befehle static int ACCESSRowsAffected = 0; #endregion string lastUpdated = ""; #endregion public void runSample() { //Schritt 0: lastUpdated aus Access-DB holen #region getLastUpdated lastUpdated = "13/6/2012"; // = lastUpdated(){try catch finally}; #endregion #region Connection & Command Objects #region SQL-Server SqlConnection SQLcon = new SqlConnection(strSQLcon); //statt strcon: Properties.System.Defaul.<myconnectionstring> SQLcon.StatisticsEnabled = true; //Fuer die Statistik in Schritt 3 SQLcon.ResetStatistics(); SqlCommand SQLcmd = new SqlCommand(); //zur Kommunikation mit der Datenbank SQLcmd.CommandType = CommandType.Text; //Text ist Standard-Wert #endregion #region Access OleDbConnection OleDBcon = new OleDbConnection(strOleDBcon); OleDbCommand OleDBcmd = new OleDbCommand(); OleDBcmd.CommandType = CommandType.Text; #endregion #endregion #region Open Connection try { Debug.Print("Debug.Print"); //Schritt 1: Verbindung oeffnen #region Open Connection & Print Details Console.WriteLine("\n\n\n>>Schritt 1"); #region SQL-Server SQLcon.Open(); Console.WriteLine("\n\nSQL-Datenbank-Verbindung wird geoeffnet"); Console.WriteLine("\n\nConnectionstring: {0}\n\tDatabase: {1}\n\tDatasource: {2}" + "\n\tServerversion: {3}\n\tSite: {4}\n\tState: {5}\n\tToString(): {6}", SQLcon.ConnectionString, SQLcon.Database, SQLcon.DataSource, SQLcon.ServerVersion, SQLcon.Site, SQLcon.State, SQLcon.ToString() ); #endregion #region Access OleDBcon.Open(); Console.WriteLine("\n\nAccess-Datenbank-Verbindung wird geoeffnet"); Console.WriteLine("\n\tConnectionstring: {0}\n\tDatabase: {1}\n\tDatasource: {2}" + "\n\tServerversion: {3}\n\tSite: {4}\n\tState: {5}\n\tToString(): {6}", OleDBcon.ConnectionString, OleDBcon.Database, OleDBcon.DataSource, OleDBcon.ServerVersion, OleDBcon.Site, OleDBcon.State, OleDBcon.ToString() ); #endregion #endregion //Schritt 2: Daten von erl_test.ticket in DataSet laden fillDS(SQLcon, SQLcmd, OleDBcon, OleDBcmd); //Schritt 3: Daten aus DataSet in Access schreiben //#region Configure OleDBCommand Object //ACCESScmds = this.getSQLcmds(WRITEACCESSTXT); //lade alle Befehle aus WRITEACCESS.txt //OleDBcmd.Connection = oc; //#endregion } catch (SqlException ex) //Wie kann ich zusaetzlich noch die OleDBException abfangen? ohne try (try catch) catch ? { #region SqlException Handling Console.WriteLine(ex.Message); #endregion } finally { #region Close Connection if (SQLcon.State == ConnectionState.Open) //Pruefung unnoetig? { SQLcon.Close(); //Speicher-Leck vermeiden und Connection-Pooling nutzen } Console.WriteLine("\n\nSQL-Datenbank-Verbindung wurde geschlossen ({0})", SQLcon.State); #endregion } #endregion while (true) ; //Konsole angezeigt lassen } private void fillDS(SqlConnection scon, SqlCommand scmd, OleDbConnection ocon, OleDbCommand ocmd) { Console.WriteLine("\n\n\n>>Schritt 2:"); Console.WriteLine("Datenbanken offen, lade nun Tabelle aus SQL-DB in DataSet und fuege anschliessend Records zu Access-DB hinzu"); #region Configure SQL Data Objects DataSet ds = new DataSet("DS1"); DataTable tblTickets = new DataTable("tblTickets"); //gleicher Name wie die Zieltabelle in Access der Ordnung halber ds.Tables.Add(tblTickets); #region SQL-Server SQLcmds = getSQLcmds(READSQLTXT); //lade alle Befehle aus READSQL.txt SqlDataAdapter SQLda = new SqlDataAdapter(SQLcmds[0].Trim(), scon); SQLda.SelectCommand.Parameters.AddWithValue("lastUpdated", lastUpdated); SQLda.MissingSchemaAction = MissingSchemaAction.AddWithKey; //Primaerschluessel miteinlesen SQLda.TableMappings.Add("ticket", "tblTickets"); //Quelle (SQL-Server) --> Ziel (DataSet.DataTable) //"dbo." hinzufügen? #endregion #endregion Console.WriteLine("\n\nSQL-Abfrage: " + SQLcmds[0] + "\n"); try { SQLda.Fill(ds, "tblTickets"); //"ticket" wurde eigentlich schon beim TableMapping hinzugefügt und "tblTickets" zugeordnet printDS(ds); fillAccess(ds, ocon, ocmd); } catch(SqlException ex) { Console.WriteLine(ex.Message); } finally { SQLda.Dispose(); } } private void fillAccess(DataSet filledDS, OleDbConnection ocon, OleDbCommand ocmd ) { DataRow dr = null; int maxRow = 0, insertedRows = 0; #region Access ACCESScmds = getSQLcmds(WRITEACCESSTXT); OleDbDataAdapter ACCESSda = new OleDbDataAdapter(ACCESScmds[0].Trim(), ocon); //ACCESSda.InsertCommand.CommandText = ACCESScmds[0]; #endregion try { #region tblTickets maxRow = filledDS.Tables["tblTickets"].Rows.Count;// +1; //Anzahl-Spalten, Rows zählt ab 0, daher + 1 for (int i = 0; i < maxRow; i++) //foreach DataRow in filledDS { dr = filledDS.Tables["tblTickets"].Rows[i];

    //in der naechsten Zeile gibts nen Fehler: "Object reference not set to an instance of an object." ACCESSda.InsertCommand.Parameters.AddWithValue("TicketId", dr["id"].ToString()); //ItemArray ist eine Auflistung der Spalten/Felder (Items): 0 == "id", 1 == "title", 2 == "sapnummer", 3 == "status", 4 == "type" //zunächst .ToString(), da in mhm_test.mdb alle Felder vom Typ 'text' sind if (ACCESSda.InsertCommand.ExecuteNonQuery() > 0) insertedRows++; //oder über: //OleDbCommandBuilder OleDBcb = new OleDbCommandBuilder(ACCESSda); //da.Update(ds, "DS1")? } #endregion tblTickets } catch (OleDbException oex) { Console.WriteLine(oex.Message); } finally { ACCESSda.Dispose(); } } private string[] getSQLcmds(string SQLCOMMANDS) { ArrayList al = new ArrayList(); StreamReader sr = new StreamReader(SQLCOMMANDS); while (sr.Peek() >= 0) { al.Add(sr.ReadLine().Trim()); //SQL-Befehle Zeilenweise aus Textdatei lesen } sr.Close(); return (string[])al.ToArray(typeof(string)); //String-Array uebergeben } private void printDS(DataSet ds) { string columnNames = "", columnValues = ""; Console.WriteLine("DataSetName: {0}", ds.DataSetName); Console.WriteLine("No. of DataTables: {0}", ds.Tables.Count); foreach (DataTable dt in ds.Tables) { //general info Console.WriteLine("DataTableName: {0}", dt.TableName); Console.WriteLine("No. of Rows: {0}", dt.Rows.Count); //print table foreach (DataColumn dc in dt.Columns) //prepare header { columnNames += dc.ColumnName + "| "; } columnNames = columnNames.Trim(); Console.WriteLine(columnNames + "Length = " + columnNames.Length); foreach(DataRow dr in dt.Rows) { for(int i = 0; i < dt.Columns.Count; i++) { if(!dr.IsNull(i)) //cell contains value columnValues += dr[i].ToString() + "| "; else columnValues += "<NULL>| "; } Console.WriteLine(columnValues); } } } public TestClass() { } } }



    • Bearbeitet Flux1989 Samstag, 7. Juli 2012 20:17
    •  
  • Samstag, 7. Juli 2012 20:24
     
      Enthält Code

    Hallo, habe meinen Code mal beigefuegt und die Stelle markiert, wo der Fehler auftritt:

    //in der naechsten Zeile gibts nen Fehler: "Object reference not set to an instance of an object."
                        ACCESSda.InsertCommand.Parameters.AddWithValue("TicketId", dr["id"].ToString());

    Der OleDBDataAdapter "ACCESSda" wird doch in seinem Gueltigkeitsbereich (Methode "fillAccess()") verwendet, warum sagt mir der Debugger dann, dass die Referenz auf kein Objekt zeigt ?

    Ist die Vorgehensweise

    ACCESSda.InsertCommand.ExecuteNonQuery()

    fuer jede in Access einzufuegende Zeile einzeln aufzurufen korrekt? oder liegt da zusaetzliches Fehlerpotential?

    Bin im Moment ratlos

  • Sonntag, 8. Juli 2012 08:44
     
     

    //in der naechsten Zeile gibts nen Fehler: "Object reference not set to an instance of an object." ACCESSda.InsertCommand.Parameters.AddWithValue("TicketId", dr["id"].ToString());

    Der OleDBDataAdapter "ACCESSda" wird doch in seinem Gueltigkeitsbereich (Methode "fillAccess()") verwendet, warum sagt mir der Debugger dann, dass die Referenz auf kein Objekt zeigt ?

    Vielleicht weil es das Feld "id" im DataReader nicht gibt? Prüfe mal, ob Dir dr["id"] überhaupt etwas liefert.

    Im übrigen hat das rein gar nichts mit dem SQL Server zu tun, die Frage wäre in einem .Net Forum besser platziert.


    Olaf Helper
    * cogito ergo sum * errare humanum est * quote erat demonstrandum *
    Wenn ich denke, ist das ein Fehler und das beweise ich täglich
    Blog Xing

  • Sonntag, 8. Juli 2012 11:56
     
     Beantwortet Enthält Code

    Hallo, habe meinen Code mal beigefuegt und die Stelle markiert, wo der Fehler auftritt:

    //in der naechsten Zeile gibts nen Fehler: "Object reference not set to an instance of an object."
                        ACCESSda.InsertCommand.Parameters.AddWithValue("TicketId", dr["id"].ToString());

    Der OleDBDataAdapter "ACCESSda" wird doch in seinem Gueltigkeitsbereich (Methode "fillAccess()") verwendet, warum sagt mir der Debugger dann, dass die Referenz auf kein Objekt zeigt ?

    Ist die Vorgehensweise

    ACCESSda.InsertCommand.ExecuteNonQuery()

    fuer jede in Access einzufuegende Zeile einzeln aufzurufen korrekt? oder liegt da zusaetzliches Fehlerpotential?

    Bin im Moment ratlos

    wenn Du im Moment ratlos bist, dann empfehle ich Dir die Beschreibung zu OleDBDataAdapter nochmals in Ruhe durch zulesen, danach Deinen Code im Debugger zu laden, einen Breakpoint in der Zeile

    ACCESSda.InsertCommand.Parameters.AddWithValue("TicketId", dr["id"].ToString());

    setzen und dann beim Erreichen des Breakpoints Deinen OleDBDataAdapter ACCESSda inspizieren.

    Nach einiger Zeit duerftest Du feststellen, dass das Feld InsertCommand im Objekt ACCESSda leer sein duerfte.

    Nur gerade SelectCommand wird Deiner Art den OleDBDataAdapter zu erzeugen generiert und die anderen Statements (Insert, Update, Delete) muessen entweder manuell oder mit Hilfe der Klasse OleDbCommandBuilder  erstellt werden.

    Erst nachdem auch das Feld InsertCommand definiert ist, kannst Du ueberhaupt erst Parameter hinzufuegen.


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.


  • Mittwoch, 11. Juli 2012 19:35
     
     
    Ok, der commandbuilder mit insertcommand hat gefehlt, danke
  • Freitag, 13. Juli 2012 06:54
     
     

    Noch eine Frage:

    gibt es ne Möglichkeit, den Inhalt eines DataTables (oder DataSets) in die SQL-DB zu updaten, ohne es vorher über mydataadapter.fill(mydataset) geladen zu haben?

    Hintergrund ist der: Ich will nur Records hinzufügen (insert), wozu sollte ich vorher mein dataset mit daten füllen, die ich nicht brauche ?

  • Freitag, 13. Juli 2012 09:11
     
     

    Noch eine Frage:

    gibt es ne Möglichkeit, den Inhalt eines DataTables (oder DataSets) in die SQL-DB zu updaten, ohne es vorher über mydataadapter.fill(mydataset) geladen zu haben?

    Hintergrund ist der: Ich will nur Records hinzufügen (insert), wozu sollte ich vorher mein dataset mit daten füllen, die ich nicht brauche ?

    ja die gibt es.

    funktioniert genau gleich, dh. Command erstellen, dann CommandBuilder Objekt erstellen. Dataset mit neuen Records erstellen und schlussendlich Aufruf von Update von DataAdapter Objekt.

    Du musst dann jedoch sicherstellen, dass der Record noch nicht in der DB existiert, da RowState im DataSet verwendet wird, um entweder INSERT, DELETE oder UPDATE Statements an die DB zu schicken.


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Mittwoch, 18. Juli 2012 10:52
     
     

    funktioniert genau gleich, dh. Command erstellen, dann CommandBuilder Objekt erstellen. Dataset mit neuen Records erstellen und schlussendlich Aufruf von Update von DataAdapter Objekt.

    Du musst dann jedoch sicherstellen, dass der Record noch nicht in der DB existiert, da RowState im DataSet verwendet wird, um entweder INSERT, DELETE oder UPDATE Statements an die DB zu schicken.

    Hättest du vielleicht ein kleines Code-Beispiel dazu ?

    Danke

  • Mittwoch, 18. Juli 2012 11:13
     
     

    eigentlich koennte man voraussetzen, dass man diese bereits kennt oder in den MSDN/Technet Seiten unter den entsprechenden Beschreibungen der Klassen/Methoden finden sollte.

    Trotzdem hier ein Link auf eine Seite welches dies ausfuehrlich beschreibt

    http://www.codeproject.com/Articles/3805/Inserting-relational-data-using-DataSet-and-DataAd


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Donnerstag, 19. Juli 2012 08:44
     
      Enthält Code

    FillSchema war das Schlüsselwort. Darüber stelle ich die konkrete Beziehung zwischen DataTable und DB-Tabelle her.

    Mein Problem ist jetzt:

    Wenn ich folgendes ausführe

    OleDBDataAdapter myDA = new OleDbDataAdapter("SELECT * FROM tblTest1", myCon);
    
    myDA.FillSchema(myDS, SchemaType.Mapped, "myDT1");
    
    myDA.SelectCommand.CommandText = "SELECT * FROM tblTest2";
    myDA.FillSchema(myDS, SchemaType.Mapped, "myDT2");
    
    printDS(myDS);
    
    
    
    

    , dann ist sind die Spalten von myDT1 korrekt. Die Spalten von myDT2 allerdings sind die "Summe" der Spalten aus tblTest1 und tblTest2, also einfach die Spalten von tblTest2 an die Spalten von tblTest1 drangehängt.

    Wie unterbinde ich das? Gibts nen Reset für FillSchema ?

  • Donnerstag, 19. Juli 2012 09:23
    Beantworter
     
      Enthält Code

    Hallo,

    Wenn Du einzelne Tabellen abrufst, verwende die FillSchema-Methode [1] mit SchemaType.Source
    und gib den Tabellennamen an (tblTest1, tblTest2).

    SchemaType.Mapped gibt nur Sinn, wenn Du ein DataTableMapping hast.
    Das verwendet man i. a. nur, wenn man für das DataSet / die DataTable andere Spaltenbezeichnungen benötigt.
    FillSchema verwendet für die Zuordnung table, table1 ... tableN und ordnet darüber die DataTable zu -
    wobei das nur bei Providern nützt, die mehrere Befehle unterstützen - Microsoft Jet gehört nicht dazu.

    Im übrigen empfiehlt sich die MissingSchemaAction auf AddWithKey festzulegen,
    damit der Primärschlüssel für die DataTable übernommen wird.

    Etwas Beispielcode um die Informationen für mehrere Tabellen abzurufen:

        class JetDataset
        {
            public static DataSet GetDataSet(string connectionString, params string[] tableNames)
            {
                var dataSet = new DataSet("DataSet1");
                using (var connection = new OleDbConnection(connectionString))
                {
                    connection.Open();
                    foreach (var tableName in tableNames)
                    {
                        using (var adapter = new OleDbDataAdapter(
                            "SELECT * FROM [" + tableName + "];", 
                            connection))
                        {
                            adapter.MissingMappingAction = MissingMappingAction.Passthrough;
                            adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
    
                            adapter.FillSchema(dataSet, SchemaType.Source, tableName);
                        }
                    }
                }
                return dataSet;
            }
    
            internal static void TestGetDataSet()
            {
                var dataSet = GetDataSet(Properties.Settings.Default.JetNorthwind, "Customers", "Orders", "Order Details");
                foreach (DataTable table in dataSet.Tables)
                {
                    Console.WriteLine("Tabelle: " + table.TableName);
                    foreach (DataColumn column in table.Columns)
                    {
                        Console.WriteLine("\t{0} ({1})", column.ColumnName, column.DataType);
                    }
                }
            }
        }
    
    Gruß Elmar

    [1] die Beispiele sind leider fehlerhaft.

  • Donnerstag, 19. Juli 2012 10:19
     
      Enthält Code

    Hey Elmar,

    danke für deine Antwort. Das eigentliche Problem, dass nach dem zweiten Aufruf von FillSchema die Spalten sich "aufaddieren" lag nur an meiner Ausgabefunktion

    //foreach DataTable dt in ds.Tables
    
    foreach(DataColumn dc in dt.Columns)
    {
      columnNames += dc.ColumName + " | ";
    }
    
    Console.WriteLine(columnNames);
    
    //jetzt folgt der "reset", den ich vergessen hab
    columnNames = string.empty;
    
    //Zeilen ausgeben

  • Donnerstag, 19. Juli 2012 10:53
    Beantworter
     
      Enthält Code

    Hallo,

    ich hatte mir Deinen Code zugegeben nicht weiter angeguckt...

    Das Problem in PrintDS lässt sich besser lösen, wenn man sich angewöhnt,
    Variablen erst zu deklarieren, wenn man sie braucht.

    Da Variablen nur ihrem Gültigkeitsbereich definiert sind und jeweils neu initialisiert werden,
    spart man sich zusätzliche Anweisungen.
    Außerdem wird es übersichtlicher, da man die Deklaration in der Nähe der Verwendung findet.

    Und so würde eine kleine Änderung reichen:

        private void printDS(DataSet ds)
        {
            Console.WriteLine("DataSetName: {0}", ds.DataSetName);
            Console.WriteLine("No. of DataTables: {0}", ds.Tables.Count);
            foreach (DataTable dt in ds.Tables)
            {
                //general info
                Console.WriteLine("DataTableName: {0}", dt.TableName);
                Console.WriteLine("No. of Rows: {0}", dt.Rows.Count);
    
                //print table
                string columnNames = "";
                foreach (DataColumn dc in dt.Columns) //prepare header
                {
                    columnNames += dc.ColumnName + "|  ";
                }
                columnNames = columnNames.Trim();
                Console.WriteLine(columnNames + "Length = " + columnNames.Length);
                
                
                foreach(DataRow dr in dt.Rows)
                {
                    string columnValues = "";
                    for(int i = 0; i < dt.Columns.Count; i++)
                    {
                        if(!dr.IsNull(i)) //cell contains value
                            columnValues += dr[i].ToString() + "|  ";
                        else
                            columnValues += "<NULL>|  ";
                    }
                    Console.WriteLine(columnValues);
                }
            }
        }
    
    

    Und so ist hier columnNames im foreach für die DataTable gültig
    und columnValues wird für jede DataRow neu initialisiert.

    Gruß Elmar

  • Freitag, 20. Juli 2012 16:47
     
     
    Jo hast Recht, danke!
  • Freitag, 20. Juli 2012 16:59
     
      Enthält Code

    Ich nutz diesen Thread, um noch eine kleine Frage zu stellen, diesmal hat es auch was mit SQL Server 2005 zu tun:

    Ich habe einen Quellstring, den ich als Parameter an ein SELECT-Command uebergeben moechte.

    string p_ID = fillMe(); //liefert 12345
    
    sqlStr = "SELECT * FROM myTable WHERE ID > '%@ID'"
    
    SqlDataAdapter DA = new SqlDataAdapter(sqlStr, con);
    
    DA.SelectCommand.Parameters.AddwithValue("ID", p_ID.ToString());

    PROBLEM: myTable.ID ist vom Format "TI0000012345", wobei neben der Gesamtzahl der Stellen die Zeichenfolge TI am Anfang fix ist. Allerdings ist die Anzahl der Nullen nicht fix, da die Zahl (hier: 12345) mit jedem neuen Eintrag in myTable waechst.

    Leider bekomme ich von fillMe() nur die jew. Zahl geliefert (hier: 12345) - wenn ich nun die Abfrage gegen meinen SQL-Server laufen lasse, wird komischerweise zwar kein Fehler ausgegeben, aber es werden auch myTable.IDs < 'TI0000012345' zurueckgeliefert.

    FRAGE: Wie kann ich die SQL-Abfrage gestalten, sodass, egal wie lang die jew. Zahl (z.B. 123456, 1234567, etc.), der SELECT-String korrekte Ergebnisse liefert? Den String p_ID mittels String-Operationen ins Format TI... zurueckzubasteln moechte ich moeglichst vermeiden, besser waere da ein entsprechendes SQL-Statement.



    • Bearbeitet Flux1989 Freitag, 20. Juli 2012 16:59
    •  
  • Freitag, 20. Juli 2012 17:05
     
     

    Ich nutz diesen Thread, um noch eine kleine Frage zu stellen, diesmal hat es auch was mit SQL Server 2005 zu tun:


    schlechte Idee.

    erstelle bitte einen neuen Thread dafuer - andere Forenteilnehmer werden Dir dankbar sein, wenn der Inhalt des Diskusionsthread auch dem eigentlich Inhalt entspricht.

    evt. kann ein Moderator den Thread splitten ?


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Freitag, 20. Juli 2012 17:12
     
      Enthält Code

    Ich nutz diesen Thread, um noch eine kleine Frage zu stellen, diesmal hat es auch was mit SQL Server 2005 zu tun:

    Ich habe einen Quellstring, den ich als Parameter an ein SELECT-Command uebergeben moechte.

    string p_ID = fillMe(); //liefert 12345
    
    sqlStr = "SELECT * FROM myTable WHERE ID > '%@ID'"
    
    SqlDataAdapter DA = new SqlDataAdapter(sqlStr, con);
    
    DA.SelectCommand.Parameters.AddwithValue("ID", p_ID.ToString());

    PROBLEM: myTable.ID ist vom Format "TI0000012345", wobei neben der Gesamtzahl der Stellen die Zeichenfolge TI am Anfang fix ist. Allerdings ist die Anzahl der Nullen nicht fix, da die Zahl (hier: 12345) mit jedem neuen Eintrag in myTable waechst.

    Leider bekomme ich von fillMe() nur die jew. Zahl geliefert (hier: 12345) - wenn ich nun die Abfrage gegen meinen SQL-Server laufen lasse, wird komischerweise zwar kein Fehler ausgegeben, aber es werden auch myTable.IDs < 'TI0000012345' zurueckgeliefert.

    FRAGE: Wie kann ich die SQL-Abfrage gestalten, sodass, egal wie lang die jew. Zahl (z.B. 123456, 1234567, etc.), der SELECT-String korrekte Ergebnisse liefert? Den String p_ID mittels String-Operationen ins Format TI... zurueckzubasteln moechte ich moeglichst vermeiden, besser waere da ein entsprechendes SQL-Statement.

    ich glaube der Vergleich

    ID > '%@ID'"

    ist das Problem, da der Vergleichsoperator ">" nicht mit Wildcard ("%") umgehen kann und daher alle ID zurueckliefert, welche das erste Zeichen mit ASCII-Wert groesser als 37 haben und T hat den ASCII Code 84 und daher werde alle Strings beginnend mit T zurueckgeliefert.


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Freitag, 20. Juli 2012 17:52
    Beantworter
     
     Beantwortet Enthält Code

    Hallo,

    es ist nunmal so, dass Du eine Zeichenkette (und keine Zahl) vergleichst
    und da spielt die Zahl der Nullen sehr wohl eine Rolle (TI1 ist immer größer als TI01 usw.).

    Auf ein führendes "%" sollte man generell verzichten, da es die Nutzung eines Index verhindert.

    Die einfachste und effizienteste Lösung ist es den Parameter entsprechend zu formatieren:

    DA.SelectCommand.Parameters.AddwithValue("ID", p_ID.ToString("'TI'0000000000"));
    
    // alternativ auch, z. B. wenn TI sich ändern könnte
    // AddwithValue("TI" + p_ID.ToString("0000000000"))
    

    Mehr siehe Zahlenformatzeichenfolgen

    Gruß Elmar

  • Samstag, 21. Juli 2012 09:53
     
     

    alle ID zurueckliefert, welche das erste Zeichen mit ASCII-Wert groesser als 37 haben und T hat den ASCII Code 84 und daher werde alle Strings beginnend mit T zurueckgeliefert.
    OK
  • Samstag, 21. Juli 2012 09:55
     
      Enthält Code

    Hallo,

    es ist nunmal so, dass Du eine Zeichenkette (und keine Zahl) vergleichst
    und da spielt die Zahl der Nullen sehr wohl eine Rolle (TI1 ist immer größer als TI01 usw.).

    ...

    Die einfachste und effizienteste Lösung ist es den Parameter entsprechend zu formatieren:

    p_ID.ToString("'TI'0000000000");

    "TI" + p_ID.ToString("0000000000");

    OK, da gebe ich dir recht. Wow, ich wusste nicht, dass es eine so elegante Loesung gibt, Danke!
  • Samstag, 21. Juli 2012 14:15
     
      Enthält Code

    Hallo,

    die Konvertierung der Zahl 12345 in den String TI0000012345 klappt wunderbar. Leider scheinen die Paramter im SELECT-String wirkunglos zu bleiben:

    const string ConStr = @"Data Source = .; Initial Catalog = mydb; User ID = myusr; pwd = mypwd"; //Batch-Query const string CmdStr = "SELECT TOP 5 id FROM Tickets (id > '@lastTicketId') ORDER BY id ASC;" + "SELECT TOP 5 id FROM Activities WHERE (id > '@lastActivityId') ORDER BY id ASC;" + "SELECT TOP 5 id FROM Calls WHERE (id > '@lastCallId') ORDER BY id ASC;"; DataSet DS = new DataSet("DS"); try { SqlConnection Con = new SqlConnection(ConStr); SqlDataAdapter DA = new SqlDataAdapter(CmdStr, Con); erlDA.SelectCommand.Parameters.AddWithValue("lastTicketId", lastTicketId.ToString("'TI'0000000000")); erlDA.SelectCommand.Parameters.AddWithValue("lastActivityId", lastActivityId.ToString("'AC'0000000000")); erlDA.SelectCommand.Parameters.AddWithValue("lastCallId", lastCallId.ToString("'CA'0000000000"));

    DA.Fill(DS);

    ...

    Liegt es vielleicht daran, dass Batch-Queries und Parameter nicht zusammen funktionieren?


    • Bearbeitet Flux1989 Samstag, 21. Juli 2012 14:27
    •  
  • Samstag, 21. Juli 2012 14:26
     
     Beantwortet Enthält Code

    hallo Flux1989

    kannst Du mir erklaeren, warum die Parameter im Querystring mit Apostrophe aufgefuehrt sind ?

    const string CmdStr = "SELECT TOP 5 id FROM Tickets (id > '@lastTicketId') ORDER BY id ASC;"
                                               + "SELECT TOP 5 id FROM Activities WHERE (id > '@lastActivityId') ORDER BY id ASC;"
                                               + "SELECT TOP 5 id FROM Calls WHERE (id > '@lastCallId') ORDER BY id ASC;";

    lass einfach die Apostrophe weg:

    const string CmdStr = "SELECT TOP 5 id FROM Tickets WHERE (id > @lastTicketId) ORDER BY id ASC;"
                                               + "SELECT TOP 5 id FROM Activities WHERE (id > @lastActivityId) ORDER BY id ASC;"
                                               + "SELECT TOP 5 id FROM Calls WHERE (id > @lastCallId) ORDER BY id ASC;";


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.


  • Samstag, 21. Juli 2012 14:29
     
     

    und was bezweckst Du genau mit 3 SELECT Statement ?

    ich glaube nicht, dass SqlDataAdapter damit problemlos funktioniert, denn er kann die INSERT/DELETE/UPDATE Statement evt. nicht daraus ableiten.

    die 3 SELECT Statement lieferen 3 Resultsets zurueck und nicht ein einziges mit allen id. Dazu muesstest Du ein UNION ueber die 3 Statements benutzen


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.


  • Samstag, 21. Juli 2012 14:56
     
     

    und was bezweckst Du genau mit 3 SELECT Statement ?

    ich glaube nicht, dass SqlDataAdapter damit problemlos funktioniert, denn er kann die INSERT/DELETE/UPDATE Statement evt. nicht daraus ableiten.

    Erstmal vielen Dank fuer deine Antwort. Die Apostrophen habe eingefuegt, weil in SQL Server 2005 das Query nur funktioniert, wenn um TI... Apostrophen gesetzt sind. Ich konnte nicht ahnen, dass man diese im SELECT-string in c# widerum weglassen muss :) Aber jetzt funktioniert es problemlos!

    Zu deiner zweiten Frage: Der Batch-Query liefert mir drei Resultsets, sprich es werden drei DataTables namens Table, Table1, Table2 entsprechend der Reihenfolge der SELECT-Statements befuellt. Das ist aus meiner Sicht kompakter und eleganter als drei separate SELECT-Strings zu deklarieren und dann dreimall DA.Fill(DS, Table_i) auszufuehren, oder?

    Bzgl. INSERT, UPDATE, DELETE magst du recht haben, aber in meinem Fall will ich nur vom SQL-Server lesen, nicht schreiben.

  • Samstag, 21. Juli 2012 16:56
    Beantworter
     
      Enthält Code

    Hallo,

    ergänzend zum Verständnis:
    Wenn Du in einer Abfrage mit Parametern arbeitest, so leitet sich der Datentyp daraus ab.
    Begrenzer sind dann nicht notwendig.

    Die entsprechende Abfrage im SSMS würde so aussehen (der Kürze halber beschränkt auf die erste):

    -- entspricht dem Parameter AddWithValue
    DECLARE @lastTicketID nvarchar(12);
    SET @lastTicketID = N'TI0000012345';
    
    SELECT TOP(5) id FROM Tickets WHERE (id > @lastTicketId) ORDER BY id ASC;

    (wobei parametrisierte Abfragen von .NET via sp_executesql ausgeführt werden und @lastTicketID in die Parameterliste käme).

    Hinweis:
    Bei neuen Code für SQL Server 2005 und später solltest Du den Ausdruck der TOP-Klausel in Klammern setzen
    (und dürftest auch dort einen Parameter verwenden, wenn Du die Anzahl variabel gestalten willst).

    Gruß Elmar


  • Samstag, 21. Juli 2012 18:24
     
      Enthält Code

    Elmar,

    der Vollstaendigkeitshalber:

    Du hast WHERE im SELECT Statement vergessen:

    SELECT TOP(5) id FROM Tickets WHERE (id > @lastTicketId) ORDER BY id ASC;


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Samstag, 21. Juli 2012 18:28
     
     

    ergänzend zum Verständnis:
    Wenn Du in einer Abfrage mit Parametern arbeitest, so leitet sich der Datentyp daraus ab.
    Begrenzer sind dann nicht notwendig.

    damit werden auch SQL Injection stark minimiert, da man dann den SQL String nicht mehr selber zusammensetzt und dabei evt. SQL Injection Code Sequenzen uebersehen kann.

    Ueberdies kann der SQL Server den process plan fuer die Query cachen und wieder verwenden, da der String immer der gleiche ist und nur die Parameterwerte sich unterscheiden.


    Please use Mark as Answer if my post solved your problem and use Vote As Helpful if a post was useful.

  • Samstag, 21. Juli 2012 19:58
    Beantworter
     
     

    Hallo Daniel,

    Danke - korrigiert.
    Es fehlte schon oben, von wo ich es blind kopiert hatte...

    Gruß Elmar

  • Samstag, 21. Juli 2012 21:42
     
     

    Danke - korrigiert.
    Es fehlte schon oben, von wo ich es blind kopiert hatte...

    ging mir gleich - hab es jetzt auch bei meinem Beitrag korrigiert.