PDA

Vollständige Version anzeigen : Anzahl ge-update-r datensätze mit ADO


melodoi
28.04.2011, 11:20
Hi,

Wenn man per

db.execute "UPDATE...SET...WHRE...", dbSeeChanges

Daten aktuallisiert, erhält man mir db.RecordsAffected die Anzahl der upgedateten Datensätze. Wie ermittelt man das unter ADO nach:

Public Function GetSQL(ByVal strSQL As String) As ADODB.Recordset

Set GetSQL = New ADODB.Recordset
GetSQL.CursorLocation = adUseClient
GetSQL.Open strSQL, con, adOpenDynamic, adLockOptimistic

End Function

Josef P.
28.04.2011, 11:25
Hallo!

ADODB ist ein Rückgabewert in der Execute-Methode enthalten.

Prinzip:
Dim cnn As ADODB.Connection
dim RecordsAffected as long

cnn.open ...
cnn.Execute SqlUpdateAnweisung, RecordsAffected
msgbox RecordsAffected

Bei der Execute-Methode der ADODB.Command-Klasse ist ebenso ein RecordsAffected-Parameter enthalten.

mfg
Josef

melodoi
28.04.2011, 17:51
Das habe ich hinbekommen. Danke. Ist die Execute-Methode nur für Aktionsabfragen (UPDATE, INSERT INTO) geeignet oder kann die mir auch ein Recordset zurückliefern (SELECT). Was müsste ich tun, wenn sie das machen soll? Oder geht eine SELECT-Abfrage nur mit con.Open... und nicht mit Execute?

Josef P.
28.04.2011, 22:17
Hallo!

Die Execute-Methode von ADODB gibt auch ein Recordset zurück, wenn du eine SELECT-Anweisung einsetzt.

mfg
Josef

melodoi
29.04.2011, 09:50
OK, das funzt. Warum bringt aber rs.RecordCount nach "Set rs = Cmd.Execute(lngCount)" nur eine "-1"? Ganzer Code (Connect-Object con schon fertig):


Dim Cmd As New ADODB.Command
Dim rs As ADODB.Recordset

Cmd.ActiveConnection = con
Cmd.CommandType = adCmdText
Cmd.CommandText = strSQL

Set GetSQL = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.CursorType = adOpenDynamic
rs.LockType = adLockOptimistic
Set rs= Cmd.Execute(lngCount)
Set Cmd = Nothing

Josef P.
29.04.2011, 09:55
Die Hilfe zu ADODB.Recordset.RecordCount hast du schon gelesen?

BTW: Funktioniert der Code aus Beitrag#5?
Wie kann man Eigenschaften des Recordset einstellen, wenn noch gar keine Instanz vorhanden ist?

Noch etwas: Gibst du überhaupt die passende Recordset-Referenz zurück?
Set GetSQL = New ADODB.Recordset ' = 1. Instanz
...
Set rs= Cmd.Execute(lngCount) ' = 2. Instanz



mfg
Josef

melodoi
29.04.2011, 10:17
Ja, habe ich, nach Execute bringt .recound in jedem Fall -1 zurück. Die Instanz passt scho, ich habe den Code hier zur Vereinfachung nur gekürzt in das eine GetSQL nicht nach rs umgeschrieben.

Der Code funktioniert schon. Ich habe hier eine Klasse für den Zugriff auf eine PostGreSQL. Der gesamte Wunsch-Code :-):

Public con As New ADODB.Connection

Public Function dbOpen() As Boolean
'Öffnen der Verbindung

strCon = "DRIVER={PostgreSQL UNICODE};" _
& "SERVER=" & strServer & ";" _
& "PORT=" & strPort & ";" _
& "DATABASE=" & strInstanz & ";" _
& "UID=" & strUser & ";" _
& "PWD=" & strPWD & ";" _
& "ByteaAsLongVarBinary=1;"

con.Open strCon
dbOpen = True

End Function

Public Function GetSQL(ByVal strSQL As String, Optional ByRef lngCount As Long = 0) As ADODB.Recordset
'Ausführen SQL-Abfrage

Dim Cmd As New ADODB.Command

Cmd.ActiveConnection = con
Cmd.CommandType = adCmdText
Cmd.CommandText = strSQL

Set GetSQL = New ADODB.Recordset
GetSQL.CursorLocation = adUseClient
GetSQL.CursorType = adOpenDynamic
GetSQL.LockType = adLockOptimistic

Set GetSQL = Cmd.Execute(lngCount)

Set Cmd = Nothing
End If

End Function

Public Function dbClose() As Boolean
On Error Resume Next
con.Close
Set con = Nothing
End Function

Josef P.
29.04.2011, 10:22
Hallo!

Ja, habe ich
Dann hast du vermutlich das übersehen:
The cursor type of the Recordset object affects whether the number of records can be determined. The RecordCount property will return -1 for a forward-only cursor; the actual count for a static or keyset cursor; and either -1 or the actual count for a dynamic cursor, depending on the data source.

Public Function GetSQL(ByVal strSQL As String, Optional ByRef lngCount As Long = 0) As ADODB.Recordset
'Ausführen SQL-Abfrage

Dim Cmd As New ADODB.Command

Cmd.ActiveConnection = con
Cmd.CommandType = adCmdText
Cmd.CommandText = strSQL

Set GetSQL = New ADODB.Recordset
GetSQL.CursorLocation = adUseClient
GetSQL.CursorType = adOpenDynamic
GetSQL.LockType = adLockOptimistic

Set GetSQL = Cmd.Execute(lngCount)

Set Cmd = Nothing
End If

End Function
Die roten Zeilen sind umsonst.
Sobald du <code>Set GetSQL = Cmd.Execute(lngCount)</code> ausführst, wird die zuvor erzeugte Recordset-Instanz zerstört, weil keine Referenz mehr gespeichert ist.

Wenn du die CursorLocation auf adUseClient einstellen willst, musst du das bei cmd.Execute in der Connection festlegen.


BTW:
Warum willst du eigentlich in einer Prozedur Select sowie Update und Insert ausführen können?
Ich halte 2 Prozeduren - eine für Recordsetrückgabe und eine für Aktionsabfragen übersichtlicher.

Noch ein Tipp:
Verzichte lieber auf so etwas wie <code>Public con As New ADODB.Connection</code> und erzeugte die Instanz explizit per <code>set con = new ADODB.Connection</code>. Dann hast du die Kontrolle über das Entfernen der Instanz. Mit <code> ... As New ...</code> wird jedes Mal eine neue Instanz erzeugt, falls noch keine oder keine mehr vorhanden ist.

mfg
Josef

melodoi
29.04.2011, 11:02
Was heißt eignetlich BTW:? :-)

Ich dachte mir schon sowas und werde es so machen:

Public Function GetSQL(ByVal strSQL As String, Optional ByRef lngCount As Long = 0) As ADODB.Recordset
'Ausführen SQL-Abfrage

If UCase(LTrim(Left(strSQL, 7))) = "SELECT " Then
Set GetSQL = New ADODB.Recordset
GetSQL.CursorLocation = adUseClient
GetSQL.Open strSQL, con, adOpenDynamic, adLockOptimistic
lngCount = GetSQL.RecordCount 'nicht zwingend nötig, nur zur Vollständigkeit
Else
Dim Cmd As New ADODB.Command
Cmd.ActiveConnection = con
Cmd.CommandType = adCmdText
Cmd.CommandText = strSQL
Cmd.Execute lngCount
Set Cmd = Nothing
End If

End Function

So richtig? Auf diese Art muss ich nicht meinen gesamten Code ändern in dem ich GetSQL aufrufe, sondern nur lngCount einfügen, wo ich es brauche.

Die Connection baue ich nur ein mal beim Starten der mde auf (.dbOpen) und beim Beenden wieder ab (.dbClose). Wenn ich sie innerhalb der Klasse benötige muss sie ja oben deklariert sein. Meist du, dass die dann nicht Public sein muss und ein Dim ausreicht also statt "Public con As New ADODB.Connection" nur "Dim con As ADODB.Connection" und in der dbOpen dann "Set con = New ADODB.Connection"? Also funktionieren tut es so.

Josef P.
29.04.2011, 11:10
Also funktionieren tut es so.
Funktionieren tut vieles ... zumindest so lange bis es doch einmal kracht. ;)

Auf diese Art muss ich nicht meinen gesamten Code ändern in dem ich GetSQL aufrufe sondern nur lngCount einfügen, wo ich es brauche.
Auf die If-Verzweigung würde ich verzichten - diese ist außerdem zu wenig. Du könntest nämlich auch eine gespeicherte Prozedur vom PG-Server aufrufen und das erhaltene Recordset auswerten wollen. So eine SQL-Anweisung wird aber nicht mit Select beginnen.

Ich dachte eher an 2 Methoden.
1. Methode: OpenRecordset
2. Methode: Execute
=> Wenn man ein Recordset öffnen will, kommt xxx.OpenRecordset zum Einsatz - zum Ausführen von Update bzw. Insert-Anweisungen darf xxx.Execute arbeiten.

Zu "GetSQL": bei so einem Methodennamen würde ich erwarten, dass ich eine SQL-Anweisung erhalte.


Zu "Code Ändern" ... lieber früher als später, falls man jetzt schon Probleme erwartet. ;)
Das Durchscannen und Umschreiben der Methodenaufrufe wird vermutlich keine 10 MInuten dauern - könnte aber den Vorteil haben, dass man im Code sofort erkennt, was passiert, ohne die SQL-Anweisung lesen zu müssen.

mfg
Josef

melodoi
29.04.2011, 11:54
Danke, ich habs beherzigt :-). Sieh nun so aus:

Public Function dbOpenRecordset(ByVal strSQL As String) As ADODB.Recordset

Set dbOpenRecordset = New ADODB.Recordset
dbOpenRecordset.CursorLocation = adUseClient
dbOpenRecordset.Open strSQL, con, adOpenDynamic, adLockOptimistic

End Function

Public Function dbExecute(ByVal strSQL As String) As Long
'
Dim Cmd As New ADODB.Command

Cmd.ActiveConnection = con
Cmd.CommandType = adCmdText
Cmd.CommandText = strSQL
Cmd.Execute dbExecute
Set Cmd = Nothing

End Function

War das richtig mit dem "Dim con As ADODB.Connection" und "Set con = New ADODB.Connection" nach :

Dim con As ADODB.Connection

Public Function dbOpen() As Boolean
'Öffnen der Verbindung
Set con = New ADODB.Connection
con.Open strCon
dbOpen = True
End Function

Josef P.
29.04.2011, 12:02
War das richtig mit dem "Dim con As ADODB.Connection" und "Set con = New ADODB.Connection"
Mir gefällt es so zumindest besser, da man genau sieht, wann die Instanz erzeugt wird.

Interessehalber: welchen Vorteil bringt das "db"-Präfix vor den Methodennamen?

Wann öffnest du eigentlich die Verbindung bzw. führst dbOpen aus?
Wenn in der Klasse die Verbindung erzeugt wird und die Verbindungsparameter fix eingearbeitet sind, könntest du auch überlegen, ob du statt auf das Feld (con) über eine Eigenschaft auf die Connection zugreifst und in der Eigenschaft prüfst, ob die Instanz schon vorhanden ist.

Prinzip (Luftcode!):
private con As ADODB.Connection

private sub dbOpen()
'Öffnen der Verbindung
Set con = New ADODB.Connection
con.Open strCon
End Function

public property get ActiveConnection() as ADODB.Connection
if con is nothing then
dbOpen
end if
set ActiveConnection = con
end Sub

Public Function dbOpenRecordset(ByVal strSQL As String) As ADODB.Recordset

Dim rst as ADODB.Recordset

Set rst = New ADODB.Recordset
rst.CursorLocation = adUseClient
rst.Open strSQL, ActiveConnection, adOpenDynamic, adLockOptimistic

set dbOpenRecordset = rst

End Function

Zum Überlegen:
Soll beim Zerstören der Klasseninstanz die Connection geschlossen werden?
=>
private sub Class_Terminate()
if not (con is nothing) then
if con.State = ??? then con.close
set con = nothing
end if
end sub
Das kann aber auch unerwünscht sein, wenn die Referenz von der ADODB.Connection noch verwendet wird.

mfg
Josef

melodoi
29.04.2011, 14:29
Ich nutze gerne Prefixe vor meinen Variablen und Funktionen (str..., lng..., bol..., fun...). Das mit dem db war in dieser Klasse schon drin (dbOpen, dbClose) und war für die beiden Funktionen dbOpenRecordset und dbExecute nicht weiter überlegt.

Da Datenbankzugriffe permanent passieren, halte ich sie ab dem Start der mde offen (dbOpen) und schließe sie erst beim Beenden (dbClose). Das passiert für dbOpen in einer Funktion, die mit dem Makro AUTOEXEC gestartet wird. Das Schießen (dbClose) passiert beim Entladen des Hauptformulars. Eine ständiges Offen und Schließen ist zu aufwendig, finde ich. Deine Anregungen halte ich mir aber offen.

Vielen Dank. Hast mir sehr weitergeholfen!!!
LG aus Dresden Sylvio

Scorefun
29.04.2011, 15:03
Bzgl. Deiner Frage zu BTW

http://www.ms-office-forum.net/forum/vbseiten.php?page=4