Juni 24, 2021, 21:03:27

Neuigkeiten:

Wenn ihr euch für eine gute Antwort bedanken möchtet, im entsprechenden Posting einfach den Knopf "sag Danke" drücken!


Datensätze (Abfrageergebnis) in Favoritenliste bzw. Arbeitskorb ablegen

Begonnen von Lord Sidious, Februar 09, 2021, 13:25:25

⏪ vorheriges - nächstes ⏩

Lord Sidious

Hallo Leute,

ich benötige in einer Angelegenheit euer Schwarmwissen, hoffentlich kann mir jmd helfen.

In meiner DB habe ich ein Formular, in dem Datensätze einzeln angezeigt werden. Die Datensätze sind Ergebnisse einer Recherche. In diesem Formular habe ich einen Button angelegt und so programmiert, dass die ID (= Primärschlüssel) des aktuellen Datensatzes in eine separate Tabelle kopiert wird, die als Favoritentabelle bzw. Arbeitskorb fungiert. Diese Tabelle besteht aus zwei Feldern: ID (= Primärschlüssel der Favoritentabelle), ID_Main (= Primärschlüssel der Haupttabelle als Fremdschlüssel). Dabei wird der Datensatz nur kopiert, wenn er nicht schon in den Favoriten liegt.

Dim db As DAO.Database
Dim sSQL As String
Dim lReturn As Long

Set db = CurrentDb
sSQL = "INSERT INTO tblFavoriten(ID_Main)" & _
        " SELECT tblMain.ID FROM tblMain LEFT JOIN tblFavoriten" & _
        " ON tblMain.ID = tblFavoriten.ID_Main WHERE tblMain.ID = " & Me.ID & " AND tblFavoriten.ID_Main IS NULL"

db.Execute sSQL, dbFailOnError
lReturn = db.RecordsEffected

Select Case lReturn
      Case 0
             MsgBox "Der aktuelle Eintrag ist bereits in der Favoritenliste enthalten!"
      Case Else
             MsgBox "Der aktuelle Eintrag wurde der Favoritenliste hinzugefügt!"
End Select

Das funktioniert soweit auch wunderbar. Nun will ich einen zweiten Button anlegen, mit dem alle im Formular geladenen Datensätze auf einen Schwung zu den Favoriten kopiert werden - allerdings nur jene, die noch nicht in der Favoritentabelle liegen. Und genau hier hakt es:

Dim db As DAO.Database
Dim sqlFavoriten As String

Set db = CurrentDb

sqlFavoriten = "INSERT INTO tblFavoriten(ID_Main) " & _
    "SELECT tblMain.ID FROM tblMain LEFT JOIN tblFavoriten ON tblMain.ID = tblFavoriten.ID_Main " & _
    "WHERE tblMain.ID = " & Me.ID & " AND tblFavoriten.ID_Main IS NULL"

DoCmd.RunCommand acCmdRecordsGoToFirst

Do While Not Me.CurrentRecord = Me.Recordset.RecordCount
    db.Execute sqlFavoriten, dbFailOnError
    DoCmd.RunCommand acCmdRecordsGoToNext
Loop

MsgBox "Alle Datensätze wurden auf die Favoritenliste gesetzt!"

Wenn ich aber nun auf den entsprechenden Button klicke, läuft Access zwar alle Datensätze bis zum letzten durch, kopiert aber nur den ersten in die Favoritentabelle - vorausgesetzt er befindet sich nicht schon dort. Gehe ich nun z.B. zum zweiten Datensatz und starte dann den Alles-auf-die-Favoritenliste-Prozess, wird ausschließlich der zweite Datensatz bei den Favoriten abgelegt usw. Wo liegt hier der Fallstrick?

"DoCmd.RunCommand acCmdRecordsGoToFirst" soll dafür sorgen, dass auch wirklich alle über die Recherche ausgeworfenen Datensätze durchgegangen werden, selbst wenn ich zunächst die Ergebnisse durchsehe und dann z.B. mittendrin auf den Button klicke. Wahrscheinlich gibts dafür aber auch eine elegantere Lösung, ebenso dafür, dass die automatische Durchsicht der Datensätze im Hintergrund, also für den User unsichtbar erfolgt ...

ebs17

Zitatalle im Formular geladenen Datensätze auf einen Schwung
Das wäre doch die gleiche Tabelle wie beim einzelnen Datensatz, nur ggf. mit einer anderen Filterung als wie oben bei genau einem (dem aktuellen) Datensatz.

ZitatsSQL = "INSERT INTO tblFavoriten(ID_Main)" & _
        " SELECT tblMain.ID FROM tblMain LEFT JOIN tblFavoriten" & _
        " ON tblMain.ID = tblFavoriten.ID_Main WHERE " & andererFilter & " AND tblFavoriten.ID_Main IS NULL"
Das wäre für mich der einzige Unterschied.

Nebenbei: Der eigene PK in tblFavoriten wäre für mich überflüssig, da Du eh nur eindeutige ID's übernimmst. Das reicht an Eindeutigkeit (Index nicht vergessen).
Mit freundlichem Glück Auf!

Eberhard

Lord Sidious

Zitat von: ebs17 am Februar 09, 2021, 16:51:22Das wäre doch die gleiche Tabelle wie beim einzelnen Datensatz, nur ggf. mit einer anderen Filterung als wie oben bei genau einem (dem aktuellen) Datensatz.

Zitat von: undefinedsSQL = "INSERT INTO tblFavoriten(ID_Main)" & _
        " SELECT tblMain.ID FROM tblMain LEFT JOIN tblFavoriten" & _
        " ON tblMain.ID = tblFavoriten.ID_Main WHERE " & andererFilter & " AND tblFavoriten.ID_Main IS NULL"
Das wäre für mich der einzige Unterschied.

Du meinst, als zusätzliche Filter jene einzubauen, die für die Abfrageergebnisse verantwortlich sind, die nun in einem Aufwasch bei den Favoriten abgelegt werden sollen? Das ist tatsächlich eine absolut naheliegende Möglichkeit ...

Zitat von: ebs17 am Februar 09, 2021, 16:51:22Nebenbei: Der eigene PK in tblFavoriten wäre für mich überflüssig, da Du eh nur eindeutige ID's übernimmst. Das reicht an Eindeutigkeit (Index nicht vergessen).
Stimmt, daran hab ich gar nicht gedacht!

Damit dürfte das Thema durch sein. Allerdings würde mich doch brennend interessieren, warum
DoCmd.RunCommand acCmdRecordsGoToFirst

Do While Not Me.CurrentRecord = Me.Recordset.RecordCount
    db.Execute sqlFavoriten, dbFailOnError
    DoCmd.RunCommand acCmdRecordsGoToNext
Loop
nicht funktioniert ... Es lässt mir einfach keine Ruhe :(

Ach ja, und: Vielen Dank, ebs17!!!

ebs17

DoCmd-Aktionen sind die VBA-Umsetzung von Menüaktionen. Die haben mit einer Objektorientierung (Me, Formular, Recordset) rein gar nichts zu tun.

Wenn Schleife im Formular, dann etwa so:
With Me.Recordset
   .MoveFirst      ' Sicherstellen, dass man oben beginnt
   Do While Not .EOF
      ' Schreiben
      .MoveNext
   Loop
End With
Mit freundlichem Glück Auf!

Eberhard

Lord Sidious

Lieber Eberhard,

danke für den Hinweis zu den DoCmd-Aktionen!

Ich habe dein Code-Beispiel nun angepasst, aber es will dennoch nicht so, wie ich will (es werden zwar alle Datensätze im Formular durchgegangen, aber nun wird überhaupt kein Datensatz auf die Favoritenliste gesetzt):

Dim db As DAO.Database
Dim rs As DAO.Recordset

Set db = CurrentDb
Set rs = db.OpenRecordset("tblFavoriten", dbOpenDynaset)

Me.Recordset.MoveFirst                 ' Sicherstellen, dass man oben beginnt
   Do While Not Me.Recordset.EOF
      If IsNull(rs!ID_Main) Then   ' Wenn Fremdschlüsselfeld in Favoritentabelle NULL, dann kopieren
         rs.Edit
         rs!ID_Main = Me!ID
         rs.Update
      End If
      Me.Recordset.MoveNext
   Loop
rs.Close

Ich habe hier versucht, die SQL-Insert-Abfrage von oben umzusetzen ...

ebs17

Aktionen sollte man einzeln durchdenken wie dann auch ihr Wirken im Zusammenhang.

Edit - Anfügen?

If IsNull(rs!ID_Main) ...
- die ID welches Datensatzes prüfst Du? Prüfen, was Du prüfst.
- reicht diese eine Prüfung stellvertretend für die Tabelle?
- kann ID_Main überhaupt NULL sein? Würde man also folgend überhaupt in den Abfragezweig einsteigen?
Mit freundlichem Glück Auf!

Eberhard

Lord Sidious

Zitat von: ebs17 am Februar 10, 2021, 09:23:08Edit - Anfügen?
Steht .Edit wirklich anfügen?? Mit dem Befehl wird doch der aktuelle Datensatz zur Bearbeitung vorbereitet und die Änderung(en) mit .Update dann gesichert oder liege ich da falsch?

Zitat von: ebs17 am Februar 10, 2021, 09:23:08If IsNull(rs!ID_Main) ...
- die ID welches Datensatzes prüfst Du? Prüfen, was Du prüfst.
- reicht diese eine Prüfung stellvertretend für die Tabelle?
- kann ID_Main überhaupt NULL sein? Würde man also folgend überhaupt in den Abfragezweig einsteigen?
Ich habe nun die If-Bedingung geändert zu "If Not rs!ID_Main = Me!ID Then" geändert, dennoch funktioniert es nicht. Die Fallunterscheidung soll ja prüfen, ob das Feld "ID_Main" des aktuellen Datensatzes der Favoritentabelle inhaltlich mit "ID" des aktuellen Datensatzes des Formulars ident ist ... wenn nein, dann soll die ID des Formular-Datensatzes reingeschrieben werden in "ID_Main" in der Favoritentabelle. Ich glaub, ich steh hier total auf der Leitung ...

ebs17

ZitatDabei wird der Datensatz nur kopiert, wenn er nicht schon in den Favoriten liegt.
Bei Deinen Spielereien ist diese Ausgangsbedingung auch verflossen? Denn wo und was wollte man editieren?


Und: Woraus beziehst Du die Überzeugung, dass ein Datensatz eines beliebigen Formularrecordsets genau zutreffend für einen beliebigen Datensatz der Tabelle wäre?
Mit freundlichem Glück Auf!

Eberhard

Lord Sidious

Lieber Eberhard,

deine letzte Kritik ist angekommen ;)

Zitat von: Lord Sidious am Februar 10, 2021, 11:54:14
Zitat von: ebs17 am Februar 10, 2021, 09:23:08Edit - Anfügen?
Steht .Edit wirklich anfügen?? Mit dem Befehl wird doch der aktuelle Datensatz zur Bearbeitung vorbereitet und die Änderung(en) mit .Update dann gesichert oder liege ich da falsch?
Was .Edit betrifft, hattest du völlig recht, da hab ich mich falsch informiert ... .AddNew ist hier natürlich das richtige Zauberwort.

Ich habe nun versucht, mich schrittweise im Code vorzutasten.

Dim db As DAO.Database
Dim rsf As DAO.Recordset

Set db = CurrentDb
Set rsf = db.OpenRecordset("tblFavoriten", dbOpenDynaset)

Me.Recordset.MoveFirst
Do While Not Me.Recordset.EOF
    rsf.AddNew
    rsf!ID_Main = Me.ID
    rsf.Update
    Me.Recordset.MoveNext
Loop
rsf.Close
Damit funktioniert schon mal die Übernahme aller im Formular geladenen Datensätze. Woran ich scheitere, ist die Überprüfung, ob sich der Inhalt von Me.ID bereits im Feld "ID_Main" der Favoritentabelle befindet (und davon abhängig, ob der aktuelle Datensatz bei den Favoriten abgelegt oder gleich direkt zum nächsten im Formular gesprungen werden soll).

Me.Recordset.MoveFirst
Do While Not Me.Recordset.EOF
    If Not rsf!ID_Main = Me.ID Then
        rsf.AddNew
        rsf!ID_Main = Me.ID
        rsf.Update
    End If
    Me.Recordset.MoveNext
Loop
rsf.Close
Ist die Fallunterscheidung eingebaut, wirft Access den Laufzeitfehler 3021 ("Kein aktueller Datensatz") raus, ohne funktioniert der Kopiervorgang souverän, ignoriert aber logischerweise bereits in der Favoritentabelle abgelegte Datensätze, wodurch es natürlich zu Redundanzen kommen kann. Es hakt augenscheinlich daran, dass bei Einsetzen der Fallunterscheidung (anders als bei ihrem Weglassen) der aktuelle Datensatz irgendwie nicht fokusiert wird ...

ebs17

Erstens: Die oben genannte Abfragelösung (Tabelle aus Formularrecordset gegen Tabelle) ist einfach, zielführend und schnell.

Angenommen, ich mag es umständlicher und also Schleife, gäbe es etwa folgende Ablauflogik:
1) Schleife über das Formularrecordset
2) Jede ID daraus muss mit allen ID's in der Tabelle verglichen werden, alle zumindest so lange, bis es einen Übereinstimmungstreffer gibt.

Die Umsetzung von 2) kann jetzt sehr verschieden sein.
- Intellektuell einfach, aber auch wirklich am Umständlichsten ist eine wirkliche Schleife durch die Favoritentabelle per zweitem Recordset, also Schleife in Schleife. Aber es würde "funktionieren".
- Eine ID-Existenzprüfung könnte man per DLookup oder DCount vornehmen. Domänenaggregatdauerfeuer ist auch nicht so toll.
- Im Zielrecordset könnte man per FindFirst zielgerichteter prüfen und bei NoMatch   das AddNew starten.
- Das gleiche schneller per Seek auf ein Recordset mit dbOpenTable (geht nur auf Realtabelle, nicht bei verknüpfter Tabelle).
- Man könnte auch aus dem einen Wert aus dem Formularrecordset mit Hilfe einer Tabelle, die nur genau einen Datensatz enthält, eine Tabelle basteln, mit der man eine Inkonsistenzprüfung auf die Favoritentabelle innerhalb einer Anfügeabfrage erstellen kann, so wie oben im Eingangscode. Nur würde hier aufwandssenkend nicht tblMain auf einen Datensatz heruntergefiltert, sondern die neue Tabelle enthält von Haus aus nur einen Datensatz und spart somit nachfolgend Vergleichsaufwand.
Wenn es zu komplizert formuliert ist, hier zum Anschauen des ersten Schrittes des Ansatzes:
SELECT 234 AS MyID
FROM TabelleMitEinemDatensatz

Der letzte Punkt ist dann nur eine Ableitung aus "Erstens", und man fragt sich dann, warum soll ich einzeln prüfen, wenn ich bequem gleich alle bekommen und prüfen und eintragen kann.
Mit freundlichem Glück Auf!

Eberhard

Lord Sidious

Zitat von: ebs17 am Februar 10, 2021, 21:45:15- Das gleiche schneller per Seek auf ein Recordset mit dbOpenTable (geht nur auf Realtabelle, nicht bei verknüpfter Tabelle).
Gott, na klar, über Seek und NoMatch! Super, vielen Dank!!! :)

Zitat von: ebs17 am Februar 10, 2021, 21:45:15Erstens: Die oben genannte Abfragelösung (Tabelle aus Formularrecordset gegen Tabelle) ist einfach, zielführend und schnell.
Da gebe ich dir grundsätzlich recht! Das "Problem" in diesem Fall ist nur, dass als Filterkriterien in der WHERE-Klausel die Inhalte von Feldern eines Suchformulars fungieren (was an sich noch kein Problem darstellt) und dabei zusätzlich auf mit der Haupttabelle verknüpfte Nebentabellen zugegriffen würde ... bin dabei herauszufinden, ob ich die Abfrage mit den richtigen Joins hinbekomme. Wenn ja, greife ich auf die Abfrage zurück, denn die wird definitiv schneller gehen.