none
Select WHERE IN mit 4000 IDs RRS feed

  • Frage

  • Hallo zusammen,

    mal ne Frage:
    spricht irgend etwas grundsätzlich dagegen, wenn ein Select Statement abgesetzt wird, in dem ein WHERE id IN mit 4000 IDs vorhanden ist?

    Gruss an Alle
    Peter

    Montag, 4. Februar 2013 11:11

Antworten

  • Hallo Peter,

    Zum einen: Für 4000 Zeilen sollte es keine 20 Sekunden dauern, alles über einer Sekunde dürfte auf anderem Wege zustande kommen.

    Was Dir das im Wege stehen könnte ist die Datenbindung, die während der Aktion prüft. ob sie ihre Daten zu aktualisieren muss (oder es gar tut). Wenn Du eine BindingSource verwendest, klemme sie solange via SuspendBinding ab und stelle sie am Ende via ResumeBinding wieder her.

    Zum zweiten: Wie hier schon mehrfach gesagt, sollte man größere Mengen via IN (...) eher meiden. Mit SQL Server 2008 R2 und später gibt es die Möglichkeit über Tabellenwertparameter gleiches zu erreichen.

    Ein Beispiel für die AdventureWorks, wobei hier die Detail Daten zu einer größeren Anzahl (siehe Kommentar) abgerufen werden:

    Imports System.Data.SqlClient
    Imports System.Text
    
    ''' <summary>
    ''' Erforder einen Tabellen Wert Typ in der AdventureWorks:
    ''' CREATE TYPE dbo.SalesOrderTableType AS TABLE ( SalesOrderID int PRIMARY KEY);
    ''' </summary>
    Public Class SqlTableParameter
        Public Shared Sub Run()
            Dim c As New SqlTableParameter()
            c.SelectWithInClause()
            c.SelectWithTableParameter()
        End Sub
    
        Public Sub SelectWithTableParameter()
            Dim orderTable = GetSalesOrders()
            ' hier: 4594
            Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count)
            Dim detailsTable = SelectOrderDetailsWithTableType(orderTable)
            ' hier 16865
            Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count)
        End Sub
    
        Public Sub SelectWithInClause()
            Dim orderTable = GetSalesOrders()
            ' hier: 4594
            Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count)
            Dim detailsTable = SelectOrderDetailsWithInClause(orderTable)
            ' hier 16865
            Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count)
        End Sub
    
        Private Function GetSalesOrders() As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim table As New DataTable("SalesOrderHeader")
            table.Columns.Add("SalesOrderID", GetType(Integer))
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Using adapter = New SqlDataAdapter(
                    "SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE TerritoryID = 1",
                    connection)
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("GetSalesOrders: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
        Private Function SelectOrderDetailsWithInClause(orderTable As DataTable) As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim inBuilder As New StringBuilder(orderTable.Rows.Count * 6)
            For Each row As DataRow In orderTable.Rows
                inBuilder.Append(CInt(row("SalesOrderID")).ToString(System.Globalization.CultureInfo.InvariantCulture))
                inBuilder.Append(","c)
            Next
            inBuilder.Length -= 1
    
            Dim selectSql = "SELECT * FROM Sales.SalesOrderDetail AS Sod " & _
                "WHERE SalesOrderID IN ( " & inBuilder.ToString() & ");"
            'Console.WriteLine("SelectSQL Length: {0}, Text: '{1}'", selectSql.Length, selectSql)
    
            Dim table As New DataTable("SalesOrderDetail")
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Dim selectCommand = New SqlCommand(selectSql, connection)
                Using adapter = New SqlDataAdapter(selectCommand)
                    adapter.MissingMappingAction = MissingMappingAction.Passthrough
                    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
    
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("SelectOrderDetailsWithInClause: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
        Private Function SelectOrderDetailsWithTableType(orderTable As DataTable) As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim table As New DataTable("SalesOrderDetail")
    
            Dim selectSql = "SELECT sod.* FROM Sales.SalesOrderDetail AS sod " & _
                "INNER JOIN @SalesOrderIds AS soi ON sod.SalesOrderID = soi.SalesOrderID;"
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Dim selectCommand = New SqlCommand(selectSql, connection)
    
                ' DataTable als Parameter
                Dim tableParameter = selectCommand.Parameters.AddWithValue("@SalesOrderIds", orderTable)
                tableParameter.SqlDbType = SqlDbType.Structured
                tableParameter.TypeName = "dbo.SalesOrderTableType"
    
                Using adapter = New SqlDataAdapter(selectCommand)
                    adapter.MissingMappingAction = MissingMappingAction.Passthrough
                    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
    
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("SelectOrderDetailsWithTableType: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
    End Class
    

    Die Laufzeit ist so ziemlich gleich, aber hat nicht mehr das Problem einer elendig langen SELECT Klausel, die doch irgendwann ans Ende kommt.

    Gruß Elmar

    • Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:04
    Dienstag, 5. Februar 2013 11:04
    Beantworter
  • Hallo Peter,

    setze zudem RaiseListChangedEvents auf False, denn durch den Filter werden sonst immer noch reichlich Ereignisse verschickt.

    Die Zeiten (für alle):

    Rows loaded 19973
    ' Ohne RaiseListChangedEvents
    Time spent:  Choose=True => 2612 ms
    Time spent:  Choose=False => 1669 ms ' Mit RaiseListChangedEvents Time spent:  Choose=True => 12463 ms
    Time spent:  Choose=False => 26722 ms

    (Das Zappeln in der Titelzeile war dabei abgestellt, kostet auch ein, zwei Sekunden).

    Wobei ich noch rätsele, warum False (Standard) doppelt solange braucht wie True bei aktiven Ereignissen (und umgekehrt wenn aus); bei angeglichener Codestrecke:

    Public Class ContactForm
        Private Sub ContactForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim rowCount = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact)
            Console.WriteLine("Rows loaded {0}", rowCount)
            ContactBindingSource.Filter = "Choose = True"
        End Sub
    
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
            SetChooseState(True)
        End Sub
    
        Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
            SetChooseState(False)
        End Sub
    
    #Const SuppressListChanged = False
    
        Private Sub SetChooseState(chooseState As Boolean)
            Dim watch = Stopwatch.StartNew()
    
            Me.ContactBindingSource.SuspendBinding()
    #If (SuppressListChanged) Then
            Me.ContactBindingSource.RaiseListChangedEvents = False
    #End If
            Try
                For Each row In Me.AdventureWorksDataSet.Contact
                    row.Choose = chooseState
                    'Me.Text = String.Format("Choose {0} ({1})", chooseState, row.ContactID)
                Next
            Finally
    #If (SuppressListChanged) Then
                Me.ContactBindingSource.RaiseListChangedEvents = True
                Me.ContactBindingSource.ResetBindings(False)
    #End If
                Me.ContactBindingSource.ResumeBinding()
            End Try
    
            watch.Stop()
            Debug.WriteLine("Time spent:  Choose={0} => {1} ms", chooseState, watch.ElapsedMilliseconds)
        End Sub
    End Class
    Gruß Elmar
    • Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:00
    Freitag, 8. Februar 2013 08:42
    Beantworter

Alle Antworten

  • Hallo Peter,

    schau mal hier:

      http://stackoverflow.com/questions/1869753/maximum-size-for-a-sql-server-query-in-clause-is-there-a-better-approach

    Bei derart vielen Werten ist es meist sinnvoller, die Werte in eine Liste zu stecken (Tablevariable, temp. Tabelle, ...) und die dann in der IN Klausel abzufragen, also bspw.:

    SELECT ... FROM ... WHERE ... IN ( SELECT ... FROM <Liste> )



    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


    Montag, 4. Februar 2013 12:14
    Moderator
  • Hallo Peter,

    Wenn es um den SQL Server geht, dann siehe MSDN IN (Transact-SQL) => Hinweise; das kann zu Laufzeitfehlern führen und ich hatte schon Fälle, das es bereits bei 2000 Werten auftrat.


    Olaf Helper

    Blog Xing

    Montag, 4. Februar 2013 12:24
  • Vielen Dank für Eure Antworten.

    Kann ich mich um eine zusätzliche Tabelle (Liste) drücken, indem ich

    Select * From table Where Col IN (123,123,222,....) or Col IN (456,878,888,....)
    verwende?

    Gruss Peter

    Montag, 4. Februar 2013 14:44
  • Vielen Dank für Eure Antworten.

    Kann ich mich um eine zusätzliche Tabelle (Liste) drücken, indem ich

    Select * From table Where Col IN (123,123,222,....) or Col IN (456,878,888,....)
    verwende?

    Gruss Peter

    Montag, 4. Februar 2013 14:44
  • Hallo Peter,

    bis zu bestimmten Grenzen geht das. Aber dennoch wirst Du wahrscheinlich irgendwann auf einen Fehler laufen. Daher wäre es trotzdem sinnvoll, mit einer anderen Liste zu arbeiten


    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

    Montag, 4. Februar 2013 14:46
    Moderator
  • Am 04.02.2013 12:11, schrieb peter haus:
    > Hallo zusammen,
    >
    > mal ne Frage:
    > spricht irgend etwas grundsätzlich dagegen, wenn ein Select Statement
    > abgesetzt wird, in dem ein WHERE id IN mit 4000 IDs vorhanden ist?
     
    Evtl die Länge des SQL-Statements? Das Statement bekommt ja eine
    Zeichenlänge im mittleren 5-stelligen Bereich. Keine Ahnung inwieweit da
    Beschränkungen bei den Strings oder beim SQL bestehen.
     
    Davon abgesehen sollte man IN () eigentlich vermeiden. Ein Join gegen
    eine Tabelle mit den 4000 IDs sollte deutlich schneller sein!
     
     
    Montag, 4. Februar 2013 14:51
  • Am 04.02.2013 15:44, schrieb peter haus:
    > Vielen Dank für Eure Antworten.
    >
    > Kann ich mich um eine zusätzliche Tabelle (Liste) drücken, indem ich
    >
    > Select * From table Where Col IN (123,123,222,....) or Col IN
    > (456,878,888,....)
    > verwende?
     
    Warum willst du dich denn um eine zusätzliche Tabelle drücken? Wo hast
    du da Schwierigkeiten?
     
    Je nachdem welche Datenbank du nutzt und wie die genaue Aufgabenstellung
    ist, hast du auch noch andere Möglichkeiten - temporäre Tabellen,
    Tabellenwertfunktionen u.s.w.
     
    Wenn du das Problem etwas genauer erläuterst, kann man dir vielleicht
    auch bessere Alternativen aufzeigen.
     
     
    Montag, 4. Februar 2013 15:00
  • Hallo Stefan

    herzlichen Dank für Deine Antwort.
    Ich habe mir die Links angeschaut.
    Bin mir aber in der Fülle an Infos unsicher.

    Kann ich auf dem SQL Server einfach eine Liste mit einer Spalte Guids anlegen,
    diese vor dem neu befüllen mit einem Delete * From Liste löschen?

    Gruss Peter

    Montag, 4. Februar 2013 16:31
  • Hallo Peter,

    das könntest Du, wenn Du sicher bist, dass nur ein einziger Aufruf/Zugriff gleichzeit erfolgen kann. Sobald das nicht 100%ig gegeben ist, musst Du mit einer eigenen Liste für den jeweiligen Aufruf arbeiten. Welche Option für dich in Frage kommt, hängt u.a. davon ab, wie genau die Daten wo und wann erzeugt und in die Liste geschrieben werden sollen. Das einfachste (nicht unbedingt das beste) wäre wohl, einfach eine temporäre Tabelle zu erzeugen und deine Werte dort reinzuschreiben.

    Die Spalte "Guids" sagt mir, dass Du mehrere Werte auf einmal dort ablegen willst. Das wiederum geht nicht, sondern Du musst einen Wert pro Zeile anlegen.


    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

    Montag, 4. Februar 2013 22:03
    Moderator
  • Hi Stafan,

    ich dachte daran, eine Spalte GUID und darin n Datansätze ablegen, genau so viele wie ich in der IN Abfrage durch Komma separierte IDs habe.

    Da nehrere Benutzer zugreifen können, brauche ich einen Liste.

    Hättest du grade eine Link parat wie das gemacht wird?

    Danke
    Gruss Peter

    PS. Schaust du mal Bitte auf meine Antwort an LutzChemnitz.


    • Bearbeitet peter haus Dienstag, 5. Februar 2013 09:10
    Dienstag, 5. Februar 2013 08:46
  • Hallo Lutz,

    ich versuche mein Problem zu schildern.

    Auf dem GUI habe ich einen TreeView mit Checkbox der Kunde->Projekte anzeigt.
    Projekte können checked sein oder nicht. Es sind ca. 4000 Projekte.
    Zu allen Projekten die checked sind, werden in einem Grid die darauf angefallenen Leistungen angezeigt.
    In der DataTable Projekt und Leistungen gibt es eine ungebundene Spalte "choose", die ich in Bindingsource.Filter verwende um die Leistungen anzuzeigen oder nicht.

    Wenn im Tree ein Projekt checked gesetzt wird, wird rowProjekt.chosse=True sowie alle rowProjekt.Childrows("Leistung") auch choose=True und somit im Grid angezeigt bzw. nicht falls Checked weg genommen wird.

    Nach dem Laden von Leistungen über einen Zeitbereich muss ich über alle Projekt Rows iterieren, um alle rowProjekt.Childrows("Leistung") auch choose=True/False zu setzen.

    Das dauert je nach Anzahl Leistungen fast eine Minute.  (4000 Leistungen ca. 20 sec).
    Das stösst auf wenig Freude.

    Das Laden der Leistungen mit entsprechender idProjekt IN (Liste) nur eine Sekunde.

    Für Ideen bin ich dankbar!
    Gruss Peter

    Dienstag, 5. Februar 2013 09:08
  • Hallo Peter,

    Zum einen: Für 4000 Zeilen sollte es keine 20 Sekunden dauern, alles über einer Sekunde dürfte auf anderem Wege zustande kommen.

    Was Dir das im Wege stehen könnte ist die Datenbindung, die während der Aktion prüft. ob sie ihre Daten zu aktualisieren muss (oder es gar tut). Wenn Du eine BindingSource verwendest, klemme sie solange via SuspendBinding ab und stelle sie am Ende via ResumeBinding wieder her.

    Zum zweiten: Wie hier schon mehrfach gesagt, sollte man größere Mengen via IN (...) eher meiden. Mit SQL Server 2008 R2 und später gibt es die Möglichkeit über Tabellenwertparameter gleiches zu erreichen.

    Ein Beispiel für die AdventureWorks, wobei hier die Detail Daten zu einer größeren Anzahl (siehe Kommentar) abgerufen werden:

    Imports System.Data.SqlClient
    Imports System.Text
    
    ''' <summary>
    ''' Erforder einen Tabellen Wert Typ in der AdventureWorks:
    ''' CREATE TYPE dbo.SalesOrderTableType AS TABLE ( SalesOrderID int PRIMARY KEY);
    ''' </summary>
    Public Class SqlTableParameter
        Public Shared Sub Run()
            Dim c As New SqlTableParameter()
            c.SelectWithInClause()
            c.SelectWithTableParameter()
        End Sub
    
        Public Sub SelectWithTableParameter()
            Dim orderTable = GetSalesOrders()
            ' hier: 4594
            Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count)
            Dim detailsTable = SelectOrderDetailsWithTableType(orderTable)
            ' hier 16865
            Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count)
        End Sub
    
        Public Sub SelectWithInClause()
            Dim orderTable = GetSalesOrders()
            ' hier: 4594
            Console.WriteLine("Orders = {0} rows", orderTable.Rows.Count)
            Dim detailsTable = SelectOrderDetailsWithInClause(orderTable)
            ' hier 16865
            Console.WriteLine("Details = {0} rows", detailsTable.Rows.Count)
        End Sub
    
        Private Function GetSalesOrders() As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim table As New DataTable("SalesOrderHeader")
            table.Columns.Add("SalesOrderID", GetType(Integer))
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Using adapter = New SqlDataAdapter(
                    "SELECT SalesOrderID FROM Sales.SalesOrderHeader WHERE TerritoryID = 1",
                    connection)
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("GetSalesOrders: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
        Private Function SelectOrderDetailsWithInClause(orderTable As DataTable) As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim inBuilder As New StringBuilder(orderTable.Rows.Count * 6)
            For Each row As DataRow In orderTable.Rows
                inBuilder.Append(CInt(row("SalesOrderID")).ToString(System.Globalization.CultureInfo.InvariantCulture))
                inBuilder.Append(","c)
            Next
            inBuilder.Length -= 1
    
            Dim selectSql = "SELECT * FROM Sales.SalesOrderDetail AS Sod " & _
                "WHERE SalesOrderID IN ( " & inBuilder.ToString() & ");"
            'Console.WriteLine("SelectSQL Length: {0}, Text: '{1}'", selectSql.Length, selectSql)
    
            Dim table As New DataTable("SalesOrderDetail")
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Dim selectCommand = New SqlCommand(selectSql, connection)
                Using adapter = New SqlDataAdapter(selectCommand)
                    adapter.MissingMappingAction = MissingMappingAction.Passthrough
                    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
    
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("SelectOrderDetailsWithInClause: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
        Private Function SelectOrderDetailsWithTableType(orderTable As DataTable) As DataTable
            Dim watch = Stopwatch.StartNew()
    
            Dim table As New DataTable("SalesOrderDetail")
    
            Dim selectSql = "SELECT sod.* FROM Sales.SalesOrderDetail AS sod " & _
                "INNER JOIN @SalesOrderIds AS soi ON sod.SalesOrderID = soi.SalesOrderID;"
            Using connection = New SqlConnection(My.Settings.AdventureWorksConnectionString)
                Dim selectCommand = New SqlCommand(selectSql, connection)
    
                ' DataTable als Parameter
                Dim tableParameter = selectCommand.Parameters.AddWithValue("@SalesOrderIds", orderTable)
                tableParameter.SqlDbType = SqlDbType.Structured
                tableParameter.TypeName = "dbo.SalesOrderTableType"
    
                Using adapter = New SqlDataAdapter(selectCommand)
                    adapter.MissingMappingAction = MissingMappingAction.Passthrough
                    adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
    
                    adapter.Fill(table)
                End Using
            End Using
    
            Console.WriteLine("SelectOrderDetailsWithTableType: {0} ms", watch.ElapsedMilliseconds)
            Return table
        End Function
    
    End Class
    

    Die Laufzeit ist so ziemlich gleich, aber hat nicht mehr das Problem einer elendig langen SELECT Klausel, die doch irgendwann ans Ende kommt.

    Gruß Elmar

    • Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:04
    Dienstag, 5. Februar 2013 11:04
    Beantworter
  •  

    Hi Elmar,

    vielen Dank für die Info.

    Ich bin erst morgen wieder am PC und werde mich überzeugen, ob ich SuspendBinding einsetze.
    Ich denke ja !? Muss es mal kontrollieren.

    Mich hat es auch sehr gewundert, dass ein Durchlauf mit ca 4000 Projekten und 4000 Leistungen so lange braucht. (17 sec)

    Etwa so  (Pseudocode ,  mit LeistungBindingSource.Filter= "choose=True")

    For each rowProjekt in ProjekteBindingSource
        rowsLeistungen=rowProjekt.Childrows("Leistung") 
        for each rowL in rowsLeistungen
           rowL.choose=rowProjekt.choose
        next
    Next

    Für die Hilfe wirklich sehr dankbar
    Peter

    Dienstag, 5. Februar 2013 14:17
  • Hallo Peter,

    kann es sein, dass die Anwendung noch im Debugmodus läuft? Dazu reicht es, wenn man die Anwendung als Debug kompiliert und die dann weitergibt. Bei ASP.NET reicht dazu auch ein Eintrag in der web.config (<compilation debug="true" ... />).

    In dem Fall wäre das Verhalten normal, aber auch behebbar (durch umstellen auf Release)

    Zusätzlich wäre es aber ggfs. auch sinnvoll, rowProjekt.choose innerhalb der Schleife in eine lokale Variable zu schreiben, dann muss nicht immer auf das Objekt und auf dessen Eigenschaft zugegriffen werden.


    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


    Dienstag, 5. Februar 2013 15:18
    Moderator
  • Danke für deinen Hinweis.

    Das mit Debug muss ich mal testen, ob das wirklich so viel ausmacht.

    Im Programm wird , da False nicht gesetzt werden muss, row.choose=True zugewiesen.

    Gruss Peter

    Dienstag, 5. Februar 2013 15:52
  • Hallo Peter,

    der Debugmode kann erhebliche Performanceeinbrüche bewirken. Wie viel das effektiv bei dir ausmacht, muss man sich anschauen. Um den Faktor 10 langsamer würde mich nicht wundern.


    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

    Dienstag, 5. Februar 2013 17:35
    Moderator
  • Hallo

    ich habe meine Zeit mit testen verbraucht.

    Als erstes habe ich ein Testprogramm erstellt, in dem ich die Projekte und Leistungen lade, sowie die Leistungen in ein DatGridView schreibe.

    Für etwa je. 4000 Datensätze, braucht das Programm 3.7 sec, zu etwa 17 sec im eigentlichen Programm. Code:
    For Each drv As DataRowView In TabProjekteBindingSource.List
      rowProjekt = drv.Row
      rowsL = rowProjekt.GettabZeitRows
      If rowProjekt.Choose Then
         For Each rowL In rowsZ
            rowL.Choose = True
         Next
      End If
    Next

    Was ich nicht feststellen konnte, ist ein Unterschied in der Debug oder Release Version.

    Das Testprojekt, ConnectionString und DataSet habe ich in beiden Projekten gleich benannt. Somit konnte ich das DataSet des eigentlichen Programms, in das Testprojekt übernehmen.
    Mit der Feststellung, dass das DataSet die Bremse ist.
    Das lesen der gleichen Tabellen, mit den gleichen Expression-Spalten im neu angelegten DataSet ist etwas sechs mal schneller.
    Woher das kommt, da habe ich keine Ahnung und davon jede Menge.
    Ist es sinnvoll den DataSet neu aufzubauen und hoffen das dann alles gut ist ??

    Zudem hat das setzen von BindingSource.SuspendBinding keine Wirkung, der DataGridView dessen DataSource an das BindingSource gebunden ist, zappelt munter darauf los.

    Vielen Dank, über eure Meinung freue ich mich.
    Gruss Peter

    Mittwoch, 6. Februar 2013 15:02
  • Hallo Peter,

    wenn die Anzeige "zappelt"  hast Du vermutlich die falsche oder zuwenige BindingSource "suspended".  Das Zappeln ist ein deutlicher Hinweis darauf, dass viel Zeit mit der Anzeige verbraten wird.

    Beachte dass nicht nur die BindingSource für TabProjekteBindingSource, sondern auch untergeordnete die (direkt oder indirekt) an GettabZeitRows gebunden sind, "beruhigt" werden müssen.

    Gruß Elmar


    Mittwoch, 6. Februar 2013 18:13
    Beantworter
  • Hallo Elmar,

    Das kapiere ich mal wieder nicht, wie ich zu wenig BindingSource.Suspend aufrufen kann. Der einzige Grid.DataSource den ich finden kann, ist der eine BindingSource.
    Der Grid ist entstanden, indem die Tabelle vom DataSource auf die Form gezogen wurde.

    Ich habe mal mit der AdventureWorks getestet.
    Leider kann ich keinen Unterschied feststellen, ob BindingSource.SuspendBinding() verwendet wird oder nicht.
    Es macht auch keinen Unterschied, ob ich BindingSource.SuspendBinding() aufrufe und danach kein BindingSource.ResumeBinding()


    Meinen Test-Code habe ich angehängt.

    Zum Test habe ich die Tabelle "Contact" verwendet.
    Im Designer eine Spalte "choose" Typ Boolean hinzugefügt.
    Sowie im TableAdapter -> Konfigurieren  WHERE (ContactID <= 6000) hinzugefügt.
    Aus dem Designten DataSet die Tabelle Contact auf die Form gezogen, noch Button1 und Button2 dazu.

    Damit es richtig was zu schaffen gibt, die Form im Vollbild angezeigt.

    Mit rumzappeln, verstehe ich, dass die Srollbar rumzappelt, mit Click eines Button bleibt der Inhalt des Grid stehen und wird danach aufgebaut.
    Beziehungsweise, wenn alle auf rows auf True sind und ich erst ab dem 50ten auf False setze, bleibt der Grid so stehen wie er ist und die
    Scrollbar zappelt los.
    Jedoch kann ich kein anderes Vehalten erkennen, ob BindingSource.SuspendBinding() aufgerufen wird oder nicht.


    Blicke ich irgend etwas überhaupt nicht?
    Dir vielen Dank.
    Gruss Peter


     Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
            Dim i As Integer
            i = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact)
            ContactBindingSource.Filter = "choose = True"

        End Sub

       
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
            Dim row As AdventureWorksDataSet.ContactRow
            Dim i As Integer

            Dim watch As New Stopwatch()
            watch.Start()

            'ContactBindingSource.SuspendBinding()
            For i = 0 To AdventureWorksDataSet.Contact.Rows.Count - 1
                row = AdventureWorksDataSet.Contact.Rows(i)
                row.choose = True
                Me.Text = "setze choose=True  " & i.ToString
            Next
            'ContactBindingSource.ResumeBinding()

            watch.Stop()
            Debug.WriteLine("Time spent: Choose=True " & watch.Elapsed.ToString)
            Debug.WriteLine("Ende choose=True")
        End Sub

        Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
            Dim row As AdventureWorksDataSet.ContactRow
            Dim i As Integer

            Dim watch As New Stopwatch()
            watch.Start()

            'ContactBindingSource.SuspendBinding()
            For i = 0 To AdventureWorksDataSet.Contact.Rows.Count - 1
                row = AdventureWorksDataSet.Contact.Rows(i)
                row.choose = False
                Me.Text = "setze choose=False  " & i.ToString
            Next
            ' ContactBindingSource.ResumeBinding()

            watch.Stop()
            Debug.WriteLine("Time spent:  Choose=False " & watch.Elapsed.ToString)
            Debug.WriteLine("Ende choose=False")
        End Sub

    Donnerstag, 7. Februar 2013 11:42
  • Hallo Peter,

    setze zudem RaiseListChangedEvents auf False, denn durch den Filter werden sonst immer noch reichlich Ereignisse verschickt.

    Die Zeiten (für alle):

    Rows loaded 19973
    ' Ohne RaiseListChangedEvents
    Time spent:  Choose=True => 2612 ms
    Time spent:  Choose=False => 1669 ms ' Mit RaiseListChangedEvents Time spent:  Choose=True => 12463 ms
    Time spent:  Choose=False => 26722 ms

    (Das Zappeln in der Titelzeile war dabei abgestellt, kostet auch ein, zwei Sekunden).

    Wobei ich noch rätsele, warum False (Standard) doppelt solange braucht wie True bei aktiven Ereignissen (und umgekehrt wenn aus); bei angeglichener Codestrecke:

    Public Class ContactForm
        Private Sub ContactForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim rowCount = Me.ContactTableAdapter.Fill(Me.AdventureWorksDataSet.Contact)
            Console.WriteLine("Rows loaded {0}", rowCount)
            ContactBindingSource.Filter = "Choose = True"
        End Sub
    
        Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
            SetChooseState(True)
        End Sub
    
        Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
            SetChooseState(False)
        End Sub
    
    #Const SuppressListChanged = False
    
        Private Sub SetChooseState(chooseState As Boolean)
            Dim watch = Stopwatch.StartNew()
    
            Me.ContactBindingSource.SuspendBinding()
    #If (SuppressListChanged) Then
            Me.ContactBindingSource.RaiseListChangedEvents = False
    #End If
            Try
                For Each row In Me.AdventureWorksDataSet.Contact
                    row.Choose = chooseState
                    'Me.Text = String.Format("Choose {0} ({1})", chooseState, row.ContactID)
                Next
            Finally
    #If (SuppressListChanged) Then
                Me.ContactBindingSource.RaiseListChangedEvents = True
                Me.ContactBindingSource.ResetBindings(False)
    #End If
                Me.ContactBindingSource.ResumeBinding()
            End Try
    
            watch.Stop()
            Debug.WriteLine("Time spent:  Choose={0} => {1} ms", chooseState, watch.ElapsedMilliseconds)
        End Sub
    End Class
    Gruß Elmar
    • Als Antwort markiert peter haus Donnerstag, 14. Februar 2013 10:00
    Freitag, 8. Februar 2013 08:42
    Beantworter
  • Hallo Elmar,

    herzlichen Dank für deine Untersuchung des verhaltens.

    Inzwischen habe ich festgestellt wo bei mir die eigentlich wirklich grosse Bremse ist.
    Ich habe in der Taballe vier ungebundene Expression Spalten hinzugefügt, mit
    Parent(Rel_Name).ParentFeld. Dabei macht es von der Laufzeit keinen Unterschied ob es nur eine ist oder vier Expression Spalten sind. Die Zeiten sind bei 4000 Datensätzen 13,6sec,  bei 2000  3,1sec, bei 1000  0,8 sec. 
    Diese Zeiten ergeben sich aber nur beim iterieren (setzen von choose) mit SuspendBinding und RaiseListChangedEvents=False. Seltsamerweise nicht nach dem laden der Daten mit TableAdapter.Fill.
    Es kommt nicht oft vo, dass so viele Datensätze geladen werden, aber es sorgt für argen Unmut beim Benutzer.
    Hättest du mir eine Idee wie ich das anders löse?
    Die Daten werden in einem Readonly-Grid angezeigt, editiert wird in einer extra Form.

    Nochmals ganz vielen Dank.
    Grüsse Peter

    Samstag, 9. Februar 2013 07:43
  • Hallo Peter,

    Ausdrücke verwenden intern ähnliche Mechanismen wie die Benachrichtigungen. Wird ein Ausdruck an eine Spalte gebunden, wird die DataColumn "benachrichtigt", um ihren neuen Wert zu ermitteln. Abgewöhnen kann man ihr das nicht.

    Um Vorschläge zu machen, fehlen mir die Informationen. Mir wären die vielen Choose Werte an sich suspekt. (Wer setzt schon 4000 Häkchen manuell  - und das in weniger als 10 Sekunden ;)

    Was man evtl. machen könnte, wäre die Auswahlspalte nicht direkt in der Tabelle zu verwalten, sondern in einer separaten DataTable oder einer Auflistung.

    Gruß Elmar


    Montag, 11. Februar 2013 10:16
    Beantworter
  • Hi Elmar,

    ich freue mich sehr, dass du mir auf mein Problem antwortest!

    Wie oben beschrieben, ist der Ursprung der abhäkelei ein TreeView.
    Dort gibt es die Möglichkeit auch Kunden und deren Zweig, sprich Projekte, rein oder raus zu nehmen. Alle auf unchecked und nur einzelne Kunden/Projekte anzuzeigen, einen eigenen Filter (checked Nodes) abspeichern und laden, u.a.m.

    Anscheinend gibt es ja keinen extremen bzw. merklichen Zeitverlust durch das setzen der Expression Spalten, direkt beim laden mit Fill.  
    Und wieso steigt die benötigte Zeit mit Anzahl der Rows nicht linear an, sondern so extrem ?
    Spekulativ: Gibt es evtl. die Möglichkeit, dass erst nach Durchlauf aller Rows der Tabelle, der interne Mechanismus angeschups wird, zum setzen der Expression Spalten, alls Möglicherweise bei jedem Zugriff auf eine Row im Loop?

    Würdest du mir sagen, wie du es machen würdest, die Spaten die ich per Parent()-Expression in die DataTable aufnehme, darzustellen?

    Dank und Gruss
    Peter

    Montag, 11. Februar 2013 11:29
  • Hallo Peter,

    das die Werte nicht linear ansteigen, dürfte an den internen Strukturen liegen. Aber daran herumzudoktern ist sowieso nicht möglich.

    Und ich kann Dir leider auch nicht wirklich sagen, wie Du es anders machen könntest, denn dafür kenne ich Dein Projekt nicht genug.

    Ich würde es zunächst mit einer Ableitung von TreeNode versuchen. Da ein TreeView sowieso keine Datenbindung kennt, kann man die Auswahl dort speichern und nur einen Verweis auf die DataRow verwalten.

    Dann würden Änderungen der Auswahl die DataTable nicht tangieren und somit (zumindest dort) keine Probleme auftauchen.

    Gruß Elmar

    Montag, 11. Februar 2013 15:17
    Beantworter
  • Hallo Elmar,<o:p></o:p>

    ich bin dir wirklich ausserordentlich Dankbar für deine Antworten.<o:p></o:p>

    Das TreeView ist nicht das Problem, ich habe eine Ableitung eines Knoten mit Row.
    Aber da habe ich die Projekte und nicht die Leistungen die im Grid angezeigt werden.

    Ich möchte eine bestimmte Filterung im Grid sehen. Was ohne die Expression-Columns nicht das geringste Problem ist. Das Laden der Daten und Filtern per BindingSource geht schnell (sogar mit Expressions. Nur eben beim iteriren über die Rows ist es schlecht).

    Wie du geschrieben hast, ist wohl die interne Struktur der DataTable das
    Problem.
    Da war meine Hoffnung, du kennst evt. ein Event das man ausschalten kann,
    sodass erst nach der iteration über die rows der Tabelle, nur einmal
    aktualisiert wird.  Anstatt wie Möglicherweise bei jedem Zugriff auf eine row der Tabelle, etwas an geschupst wird.
    Da der Grid nur zur Anzeige und nicht zur Éingabe benutzt wird.
    Als letzte "Klimmzug-Lösung" überlege ich, kann ich die DataTable mit einer Abfrage mit einem Join laden, sodass ich zu den entsprechenden Feldern komme?

    Herzliche Grüsse und dir gutes gelingen
    Peter


    • Bearbeitet peter haus Dienstag, 12. Februar 2013 07:45
    Dienstag, 12. Februar 2013 07:30
  • Hallo Peter,

    was da dazwischenfunkt, kann ich leider nicht genau bestimmen. Schon beim obigen Test war ich etwas irritiert, wie nachzulesen; den Grund habe ich bisher aber nicht evaluieren können.

    Wenn es nur für die Anzeige ist: Probieren könnte man eine Kopie der DataTable mit den ausgewählten Daten zu verwenden. Oder aber die DataTable durch eine Klasse plus Auflilstung zu ersetzen. Ohne Kenntnis des Gesamtprojekts ist das aber nur phantasieren.

    BTW: Zum IN Problem ein aktueller Blog-Artikel:
    Under rare conditions, using IN clause can cause unexpected SQL behavior

    Auch wenn es eine eher exotische Variante ist und many nicht näher quantifiziert wird, zeigt es das IN Probleme machen kann.

    Gruß Elmar

    Dienstag, 12. Februar 2013 10:26
    Beantworter
  • Hallo Elmar,

    ich denke ich habe eine bzw. zwei mögliche Lösungen für mein Problem.

    Die eine die ich wohl anwende ist, vor dem iterieren über die rows, die Column Expression auf String.Empty setzen. Nach dem iterieren wieder setzen.

    quickie-Code im Testprogramm:
    Private _Kunde As String = "Parent(FK_Projekt_Zeit).Kunde"
    Private _Projekt As String = "Parent(FK_Projekt_Zeit).Projektbezeichnung"
    Private _Initialen As String = "Parent(FK_tabZeit_tabPersonal).Initialen"
    Private _KategorieName As String = "Parent(ProjektKategorie_tabZeit).KategorieName"

    Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click
    Me.ZrfDataSet.tabZeit.Columns("KategorieName").Expression = ""
    Me.ZrfDataSet.tabZeit.Columns("Kunde").Expression = ""
    Me.ZrfDataSet.tabZeit.Columns("Projekt").Expression = ""
    Me.ZrfDataSet.tabZeit.Columns("Initiale").Expression = ""

    TabZeitBindingSource.SuspendBinding()
    Me.tabZeitBindingSource.RaiseListChangedEvents = False

    For Each drv As DataRowView In TabProjekteBindingSource.List
       rowProjekt = drv.Row
       rowsL = rowProjekt.GettabZeitRows
       If rowProjekt.Choose Then
          For Each rowL In rowsL
             rowL.Choose = True
          Next
       End If
     Next

     Me.ZrfDataSet.tabZeit.Columns("KategorieName").Expression = _KategorieName
     Me.ZrfDataSet.tabZeit.Columns("Kunde").Expression = _Kunde
     Me.ZrfDataSet.tabZeit.Columns("Projekt").Expression = _Projekt
     Me.ZrfDataSet.tabZeit.Columns("Initiale").Expression = _Initialen

     Me.tabZeitBindingSource.RaiseListChangedEvents = True
     Me.tabZeitBindingSource.ResetBindings(False)

     TabZeitBindingSource.ResumeBinding()
    End Sub

    Was zuvor 13,6 sec brauchte, benötigt jetzt 0,27 sec.

    Die Andere Variante.
    Ich habe im SQL Server einen View erstellt. In der vom Designer incl. TableAdapter erstellten Tabelle, einen zusätzlicher TableAdapter erstellt, in dem ich den View aufrufe. Die ungebundenen Spalten der Tabelle KategorieName, Kunde, Projekt, Initialen zu gebundenen gemacht (da jetzt zusätzlich im View). Ein Update funktioniert auch weiter auf die Ursrungstabelle, die Update, ... Anweisungen blieben erhalten.

    Nochmals Dank für deine Hilfe.
    Grüsse Peter


    • Bearbeitet peter haus Mittwoch, 13. Februar 2013 12:51
    Dienstag, 12. Februar 2013 21:06
  • Hallo Peter,

    wenn es funktioniert - es sonst keine Nutzer gibt -, kann man die Expressions auch mal totlegen.

    Gruß Elmar

    Mittwoch, 13. Februar 2013 19:17
    Beantworter
  • Hallo Elmar,

    die Lösung ist jetzt eine andere als die ursprüngliche Frage, aber ich bin total froh, ine Lösung für das Problem gefunden zu haben. Mit den Wartezeiten war es in Breichen sehr unerfreulich.

    Dir vielen herzlichen Dank und gute Wünsche
    Peter

    Donnerstag, 14. Februar 2013 09:59