Access SQL-Abfrage mit Rechenoperation (Rückgabe von errechnetem Wert)

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von petaod.

    Access SQL-Abfrage mit Rechenoperation (Rückgabe von errechnetem Wert)

    Hallo Leute,

    ich stehe vor der Aufgabe einen Wert in einer Access-Tabelle hochzuzählen (und in der Tabelle auch zu aktualisieren) und gleichzeitig den hochgezählten Wert auszulesen.

    Den Wert hochzählen kann ich ja schon mal:


    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Übergibt die nächste Rechnungsnummer und setzt diese als letzte Rechnungsnummer in der Datenbank
    3. ''' </summary>
    4. ''' <returns></returns>
    5. ''' <remarks></remarks>
    6. Public Function GetNextRechNr() As Long
    7. Dim conn As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & FlexX_Datenverzeichnis + "Umsaetze.mdb" & "; Jet OLEDB:Database Password=" & DatenbankPasswort & ";")
    8. Dim cmd As New OleDbCommand
    9. Dim sql As String = ""
    10. cmd.Connection = conn
    11. conn.Open()
    12. sql = "UPDATE Rechnungsnummer SET Rechnungsnummer = Rechnungsnummer +1"
    13. 'Der neue Wert von 'Rechnungsnummer" soll auch gleich abgefragt werden
    14. 'wie wird das gehandhabt?
    15. cmd = New OleDbCommand(sql, conn) 'Return CLng(cmd.ExecuteScalar())
    16. cmd.ExecuteNonQuery()
    17. conn.Close()
    18. Return 111
    19. End Function


    Wie bekomme ich aber den Wert in eine Variable übergeben und gleichzeitig hochgezählt. Geht das überhaupt oder muss ich dazu zwei SQL Abfragen hintereinander machen?

    Ich vermute es gibt dazu eine kombinierte SQL-Abfrage, bin aber bis jetzt nicht drauf gekommen wie die lauten sollte.

    LG Roland
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at
    Ich würde über das Datenmodell nochmals nachdenken.

    Du hast eine eigene Tabelle mit einer Zeile und einer Spalte, die du für das Verwalten einer Rechnungsnummer verwendest.
    Mach das lieber über die Autoincrement-Eigenschaft in der Rechnungstabelle.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    Das geht leider nicht. In der Datenbank befinden sich 3 Tabellen:

    1. Artikelumsätze
    2. Rechnungsabschlüsse mit Detailinformationen
    3. Tabelle mit der zuletzt verwendeten Rechnungsnummer und ein paar anderen Werten die auf jedem Client verfügbar sein müssen

    Das Problem:
    Zwischen dem Ermitteln der Rechnungsnummer und Schreiben der Datensätze in die anderen Spalten kann schon mal eine oder mehrere Sekunden vergehen. Meine erste Idee wäre auch gewesen aus der Tabelle mit den Rechnungsabschlüssen einfach die nächste freie Rechnungsnummer zu ermitteln. Wenn das aber 10 Kassen zur selben Zeit machen, dann kann schon mal eine Rechnungsnummer doppelt vergeben werden.

    Deshalb eine eigene Tabelle mit einem Wert, der sofort bei der Abfrage erhöht wird. Dann ist es egal wie lange die einzelne Kasse braucht um die restlichen Daten in der Tabelle zu verbuchen.

    Also brauche ich nur eine Funktion die den Wert um eins erhöht, speichert und den neuen Wert zurückgibt.

    Dieser Code hier würde zwar funktionieren, aber ich glaube ich habe da eine SQL-Abfrage unnötig zu viel (soll ja nicht all zu viel Rechenzeit und nicht unötig viele Datenbankzugriffe erfordern):

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Übergibt die nächste Rechnungsnummer und setzt diese als letzte Rechnungsnummer in der Datenbank
    3. ''' </summary>
    4. ''' <returns></returns>
    5. ''' <remarks></remarks>
    6. Public Function GetNextRechNr() As Long
    7. Dim RecNu As Long = 0
    8. Dim conn As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & FlexX_Datenverzeichnis + "Umsaetze.mdb" & "; Jet OLEDB:Database Password=" & DatenbankPasswort & ";")
    9. Dim cmd As New OleDbCommand
    10. Dim sql As String = ""
    11. cmd.Connection = conn
    12. conn.Open()
    13. sql = "UPDATE Rechnungsnummer SET Rechnungsnummer = Rechnungsnummer +1"
    14. cmd = New OleDbCommand(sql, conn)
    15. cmd.ExecuteNonQuery()
    16. sql = "SELECT Rechnungsnummer FROM Rechnungsnummer"
    17. cmd = New OleDbCommand(sql, conn)
    18. RecNu = CLng(cmd.ExecuteScalar())
    19. conn.Close()
    20. Return RecNu
    21. End Function


    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „dive26“ ()

    ich hab nicht verstanden, wozu das gut sein soll, aber diese Frage kann ich dir beantworten:

    VB.NET-Quellcode

    1. 'Der neue Wert von 'Rechnungsnummer" soll auch gleich abgefragt werden
    2. 'wie wird das gehandhabt?
    mit einem Select-Command - wie sonst?

    aber ich empfehle auch, erstmal DB-Grundlagen zu lernen.
    (Scheint mir jdfs. ein grober Fehler, dass dein Update-Command keine Where-Einschränkung hat. Da werden dann wohl alle Rechnungsnummern hochgezählt. Vlt. irre ich mich - wie gesagt: ich peile nicht, wozu das überhaupt gut sein soll)
    @ErfinderDesRades
    Es gibt in der Tabelle "Rechnungsnummern" nur eine Zeile mit mehreren Spalten. Also nur einen Datenbankeintrag.
    Daher kein WHERE, da sowieso immer nur eine Zeile da drin steht.


    derzeit steht zwar nur noch AnzahlPersonen dabei, es wird aber auch noch ein paar andere globale Werte geben
    Ich könnte die Rechnungsnummer und die anderen Werte zwar auch in eine simple Datei speichern, jedoch hätte ich die Umsatzdaten gerne in einer einzigen Datenbank zusammengefasst. Hat fiskalische Gründe (Unveränderbarkeit der Daten) - mit Datenbankpasswort (ja ja, kann man alles knacken).

    Dass ich eine SELECT-Abfrage brauche ist mir schon klar (habe ich ja im zweiten Codebeispiel gepostet und das funktioniert soweit auch. Aber wie ich eine UPDATE und eine SELECT Anweisung in einen einzigen Befehl verkapseln kann weis ich nicht. Oder macht das dann schon keinen eklatanten Geschwindigkeitsunterschied mehr aus ob eine Abfrage oder zwei hintereinander?
    Bilder
    • 30062014120457.jpg

      343,77 kB, 1.036×684, 149 mal angesehen
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

    dive26 schrieb:

    Dieser Code hier würde zwar funktionieren, aber ich glaube ich habe da eine SQL-Abfrage unnötig zu viel
    Der funktioniert nur dann, wenn sich zwischen die beiden SQL-Befehlen nicht ein anderer Client mit demselben Anliegen drängt.
    Plötzlich verarbeitet dann der eine Client den Satz vom anderen.

    dive26 schrieb:

    soll ja nicht all zu viel Rechenzeit und nicht unötig viele Datenbankzugriffe erfordern
    Ich würde mir viel mehr Sorgen um die Datenintegrität machen, als um ein paar verlorene Nanosekunden.

    Schau dir mal diesen Beitrag zum Thema @@Identity an:
    support.microsoft.com/kb/815629
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    @ErfinderDesRades
    Prinzipiell schon, aber der interne Ablauf sieht so aus, dass zwischen Ermitteln der Rechnungsnummer, anschließendem Druck und dem speichern der Detailinfos etwas Zeit vergehen kann. Also ich brauche die Rechnungsnummer um diese am Formular andrucken zu lassen. Zusätzlich wird auch noch (fürs Finanzamt) eine zweite Protokolldatenbank beschrieben, dort brauche ich die ebenfalls.

    Vom Ablauf her lässt sich hier leider nicht auf etwas anderes ausweichen.

    @Pedaod
    Ja, Datenintegrität ist mir auch das wichtigste. Auf die Nanosekunde kommt es auch nicht an (ein Test ergab unter einer ms beim Zugriff durch beide Abfragen).
    Danke für den Link. Muss ich mir mal in Ruhe anschauen, auf den ersten Blick blick ich mich da noch nicht durch.
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „dive26“ ()

    also ich würde ganz normal eine Rechnung erstellen, und die auch schon unbearbeitet in die DB inserten. Beim Insert wird ja eh der DB-generierte neue Autowert-Primkey zurückgemeldet.
    Ja, und alles paletti - dann kannste die Rechnung fertig machen und drucken und bla, und natürlich ein zweites mal speichern in der endgültigen Version.
    @ErfinderDesRades
    Das wäre sicher eine Möglichkeit, jedoch für mich leider in der Organisation extrem hinderlich (hängen zig Sachen hinten dran und alle Einträge müssten nochmals aktualisiert werden, das benötigt dann wiederum mehr Code und mehr Datenbankzeit). Auch wenn ich die Rechnungsnummer so wie von Dir vorgeschlagen handhaben würde, hätte ich immer noch ein paar Andere Werte wo ich vor dem selben Problem stünde. Aber danke trotzdem für die Idee.

    Dann bleibe ich am Besten bei den zwei Abfragen und helfe mir mit einem Hilfswert um sicherzustellen, dass zwischen den beiden Abfragen kein anderer Client mit dem selben Anliegen zugegriffen hat. Dies ist wohl eher unwahrscheinlich bei einer Zugriffszeit unter einer ms, aber dennoch möglich. Folgender Code berücksichtigt auch diesen Umstand:

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Übergibt die nächste Rechnungsnummer und setzt diese als letzte Rechnungsnummer in der Datenbank
    3. ''' </summary>
    4. ''' <returns></returns>
    5. ''' <remarks></remarks>
    6. Public Function GetNextRechNr() As Long
    7. Dim RecNu As Long = 0
    8. Dim conn As OleDbConnection = New OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & FlexX_Datenverzeichnis + "Umsaetze.mdb" & "; Jet OLEDB:Database Password=" & DatenbankPasswort & ";")
    9. Dim cmd As New OleDbCommand
    10. Dim sql As String = ""
    11. Dim Kennung As String = My.Settings.Stationskennung + "_" + Now().ToString("ss.fff")
    12. cmd.Connection = conn
    13. conn.Open()
    14. sql = "UPDATE Rechnungsnummer SET Rechnungsnummer = Rechnungsnummer +1 , LetzteStationImZugriff='" + Kennung + "'"
    15. cmd = New OleDbCommand(sql, conn)
    16. cmd.ExecuteNonQuery()
    17. sql = "SELECT Rechnungsnummer FROM Rechnungsnummer WHERE LetzteStationImZugriff='" + Kennung + "'"
    18. cmd = New OleDbCommand(sql, conn)
    19. RecNu = CLng(cmd.ExecuteScalar())
    20. 'Wenn keine Rechnungsnummer (0) übergeben wird, dann hat inzwischen eine andere Station eine Rechnungsnummer angefordert.
    21. 'Da das Abfragefenster extrem klein ist, kann nicht mehr als eine Abfrage dazwischen gemacht worden sein und sogar diese ist eher unwahrscheinlich
    22. 'Daher, wenn dieser Fall dennoch eintritt einfach eine erneute Abfrage ohne Kennung durchführen und das Ergebnis mit 1 subtrahieren
    23. If RecNu = 0 Then
    24. sql = "SELECT Rechnungsnummer FROM Rechnungsnummer"
    25. cmd = New OleDbCommand(sql, conn)
    26. RecNu = CLng(cmd.ExecuteScalar()) - 1
    27. ErrorLog("Doppelter Datenbankzugriff auf Rechnungsnummer " + RecNu.ToString + " wurde zugewiesen")
    28. End If
    29. conn.Close()
    30. Return RecNu
    31. End Function


    Sollte der unwahrscheinliche Fall eintreten, dann sehe ich das im Logfile. Kann das ja die nächsten Monaten bei meinem BETA-Test und beim Entwickeln beobachten ob der Fall überhaupt eintritt.
    In meiner alten Software (VB6) hatte ich das auch so gehandhabt, jedoch ohne diesen Sicherheitsgriff. Bis jetzt hat es auch im Netzwkerkbetrieb mit mehreren Clients ohne eine fehlende Rechnungsnummer geklappt (und das seit 2005).

    Danke für Eure Antworten.
    Zumindest weis ich jetzt, dass es keine Kombi-SQL Anweisung gibt die Rechnen und Rückgeben kann.

    LG Roland
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

    dive26 schrieb:

    Kann das ja die nächsten Monaten bei meinem BETA-Test und beim Entwickeln beobachten ob der Fall überhaupt eintritt.
    Das wird im Beta-Test und beim Entwickeln nicht auftauchen, sondern nur im produktiven Betrieb.
    Und da auch nur ganz selten.
    Aber wenn, ist die Kacke am Dampfen.

    dive26 schrieb:

    Zumindest weis ich jetzt, dass es keine Kombi-SQL Anweisung gibt die Rechnen und Rückgeben kann.
    Es gibt so eine Art Kombi-Anweisung (nachgeschaltet).
    ​SELECT @@IDENTITY gibt dir die letzte ID aus deiner DB-Session zurück.
    Aber das kannst du nur verwenden, wenn du tatsächlich mit Autoincrement beim Insert arbeitest, also wirklich einen echten Primary Key verwendest.
    Dein Ansatz mit der ausgelagerten Tabelle, in der du das selbst verwaltest, muss über kurz oder lang schief gehen.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --
    Kann man denn eine Primary Autoincrement ID von z.B 1,2,3 auf 20140001,20140002 etc. setzen ohne Datensätze dazwischen? So weit ich weis nicht (kann mich aber auch irren).
    Der User soll aber die Möglichkeit haben die Rechnungsnummer individuell zu ändern.
    Die Finanz schreibt nur vor, dass diese Fortlaufend ist. Aber es spricht nichts dagegen, dass jedes Monat oder jedes Jahr der Nummernkreis geändert wird (also z.B. von 2014xxxxx auf 2015xxxxx).
    Von Daher ist der Ansatz mit der letzten ID nicht praktikabel.

    Danke Euch allen für die hilfreichen Antworten.
    LG Roland
    Liebe Grüße
    Roland Berghöfer

    Meine aktuellen und kostenlos verwendbaren Tools (mit VB.NET erstellt): freeremarkabletools.com | priconman.com | SimpleCalendar | AudibleTouch | BOComponent.com | bonit.at

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „dive26“ ()

    ErfinderDesRades schrieb:

    Beim Insert wird ja eh der DB-generierte neue Autowert-Primkey zurückgemeldet.
    Wie du sehr häufig beobachten kannst, wird das Fixum des Nummernkreises einfach vorangestellt, so dass die angezeigte Rechnungsnummer aus Jahr + DatabaseID besteht.
    Bei solchen Verfahren ist die erste Rechnungsnummer des Jahres nicht 2014000001 sondern z.B. 2014056842.

    Ansonsten:
    Ja, du kannst den Grundwert des Autoincrement erhöhen.
    - leere Kopie der Tabelle erzeugen (ohne Daten)
    - Feld-Typ von Autowert auf Zahl ändern
    - Einen Datensatz erstellen mit dem Wert deines neuen Nummernkreises (z.B. 2014000000)
    - eine Anfügeabfrage erstellen, in der du die neue Tabelle (mit dem einzelnen Datensatz) der alten Tabelle hinzufügst.
    Dein nächster erzeugter Eintrag erhält jetzt die Nummer 2014000001
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --