Discussion:
Was ist schneller AddNew oder INSERT ???
(zu alt für eine Antwort)
Andy Dorwald
2004-02-20 17:16:51 UTC
Permalink
Hallo,

ich habe mal eine bescheide Frage zur Performance beim einfügen von
Datensätze per ADO in eine DB.

Ich frage mich nämlich schon die ganze Zeit, welche Methode
schneller/effektiver ist, wenn ich z.B. ein paar tausend Adressen
importieren will?

1.)
For i = 0 To XXX
DB.Execute "INSERT INTO Adressen (Feld1, Feld2, Feld3, ...)
VALUES(Wert1,Wert2,Wert3,...)"
Next

ODER

2.)

with RS
.OPEN "Adressen", DB, adOpenKeyset, adLockOptimistic
For i = 0 To XXX
.AddNew
!Feld1 = Wert1
!Feld2 = Wert2
!Feld3 = Wert3
[...usw...]
.UPDATE
Next
end with

ODER VIELLEICHT SOGAR...

3.)

with RS
.OPEN "Adressen", DB, adOpenKeyset, adLockOptimistic
For i = 0 To XXX
.AddNew
!Feld1 = Wert1
!Feld2 = Wert2
!Feld3 = Wert3
[...usw...]
.UPDATEBATCH
Next
end with


Ich bin echt ein wenig ratlos - ich habe auch versucht dies zu testen, aber
es war auf meinem Rechner kaum spürbar, da die DB auf gleichem PC ist, wie
das VB-Programm.

Ich MUSS übrigens wirklich Satz für Satz übergeben, da ich je potentiellem
Datensatz einige plausibilitätschecks machen muss. Ein Übergeben von ADO1 zu
ADO2 ist somit nicht möglich!

Ich würde mich WIRKLICH SEHR über eine "Empfehlung" freuen!

Viele Grüße
Andy ;-)))
Michael Zimmermann
2004-02-20 17:41:23 UTC
Permalink
Hallo!
Post by Andy Dorwald
ich habe mal eine bescheide Frage zur Performance beim
einfügen von Datensätze per ADO in eine DB.
Ich frage mich nämlich schon die ganze Zeit, welche
Methode schneller/effektiver ist, wenn ich z.B. ein
paar tausend Adressen importieren will?
1.)
[SQL]
Post by Andy Dorwald
ODER
2.)
[Recordset-Gedaddel]

Das hatten wir gerade vorhin in der Access-NG.

SQL gewinnt gegen Recordset mit einem
Geschwindigkeitsvorteil von fast 350%.

Dabei war es ein DAO-Recordset - ADO ist noch langsamer.

Gruß aus Mainz
Michael
Andy Dorwald
2004-02-20 19:15:39 UTC
Permalink
Hallo Michael,
Post by Michael Zimmermann
SQL gewinnt gegen Recordset mit einem
Geschwindigkeitsvorteil von fast 350%.
Dabei war es ein DAO-Recordset - ADO ist noch langsamer.
Lass es mich bitte noch einmal "auf der Zunge zergehen"!

UNGLAUBLICHE 350% (und höher) ist die Performance, wenn ich mit einem
gewöhnlichen INSERT die Datensätze einfüge?

Mit anderen Worten, bei z.B. 32.000 Adressen rufe ich 32.000 mal "DB.INSERT"
auf!!!?

Für was nützt denn dann UPDATEBATCH?


Vielen DANK jedenfalls schon mal!
Andy
Michael Zimmermann
2004-02-20 19:32:55 UTC
Permalink
Hallo, Andy!
Post by Andy Dorwald
Post by Michael Zimmermann
SQL gewinnt gegen Recordset mit einem
Geschwindigkeitsvorteil von fast 350%.
Dabei war es ein DAO-Recordset - ADO ist noch langsamer.
Lass es mich bitte noch einmal "auf der Zunge zergehen"!
UNGLAUBLICHE 350% (und höher) ist die Performance, wenn
ich mit einem gewöhnlichen INSERT die Datensätze einfüge?
Mit anderen Worten, bei z.B. 32.000 Adressen rufe ich
32.000 mal "DB.INSERT" auf!!!?
Kommt drauf an, wie Deine Daten zusammenkommen. Wenn Du sie
per SELECT holen kannst, hast Du nur einen Schritt.
Mit einzelnen VALUES wird der Vorteil sicher nicht so hoch
ausfallen, da Du ja auch durch die Schleife eierst.

Schau Dir einfach mal den Beispielcode für die Messung an:

dim db as Dao.Database
dim rsd as dao.Recordset
Dim fld As DAO.Field
Dim SQL As String

Set db = CurrentDb

'ttestOrtsNamen: 9000 DS

'VBA
Set rsd = db.OpenRecordset("ttestOrtsNamen", dbOpenDynaset)
Set fld = rsd.Fields("Ortname")

Do While Not rsd.EOF
rsd.Edit
fld.Value = "Test"
rsd.Update
rsd.MoveNext
Loop

rsd.Close
Set rsd = Nothing


'0,35 s / 9000 DS

'SQL
SQL = "UPDATE ttestOrtsNamenVBA SET OrtName = 'Test'"
db.Execute SQL

'Alternativ:
'qupdTest: UPDATE ttestOrtsNamenVBA SET OrtName = 'Test'
'gespeichert
'db.Execute qupdTest

'0,08 s / 9000 DS


Set db = Nothing

Geschwindigkeitsvorteil SQL: 337,5%
Die gespeichete Abfrage wäre noch einen Tick schneller.

Du kannst auch Geschwindigkeitsunterschiede von absolut
gesehen kleinen Laufzeiten mit der
QueryPeformanceCounter-API ganz gut messen, da diese
zeitlich sehr hoch auflöst.

Gruß aus Mainz
Michael
Andy Dorwald
2004-02-20 20:29:28 UTC
Permalink
Hallo Michael,
Post by Michael Zimmermann
Kommt drauf an, wie Deine Daten zusammenkommen. Wenn Du sie
per SELECT holen kannst, hast Du nur einen Schritt.
Mit einzelnen VALUES wird der Vorteil sicher nicht so hoch
ausfallen, da Du ja auch durch die Schleife eierst.
Okay, Du hast mich (selbstverständlich) überzeugt!

Ich sehe jetzt nur noch ein "ECHTES" zeitraubendes (auf Frontendseite)
Problem gegenüber AddNew.

Bei einem INSERT-String bin ich gezwungen aufzupassen, dass die zu
übergebenden Strings OHNE enthaltenes Hochkomma vorliegen.

Um dies vorzubeugen arbeite ich mit zweimaligem zeitreffsendem REPLACE (!).

Einfaches BEISPIEL:

Zunächst die Ausgangssituation:
txtBermerkung = "Ich besitze ein ' Hochkomma!"
txtInfo = "Ich besitze zwei ' ' Hochkomma!"


Nun mein Weg zum fehlerfreien SQLString...

1. Step
Wert1 = "@" & txtBermerkung & "@"
Wert2 = "@" & txtInfo & "@"

...
SQLString = "INSERT ... ... ..." & Wert1 & ", " & Wert2
...

2. Step
SQLString = Replace(SQLString, "'", "´") ' hier mach ich aus ' ein ´
!!!

3. Step
SQLString = Replace(SQLString, "@", "'") ' jetzt mach ich aus @ ein '
!!!

...DIES muss ich nun bei z.B. 35.000 Sätzen auch 35.000 mal ausführen!

Bei einem AddNew könnte ich direkt die Variable zuteilen und eventuell
enthaltene Hochkommas sind egal.

z.B. !Bermerkung = txtBermerkung ' That´s all!!!


mmmhhhh, oder habe ich hier einen DENKFEHLER (Wissenslücke???)? Wenn nicht,
frage ich mich ob dann noch immer INSERT schneller ist!?!?!?


Würde mich freuen, wenn du hierzu noch einen Kommentar hättest! ;-)))

Viele Grüße
Andy :-)
Dieter Strassner
2004-02-20 23:17:46 UTC
Permalink
Hi Andy,

ich jage alle Stringfelder durch diese Function. Es maskiert das Hochkomma
und setzt auch gleich die immer benötigten Hochkomm vor und hinter den
Feldinhalt. Das Ergebnis hänge ich dann in das SQL-Statement:
______________________

Public Function SQL_Str(ByRef varText As Variant, _
Optional ByRef bNULLvalue As Boolean = True) As String
'
' SQL-gerechte Stringaufbereitung durchführen,
'
Const HK As String = "'"

If IsNull(varText) Then
If bNULLvalue Then
SQL_Str = "NULL"
Else
SQL_Str = HK & HK
End If
Else
SQL_Str = CStr(varText)

If LenB(SQL_Str) = 0 Then
If bNULLvalue Then
SQL_Str = "NULL"
Else
SQL_Str = HK & HK
End If
Else
SQL_Str = HK & SYSTEM.Replace(SQL_Str, HK, HK & HK) & HK '
Apostroph maskieren
End If

End If

End Function
_______________________

Viele Grüße

Dieter


Rückfragen bitte nur in die Newsgroup!

EDV-Kommunikation Strassner e.K.
68623 Lampertheim
Internet: www.strassner.biz
Post by Andy Dorwald
Hallo Michael,
Post by Michael Zimmermann
Kommt drauf an, wie Deine Daten zusammenkommen. Wenn Du sie
per SELECT holen kannst, hast Du nur einen Schritt.
Mit einzelnen VALUES wird der Vorteil sicher nicht so hoch
ausfallen, da Du ja auch durch die Schleife eierst.
Okay, Du hast mich (selbstverständlich) überzeugt!
Ich sehe jetzt nur noch ein "ECHTES" zeitraubendes (auf Frontendseite)
Problem gegenüber AddNew.
Bei einem INSERT-String bin ich gezwungen aufzupassen, dass die zu
übergebenden Strings OHNE enthaltenes Hochkomma vorliegen.
Um dies vorzubeugen arbeite ich mit zweimaligem zeitreffsendem REPLACE (!).
txtBermerkung = "Ich besitze ein ' Hochkomma!"
txtInfo = "Ich besitze zwei ' ' Hochkomma!"
Nun mein Weg zum fehlerfreien SQLString...
1. Step
...
SQLString = "INSERT ... ... ..." & Wert1 & ", " & Wert2
...
2. Step
SQLString = Replace(SQLString, "'", "Ž") ' hier mach ich aus ' ein
Ž
Post by Andy Dorwald
!!!
3. Step
!!!
...DIES muss ich nun bei z.B. 35.000 Sätzen auch 35.000 mal ausführen!
Bei einem AddNew könnte ich direkt die Variable zuteilen und eventuell
enthaltene Hochkommas sind egal.
z.B. !Bermerkung = txtBermerkung ' ThatŽs all!!!
mmmhhhh, oder habe ich hier einen DENKFEHLER (Wissenslücke???)? Wenn nicht,
frage ich mich ob dann noch immer INSERT schneller ist!?!?!?
Würde mich freuen, wenn du hierzu noch einen Kommentar hättest! ;-)))
Viele Grüße
Andy :-)
Dieter Strassner
2004-02-20 23:52:37 UTC
Permalink
PS: Bitte prüfe, ob die NULL-Behandlung bei Dir genauso laufen soll. Wirst
dort vermutlich etwas anpassen müssen.


Viele Grüße

Dieter


Rückfragen bitte nur in die Newsgroup!

EDV-Kommunikation Strassner e.K.
68623 Lampertheim
Internet: www.strassner.biz
Andy Dorwald
2004-02-21 00:46:44 UTC
Permalink
Hi Dieter,
ich jage alle Stringfelder durch diese Function. [...]
Public Function SQL_Str(ByRef varText As Variant, _
Optional ByRef bNULLvalue As Boolean = True) As String
'
' SQL-gerechte Stringaufbereitung durchführen,
'
Const HK As String = "'"
If IsNull(varText) Then
If bNULLvalue Then
SQL_Str = "NULL"
Else
SQL_Str = HK & HK
End If
Else
SQL_Str = CStr(varText)
If LenB(SQL_Str) = 0 Then
If bNULLvalue Then
SQL_Str = "NULL"
Else
SQL_Str = HK & HK
End If
Else
SQL_Str = HK & SYSTEM.Replace(SQL_Str, HK, HK & HK) & HK '
Apostroph maskieren
End If
End If
End Function
_______________________
Mmmh, meinst du nicht, dass da etwas viel zeitraubendes IF...Then enthalten
ist?
Ausserdem musst du das Replace bei deiner Methode PRO FELD ausführen!

Ich habe z.B. 40 Felder je INSERT auf einmal zu senden. Dadurch müsste ich
deine Funktion 40 mal ansprechen!

Bei der von mir genannten Variante nur einmal (nämlich der vollständige
SQLString jeweils wenn er "fertiggebaut" ist).

Aber mir kommt gerade eine ganz andere Idee. In der Regel gibt es ja kaum
Einträge mit einem Hochkomma in einem Feld. Das ist Fakt (z.B. Straße, Ort,
PLZ oder auch eine Telefonnummer). Man könnte somit deine Function wie folgt
(wenn schon Pro Feld) optimieren.

Public Function SQL_Str(ByRef varText As Variant, _
Optional ByRef bNULLvalue As Boolean = True) As String

' SQL-gerechte Stringaufbereitung durchführen,

Const HK As String = "'"

IF IsNull(varText) Then ' seltenes IsNull abfangen
...wie gehabt...
ElseIF InStr(1, varText, "'") = 0 THEN ' Standard (gleich an zweiter
Stelle, bringt Performance!)
SQL_Str = HK & varText & HK
Else ' noch selteneres
Hochkomma abfangen
SQL_Str = HK & Replace(SQL_Str, HK, HK & HK) & HK
End IF

_______________________

Ich habe das jetzt mal "per Trockenübung" (ohne IDE) geschrieben. Kann also
sein, dass ein kleiner Bug drin ist. Aber vom Prinzip her stimmt es.
Jedenfalls diese Funktion ist wesentlich kürzer und erzielt das gleiche
Ergebnis wie deine! Im Idealfall folgen pro Durchlauf nur 2 IF´s. Nämlich Ob
= NULL oder = Feld-ohne-Hochkomma !

Dennoch, mir gefällt das mit dem "pro Feld" durchjagen nicht so gut. Bei
35.000 Datensätzen á 40 Felder, müsste diese knapp 1,5 Mio mal durchlaufen
werden - und jedesmal eine Typkonvertierung zu Variant.

Okay, vielleicht täusche ich mich auch und die vielen IF...THEN bringen
keinen nennenswerten Performance-Einbruch...

Zumindest ist es schonmal ein Weg!

Das mit der Konstante finde ich übrigens sehr gut!!! :-)

Viele Grüße
Andy
Dieter Strassner
2004-02-21 07:31:32 UTC
Permalink
Hi Andy,
Post by Andy Dorwald
...
Aber mir kommt gerade eine ganz andere Idee. In der Regel gibt es ja kaum
Einträge mit einem Hochkomma in einem Feld. Das ist Fakt (z.B. Straße, Ort,
PLZ oder auch eine Telefonnummer). Man könnte somit deine Function wie folgt
(wenn schon Pro Feld) optimieren.
...
Das seh ich auch so.
Post by Andy Dorwald
...
Okay, vielleicht täusche ich mich auch und die vielen IF...THEN bringen
keinen nennenswerten Performance-Einbruch...
...
Teste doch mal deine Idee und vergleiche in einer größeren Schleife mit
meiner Function. Mich würde interessieren ob sich nennenswerte Unterschiede
ergeben.

Viele Grüße

Dieter


Rückfragen bitte nur in die Newsgroup!

EDV-Kommunikation Strassner e.K.
68623 Lampertheim
Internet: www.strassner.biz
Michael Bauer
2004-02-21 07:43:21 UTC
Permalink
Hallo Andy,
Post by Andy Dorwald
Das mit der Konstante finde ich übrigens sehr gut!!!
Dann kannst Du nochwas rausholen und für "hk & hk" ebenfalls eine
Konstante anlegen.

const DHK as string ="''"
--
Viele Grüße
Michael Bauer
Peter Götz
2004-02-21 10:25:21 UTC
Permalink
Hallo Michael,
Post by Michael Zimmermann
Das hatten wir gerade vorhin in der Access-NG.
SQL gewinnt gegen Recordset mit einem
Geschwindigkeitsvorteil von fast 350%.
Entschuldige die harte Ausdrucksweise, aber das ist schlichter Unsinn.
Es gibt Anwendungsfälle, in denen das Ändern oder Hinzufügen von Daten zu
einer DB-Tabelle via Command (Insert, Update usw.) sinnvoll ist und es gibt
aber ebenso Fälle in denen es sinnvoll ist mit einem Recordset zu arbeiten
und Änderungen, Hinzufügungen und Löschungen am Recordset vorzunehmen.

Dein Vergleich wäre etwa so, als würde ich sagen ein Rennauto ist besser als
ein 20t Lastwagen.
Wenn es darauf ankommt, eine bestimmte Strecke in möglichst kurzer Zeit
zurückzulegen ist sicher der Rennwagen das geeignetere Fahrzeug. Kommt es
aber darauf an eine Last von einigen Tonnen über eine bestimmte Strecke zu
transportieren, dann ist ein Rennwagen sicher reichlich ungeeignet und der
Lastwagen wohl doch das für diesen Zweck besser geeignete Vehikel.
Post by Michael Zimmermann
Dabei war es ein DAO-Recordset - ADO ist noch langsamer.
Was sind dabei Deine Vergleichsgrössen?

Gruß aus St.Georgen
Peter Götz
www.gssg.de (mit VB-Tips u. Beispielprogrammen)
Michael Zimmermann
2004-02-21 13:48:37 UTC
Permalink
Hallo!
Post by Peter Götz
Post by Michael Zimmermann
Das hatten wir gerade vorhin in der Access-NG.
SQL gewinnt gegen Recordset mit einem
Geschwindigkeitsvorteil von fast 350%.
Entschuldige die harte Ausdrucksweise, aber das ist
schlichter Unsinn.
Schon okay, ich sag das gleiche immer zu ADOisten ;-)
Post by Peter Götz
Es gibt Anwendungsfälle, in denen das Ändern oder
Hinzufügen von Daten zu einer DB-Tabelle via Command
(Insert, Update usw.) sinnvoll ist und es gibt
aber ebenso Fälle in denen es sinnvoll ist mit einem
Recordset zu arbeiten und Änderungen, Hinzufügungen und
Löschungen am Recordset vorzunehmen.
Ich habe bisher fast immer beim Entwickeln die Varianten
SQL - DAO - ADO mit dem QueryPerformanceCounter
durchgemessen.

Ich habe noch nie einen Fall gehabt (was nicht heißt, daß
es keinen geben muß), in dem SQL nicht schneller gewesen
wäre als Recordset-Methoden.
Post by Peter Götz
Wenn es darauf ankommt, eine bestimmte Strecke in
möglichst kurzer Zeit zurückzulegen ist sicher der
^^^^^^^^^^^^^^^^^^^^^
Post by Peter Götz
Rennwagen das geeignetere Fahrzeug.
| > > SQL gewinnt gegen Recordset mit einem
| > > Geschwindigkeitsvorteil von fast 350%.
^^^^^^^^^^^^^^^^^^^^^^^

Was hab ich denn gesagt?
Post by Peter Götz
...
Was sind dabei Deine Vergleichsgrössen?
Den Beispielcode, auf den ich meine "fast 350%" beziehe,
siehe dort: c15na4$1ecme3$***@ID-28927.news.uni-berlin.de

Gruß aus Mainz
Michael
Peter Götz
2004-02-21 15:56:50 UTC
Permalink
Hallo Michael,
Post by Michael Zimmermann
Ich habe noch nie einen Fall gehabt (was nicht heißt, daß
es keinen geben muß), in dem SQL nicht schneller gewesen
wäre als Recordset-Methoden.
Dann siehst Du gerade jetzt bei der von Andy beschriebenen Aufgabenstellung
genau einen solchen Fall.

Du solltest Dir mal mein anderes Posting in diesem Thread ansehen.
Vielleicht wird Dir dann etwas deutlicher, dass es schlicht und einfach
keinen Sinn macht die Ausführungszeit von Commands und von Änderungen in
einem Recordset zu vergleichen, weil man damit in der Regel völlig
unterschiedliche Dinge verfolgt.
Viele Dinge lassen sich mit lokalen, verbindungslosen Recordset lösen ohne
überhaupt auf eine Datenbank zugreifen zu müssen.
Gerade der Anwendungsfall, denn Andy beschrieben hat, jeder Datensatz muss
auf Plausibilität geprüft werden bevor er übernommen wird, lässt sich eben
sehr viel ökonomischer mit einem Recordset, welches im Batchbetrieb arbeitet
lösen. Während der gesamten Zeit der Datenprüfung und der Datenübernahme in
das neue Recordset wird nicht ein einziges Mal die Datenbank angesprochen.
Es wird auch keine Netzlast erzeugt bei der das Verhältnis zwischen
Nutzdaten und Netwerkprotokolldaten extrem ungünstig ist.
Post by Michael Zimmermann
Post by Peter Götz
Wenn es darauf ankommt, eine bestimmte Strecke in
möglichst kurzer Zeit zurückzulegen ist sicher der
^^^^^^^^^^^^^^^^^^^^^
Post by Peter Götz
Rennwagen das geeignetere Fahrzeug.
| > > SQL gewinnt gegen Recordset mit einem
| > > Geschwindigkeitsvorteil von fast 350%.
^^^^^^^^^^^^^^^^^^^^^^^
Und was nützt das im konkreten Fall.
Da wird ein einziger Datensatz sehr schnell übertragen. Nach einigen
Millisekunden kommt dann schon wieder ein Datensatz. Für ein Netzwerk (LAN)
bedeutet dies relativ wenig Nutzdaten im Vergleich zu vielen Protokolldaten
oder anders ausgedrückt ein mässiger bis saumässiger Wirkungsgrad. Bei
einigen zig-tausend Datensätzen würde das eine reichlich überflüssige
Netzlast produzieren und zum anderen auch den Server über einen längeren
Zeitraum völlig sinnlos belasten.

Dies ist eben ein ganz typischer Fall, bei dem ein im Batchbetrieb
betriebenes Recordset sowohl die Netz- als auch die Serverlast ganz
beachtlich vermindern kann und somit im Endeffekt dazu beiträgt, dass die zu
erledigende Aufgabe eben doch sehr viel schneller erledigt werden kann.
Die Übernahme von einem Recordset ins andere erfolgt völlig unabhängig vom
Netz und vom DB-Server. Erst wenn alle Datensätze auf Plausibilität
überprüft und übernommen sind, wird die gesamte Datenmenge in einem einzigen
Rutsch vom Client zur DB übertragen.

Man könnte es vielleicht auch so ausdrücken:
Wenn Du ein Kilo Salz kaufen willst, kannst Du natürlich einige tausend mal
zum Krämer laufen und jeweils ein Salzkorn holen. Auch wenn Du ein
Supersprinter bist und das Gewicht eines einzigen Salzkorns Deine
Geschwindigkeit kaum negativ beeinflussen wird, dürfte jeder einsehen, dass
es doch vernünftiger ist, alle Salzkörner in eine Tüte zu packen und sie so
in nur einem einzigen, wegen des etwas höheren Gewichtes sogar etwas
langsameren Laufes nach Hause zu tragen.
Post by Michael Zimmermann
Was hab ich denn gesagt?
Eigentlich nur, dass Du nicht verstanden hast, wann man besser mit Commands
arbeitet und wann es sinnvoller und ökonomischer ist mit Recordsets zu
arbeiten.

Gruß aus St.Georgen
Peter Götz
www.gssg.de (mit VB-Tips u. Beispielprogrammen)
Michael Zimmermann
2004-02-21 17:43:45 UTC
Permalink
Peter Götz:
[Rennwagen, Lastwagen, Salz]

Gibt es statt blumiger hinkender Beispiele vielleicht
einen konkreten Code nebst gemessener Laufzeit?
Post by Peter Götz
Eigentlich nur, dass Du nicht verstanden hast, wann man
besser mit Commands arbeitet und wann es sinnvoller und
ökonomischer ist mit Recordsets zu arbeiten.
Falls Du überzeugen willst, sind mathematisch nachprüfbare
Fakten besser geeignet als Unverschämtheiten.
:-(

EOT
Andy Dorwald
2004-02-21 19:02:01 UTC
Permalink
Hallo Michael und Peter,
Post by Michael Zimmermann
[Rennwagen, Lastwagen, Salz]
Post by Peter Götz
Eigentlich nur, dass Du nicht verstanden hast, wann man
besser mit Commands arbeitet und wann es sinnvoller und
ökonomischer ist mit Recordsets zu arbeiten.
Falls Du überzeugen willst, sind mathematisch nachprüfbare
Fakten besser geeignet als Unverschämtheiten.
Eigentlich habt Ihr Beide Recht. Inzwischen habe ich viel (sehr viel)
gegoogelt. Auf etlichen Seiten und Nuch-Auszügen lese ich immer wieder, dass
ein INSERT um ein vielfaches schneller ist, als das AddNew-Verfahren.

Lediglich in Kombination mit meiner "Aufgabenstellung" hingt das INSERT
nach, da man nicht "bequem" die ganzen Daten "vorbereiten" kann, um sie dann
in einem Rutsch auf den Server zu schieben.

Man erkennt hier einmal wieder klar den Unterschied von Theorie und Praxis.
INSERT ist schneller (theoretisch) und AddNew (gerade in Kombination mit
lokalem UPDATE und anschl. UPDATEBATCH) ist ebenfalls schneller
(praxisnäher!). Wobei es sicherlich Fälle gibt, wo auch in der Praxis das
INSERT schneller ist (z.B. wo daten sporatisch und nicht in einem Fluss
auftreten und jederzeit fertig sind - z.B. eine Klasse die eine Art Log-Buch
schreibt).

Jedenfalls bin ich euch beiden SEHR DANKBAR für den richtigen Weg, den ich
jetzt gehe!!! ;-)))

Grüße
Andy
Peter Götz
2004-02-21 11:00:06 UTC
Permalink
Hallo Andy,
Post by Andy Dorwald
Ich frage mich nämlich schon die ganze Zeit, welche Methode
schneller/effektiver ist, wenn ich z.B. ein paar tausend Adressen
importieren will?
1.)
For i = 0 To XXX
DB.Execute "INSERT INTO Adressen (Feld1, Feld2, Feld3, ...)
VALUES(Wert1,Wert2,Wert3,...)"
Next
Wenn es darum geht einige Tausend Datensätze aus einer Tabelle in eine
Tabelle (einer anderen DB) zu kopieren eignet sich Dein SQL-Statement mit
den Values nicht sonderlich, da Du so ja jeden Datensatz einzeln anfassen
musst. Für eine grosse Menge von Datensätzen wäre dieses geeigneter:

INSERT INTO Ziel [IN ExterneDatenbank] [(Feld1[, Feld2[, ...]])]
SELECT [Quelle.]Feld1[, Feld2[, ...]
FROM Tabellenausdruck
Post by Andy Dorwald
ODER
2.)
with RS
.OPEN "Adressen", DB, adOpenKeyset, adLockOptimistic
adOpenKeyset bedingt einen serverseitigen Cursor und ist für Deinen Zweck
sicher nicht die beste Wahl und vor allem ein beachtlicher
Ressourcenfresser.
Du solltest dazu besser ein statisches Recordset mit clientseitgem Cursor
verwenden und dabei auch noch im Batchbetrieb arbeiten.
Post by Andy Dorwald
For i = 0 To XXX
.AddNew
!Feld1 = Wert1
!Feld2 = Wert2
!Feld3 = Wert3
[...usw...]
.UPDATE
Next
end with
ODER VIELLEICHT SOGAR...
3.)
with RS
.OPEN "Adressen", DB, adOpenKeyset, adLockOptimistic
Das ist Unsinn.
Ein mit CursorType adOpenKeyset geöffnetes Recordset kann nicht im
Batchbetrieb arbeiten.
Post by Andy Dorwald
For i = 0 To XXX
.AddNew
!Feld1 = Wert1
!Feld2 = Wert2
!Feld3 = Wert3
[...usw...]
.UPDATEBATCH
Das wäre eine völlig sinnlose Anwendung des Batchbetriebes, da in diesem
Fall jeder Datensatz einzeln zur DB übertragen würde. Wozu dann
Batchbetrieb?
Post by Andy Dorwald
Next
end with
Ich bin echt ein wenig ratlos - ich habe auch versucht dies zu testen, aber
es war auf meinem Rechner kaum spürbar, da die DB auf gleichem PC ist, wie
das VB-Programm.
Ich MUSS übrigens wirklich Satz für Satz übergeben, da ich je potentiellem
Datensatz einige plausibilitätschecks machen muss. Ein Übergeben von ADO1 zu
ADO2 ist somit nicht möglich!
Du musst also sowieso jeden Datensatz einzeln anfassen.
In diesem Fall würde ich ein Recordset so öffnen:

Dim i as long
Dim k as long
Dim strSQL as String
Dim RS as ADODB.Recordset
Dim lngID as Long

lngID = HöchsteBisherigeID
strSQL = "Select * From Tabelle Where ID > lngID"
' SQL-Statement liefert ein Recordset mit 0 Datensätzen

' Instanz eines Recordsets erzeugen
Set RS = New ADODB.Recordset

' *** Recordset öffnen
With RS
Set .ActiveConnection = Cnn
.Source = strSQL
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockBatchOptimistic
.Open
End With

With RS
For i = 1 to xxx
.AddNew
.Fields(0).Value = lngID + i
for k = 1 to RS.Fields.Count-1
if CheckValue(RSalt.Fields(k).Value, FieldIndex) then
.Fields(k).Value = RSalt.Fields(k).Value
end if
Next k
.Update
Next i
RS.UpdateBatch
End With

CheckValue steht symbolisch für eine Function, welche den Wert aus dem alten
Recordset prüft und True zurückgibt, wenn der Wert ok ist. Ist der Wert
nicht plausibel, dann gibt CheckValue ein False zurück.

Innerhalb der Schleife i werden so alle Datensätze aus dem Quellrecordset
(RSalt) zum neuen lokalen Recordset (RS) kopiert. Nachdem alle Datensätze im
neuen Recordset sind, werden sie mit RS.UpdateBatch in einem Rutsch zur DB
übertragen.
Post by Andy Dorwald
Ich würde mich WIRKLICH SEHR über eine "Empfehlung" freuen!
Da wäre die wichtigste Empfehlung, dass Du Dir mal die ADO-Dokumentation zu
den verschiedenen CursorTypen und CursorLocations ansiehst. Nicht jeder
CursorTyp passt zu jeder CursorLocation und umgekehrt. Ebenso ist z.B. ein
Batchbetrieb nur bei Recordsets mit clientseitigem Cursor möglich.
Clientseitiger Cursor und CursorType = adOpenKeySet sind in dieser
Kombination aber auch nicht möglich.
ADO ändert Werte aus solch unsinnigen Kombinationen ohne was zu sagen, in
Werte die zusammenpassen, sprich technisch möglich sind. Das Ergebnis einer
solchen ADO-Aktivität kann aber sehr häufig ein Recordset mit absolut
unerwünschten Eigenschaften sein. Man ist deshalb gut beraten, CursorType,
CursorLocation und LockType explizit auf zusammenpassende, sinnvolle Werte
einzustellen.

Gruß aus St.Georgen
Peter Götz
www.gssg.de (mit VB-Tips u. Beispielprogrammen)
Andy Dorwald
2004-02-21 15:26:35 UTC
Permalink
Hallo Peter,
Post by Peter Götz
INSERT INTO Ziel [IN ExterneDatenbank] [(Feld1[, Feld2[, ...]])]
SELECT [Quelle.]Feld1[, Feld2[, ...]
FROM Tabellenausdruck
Klaro, wenn ich 1:1 kopieren will (bzw. Auswahlfelder direkt übergeben
will), gehe ich auch diesen Weg!
Ich kann u.a. aus folgendem Grund dies NICHT anwenden. Die Zeil-DB hat für
bestimmte String-Felder eine bestimmte Größe. Als Beispiel ein Feld z.B.
KurzInfo mit 120 Zeichen. Wenn nun die zu importierenden "Fremddatensätze"
125 Zeichen haben, knallt es!
Aber auch so Fälle wo man ein Datumsfeld "erwartet" aber die
"Fremddatensätze" etwas fehlerhaft sind (z.B. eine manuell überarbeitete
Excel-Tabelle), dass zwischendrinn in einem Datumsfeld ein Wort steht!
Post by Peter Götz
Post by Andy Dorwald
.OPEN "Adressen", DB, adOpenKeyset, adLockOptimistic
adOpenKeyset bedingt einen serverseitigen Cursor und ist für Deinen Zweck
sicher nicht die beste Wahl und vor allem ein beachtlicher
Ressourcenfresser.
Du solltest dazu besser ein statisches Recordset mit clientseitgem Cursor
verwenden und dabei auch noch im Batchbetrieb arbeiten.
[...]
Ein mit CursorType adOpenKeyset geöffnetes Recordset kann nicht im
Batchbetrieb arbeiten.
Okay, verstanden!
Post by Peter Götz
Post by Andy Dorwald
For i = 0 To XXX
.AddNew
!Feld1 = Wert1
[...usw...]
.UPDATEBATCH
NEXT
Das wäre eine völlig sinnlose Anwendung des Batchbetriebes, da in diesem
Fall jeder Datensatz einzeln zur DB übertragen würde. Wozu dann
Batchbetrieb?
Uuups & LOL. Okay, dass UPDATEBATCH ist natürlich definitiv an der falschen
Stelle. Das weiß ich natürlich auch, es war somit nur ein billiger
Tippfehler!
Post by Peter Götz
Du musst also sowieso jeden Datensatz einzeln anfassen.
Dim i as long, k as long, strSQL as String, RS as ADODB.Recordset,
lngID as Long
Post by Peter Götz
lngID = HöchsteBisherigeID
strSQL = "Select * From Tabelle Where ID > lngID"
' SQL-Statement liefert ein Recordset mit 0 Datensätzen
Dazu müsste ich die bisher höchste erstmal wissen. Kann ich nicht einfach
fragen "...ID < 0" ?
Post by Peter Götz
' Instanz eines Recordsets erzeugen
Set RS = New ADODB.Recordset
' *** Recordset öffnen
With RS
Set .ActiveConnection = Cnn
.Source = strSQL
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockBatchOptimistic
.Open
End With
With RS
For i = 1 to xxx
.AddNew
.Fields(0).Value = lngID + i
for k = 1 to RS.Fields.Count-1
if CheckValue(RSalt.Fields(k).Value, FieldIndex) then
.Fields(k).Value = RSalt.Fields(k).Value
end if
Next k
.Update
Next i
RS.UpdateBatch
End With
Fantastische Idee, dass ganze zunächst in ein lokales RS zu werfen!!! 1000
Punkte! :-)))

Übrigens, dass For...Next habe ich nur in diesem Beispiel verwendet. In der
echten Anwendung frage ich nach einem .EOF und gehe mit einem .MoveNext und
LOOP durch. Ich wollte in dem Beispiel lediglich kein weiteres Resultset
reinbringen.

Wie ist das eigentlich mit diesem UPDATEBATCH? Angenommen ich habe nun mein
lokales 35.000 Datensätze RS fertig. Wenn ich dann das UPDATEBATCH anwerfe,
meckert er dann nicht, dass es z.B. zu viele Datensätze auf einmal sind oder
so? Ganz düster kann ich mich an soetwas vor ein paar Jahren zurückerinnern.
Da musste ich einmal alle paar tausend DS ein UPDATEBATCH senden. Okay, ich
kann mich auch täuschen. Ich frage nur lieber, bevor ich auf das nächste
Workaround zusteuere!
Post by Peter Götz
Da wäre die wichtigste Empfehlung, dass Du Dir mal die ADO-Dokumentation zu
den verschiedenen CursorTypen und CursorLocations ansiehst.
Ich habe hier das Kofler-Buch "Visual Basic Datenbankprogrammierung". Werde
ich gerne nochmal gezielt nachschauen.
Post by Peter Götz
ADO ändert Werte aus solch unsinnigen Kombinationen ohne was zu sagen, in
Werte die zusammenpassen, sprich technisch möglich sind. Das Ergebnis einer
solchen ADO-Aktivität kann aber sehr häufig ein Recordset mit absolut
unerwünschten Eigenschaften sein.
DAS hatte ich auch schon gemerkt!


@Michael
Zumindest weiß ich nun, dass ich für´s schnelle rüberschieben von ein paar
DS ein INSERT dem AddNew bevorzugen werde!
Vielen Dank an dieser Stelle. Für mein momentanes "Problem" (einem
Import-Modul) werde ich jetzt aber das lokale RS zunächst vorziehen.

Viele Grüße
Andy
Peter Götz
2004-02-22 13:18:14 UTC
Permalink
Hallo Andy,
Post by Andy Dorwald
Post by Peter Götz
lngID = HöchsteBisherigeID
strSQL = "Select * From Tabelle Where ID > lngID"
' SQL-Statement liefert ein Recordset mit 0 Datensätzen
Dazu müsste ich die bisher höchste erstmal wissen. Kann ich nicht einfach
fragen "...ID < 0" ?
Die höchste ID bekommst Du mit

set RS = Cnn.Execute ("Select Max(ID) From Tabelle")
Post by Andy Dorwald
Post by Peter Götz
' Instanz eines Recordsets erzeugen
Set RS = New ADODB.Recordset
' *** Recordset öffnen
With RS
Set .ActiveConnection = Cnn
.Source = strSQL
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockBatchOptimistic
.Open
End With
With RS
For i = 1 to xxx
.AddNew
.Fields(0).Value = lngID + i
for k = 1 to RS.Fields.Count-1
if CheckValue(RSalt.Fields(k).Value, FieldIndex) then
.Fields(k).Value = RSalt.Fields(k).Value
end if
Next k
.Update
Next i
RS.UpdateBatch
End With
Fantastische Idee, dass ganze zunächst in ein lokales RS zu werfen!!! 1000
Punkte! :-)))
Na ja, das ist eine ganz normale Anwendung eines Recordsets im Batchbetrieb.
Post by Andy Dorwald
Übrigens, dass For...Next habe ich nur in diesem Beispiel verwendet. In der
echten Anwendung frage ich nach einem .EOF und gehe mit einem .MoveNext und
LOOP durch. Ich wollte in dem Beispiel lediglich kein weiteres Resultset
reinbringen.
Wie ist das eigentlich mit diesem UPDATEBATCH? Angenommen ich habe nun mein
lokales 35.000 Datensätze RS fertig. Wenn ich dann das UPDATEBATCH anwerfe,
meckert er dann nicht, dass es z.B. zu viele Datensätze auf einmal sind oder
so?
Nein, zuviele werden es so leicht nicht werden.
Es könnten aber z.B. Datensätze enthalten sein, die im Primärschlüsselfeld
einen Wert enthalten, der in der DB-Tabelle bereits enthalten ist, was zu
einer Fehlermeldung führt.
Um herauszubekommen welche Datensätze betroffen sind, kannst Du

RS.Filter = adFilterConflictingRecords

verwenden.
Post by Andy Dorwald
Ganz düster kann ich mich an soetwas vor ein paar Jahren zurückerinnern.
Da musste ich einmal alle paar tausend DS ein UPDATEBATCH senden. Okay, ich
kann mich auch täuschen. Ich frage nur lieber, bevor ich auf das nächste
Workaround zusteuere!
Post by Peter Götz
Da wäre die wichtigste Empfehlung, dass Du Dir mal die ADO-Dokumentation
zu
Post by Peter Götz
den verschiedenen CursorTypen und CursorLocations ansiehst.
Ich habe hier das Kofler-Buch "Visual Basic Datenbankprogrammierung". Werde
ich gerne nochmal gezielt nachschauen.
Kofler hat leider einen mir nicht sonderlich verständlichen Hang zu
DataControls und ähnlichen überflüssigen Gimmiks. Gerade daher rührt es
meiner Meinung nach, dass man immer wieder erlebt, dass vielen ADO-Nutzern
die Unterschiede zwischen den verschiedenen CursorTypen, CursorLocations und
LockTypen nicht klar sind. Die ADO-Dokumentation (ADO2xx.chm bzw. das
Microsoft Data Acces Components (MDAC) SDK
scheinen mir als Informationsqelle zu den ADO-Grundlagen immer noch am
besten geeignet.
Post by Andy Dorwald
Post by Peter Götz
ADO ändert Werte aus solch unsinnigen Kombinationen ohne was zu sagen, in
Werte die zusammenpassen, sprich technisch möglich sind. Das Ergebnis
einer
Post by Peter Götz
solchen ADO-Aktivität kann aber sehr häufig ein Recordset mit absolut
unerwünschten Eigenschaften sein.
DAS hatte ich auch schon gemerkt!
@Michael
Zumindest weiß ich nun, dass ich fürŽs schnelle rüberschieben von ein paar
DS ein INSERT dem AddNew bevorzugen werde!
Vielen Dank an dieser Stelle. Für mein momentanes "Problem" (einem
Import-Modul) werde ich jetzt aber das lokale RS zunächst vorziehen.
Es lohnt sich auf jeden Fall, sich mit den lokalen, evtl. auch gänzlich
verbindungslosen Recordsets, vertraut zu machen. Viele Dinge lassen sich so
viel eleganter, weitaus ressourcenschonender und auch oft sehr viel
schneller als mit Commands oder Recordsets die direkt auf die DB-Tabellen
zugreifen, lösen. Auch mit Blick auf ADO.net ist es sehr hilfreich, sich mit
dem Umgang von verbindungslosen Daten vertraut zu machen.

Gruß aus St.Georgen
Peter Götz
www.gssg.de (mit VB-Tips u. Beispielprogrammen)
Andy Dorwald
2004-02-22 21:16:29 UTC
Permalink
Leider hat sich das Problem etwas/ziemlich "verschärft" *SORRY!*

Siehe bitte Thread: "Wie am besten zwei Tabellen (Master+Detail) füllen!?"
von heute (22:13 Uhr)

Gibt es dazu eine "saubere" Lösung???

Vielen Dank im voraus!

Loading...