dedlfix: MySQL SUM mit GROUP Problem

Beitrag lesen

Tach!

Ich hatte zwar nun die Liste hinbekommen, um den Wert des aktuellen Lagerbestands zu ermitteln, doch nun wollte ich die Bestandsveränderung berücksichtigen, so dass ich zum Beispiel den Wert des Lagers zum 31.12. ausgeben kann.

Du hast ja nun auch noch eine weitere Datenquelle hinzugenommen und mit noch mehr Joins und noch mehr Möglichkeiten, ungewollt Datenmengen verknüpfen zu lassen, wird es nicht übersichtlicher oder einfacher.

Hast du mal probiert, wie sowas mit einem Correlated Subselect funktioniert? Stell dir mal vor, wie du die Aufgabe ohne Datenbank lösen würdest. Du bekommst eine Liste mit den Artikeln und einen Stift. Du gehst ins Lager und ermittelst den Bestand, gegebenenfalls musst du dabei summieren, wenn der Artikel an zwei Stellen liegt. Du gehst dann ins Büro und schaust in die Akten, was die bisherige Menge war. Dabei interessiert dich aber nur eine bestimmt Menge, nicht alles aus allen Jahren. Anschließend musst du noch ein bisschen rechnen und das Ergebnis in die Liste schreiben. Und das machst du für jeden Artikel.

Du hast da also ein paar Teilschritte für jede Zeile zu absolvieren. Nun stell dir vor, dass du diese Teilschritte an jemanden delegierst: "Ermittle im Lager die Anzahl vom Artikel mit der Nummer - moment, lass mich in meine Liste schauen - 4711". Und eine ähnliche Aufgabe für die zweite Zahl. Zusammenrechnen machst du selbst. Das sieht zwar etwas umständlich aus, weil du jedes Mal die Teilschritte formulierst und dein Helfer losrennt, aber du kannst auf diese Weise die Ausführung dieses konkreten Schrittes sehr einfach nachvollziehen, weil du diesen Einzelschritt im Falle eines (Problem)falles für einen einzelnen Artikel ausführen kannst statt für die gesamte Liste.

Alternativ dazu (und das wäre statt dem obigen Subselect-Ansatz der Join-Ansatz) lässt du dir eine Lagerbestandsliste und dazu noch eine Bewegungsliste als große Zahlenberge geben und verknüpfst die Werte mit deiner Artikelliste. Du kannst dann aber nicht so einfach feststellen, ob die Zahlen stimmen. Du müsstest separate Tests mit einzelnen Artikeln formulieren und kannst dir dabei nicht ganz sicher sein, dass diese Aufgabenformulierung auch zum selben Ergebnis führt, wie die Formulierung der Aufgabenstellung zur Ermittlung des Datenberges. Da hat man es doch mit der als konkrete Teilschritte formulierten Aufgabenstellung besser.

Und nun stell dir vor, wie du das mit einer Programmiersprache lösen würdest. Vermutlich auch nicht mit array_merge() oder ähnlichem Konstrukt, sondern mit einer For(each)-Schleife. Pro Artikel ermittelst du nun die Zwischenergebnisse der Teilaufgaben, alles in separaten Programmzeilen, damit der Code übersichtlich und lesbar bleibt. Zum Schluss kommt das Zusammenrechnen des Ergebnisses mit den Variablen, in denen das Zwischenergebnis liegt.

Nun schicken wir aber ungern massenhaft Einzenabfragen an das DBMS, und so kommt es, dass wir versuchen das zu einer Abfrage zusammenzufassen - und die meisten greifen zunächst zu Joins und am Ende zu Joints. Probier mal den Subselect-Ansatz. Formuliere dazu erstmal die Statements für die Teilaufgaben. Dafür sind übrigens Tools wie die MySQL Workbench sehr hilfreich. Damit kann man die Querys direkt formulieren und sofort ausführen, ohne jedes Mal Abfrageinfrastruktur drumherumzubauen, wie man das am Ende in einem Programm braucht.

Also erster Schritt: Query für die Ermittlung des Lagerbestandes für einen bestimmten Artikel. Statt "für einen bestimmten Artikel" nehmen wir für den Test erstmal eine konkrete Artikelnummer. Ergebnis ist eine einzelne Zahl, und so soll das sein. Das kann man nun mit weiteren Artikelnummern probieren und wird die richtigen Ergebnisse bekommen. Ist ja eine ziemlich einfache Abfrage. Die legen wir mal kurz zur Seite und nehmen die nächste Teilabfrage ins Visier. Und so weiter. Danach formulieren wir die Abfrage nach den Artikeln. Auch die ist einfach, SELECT id, name FROM artikel. Nun haben wir einen kleinen Stapel einzeln ausführ- und testbarer Querys und kombinieren nun die Teilaufgabenquerys mit der Artikel-Abfrage. Diese Teilaufgabenquerys fügst du zunächst wie weitere Felder in die SELECT-Klausel ein und ersetzen dabei die konkrete Artikelnummer mit einem Verweis auf das Feld der Hauptquery. Das Zwischenergebnis dieses Schrittes ist eine Liste mit Artikeln sowie den Zahlen der Teilquerys. Du kannst kontrollieren, dass dieses Zusammenfügung dieselben Werte bringt wie die zuvor separat ausgeführten Querys. Der letzte Schritt ist nun, die notwendige Zusammenrechnung dieser Teilwerte umzusetzen.

Und so kommt man Schritt für Schritt, jeden davon einzeln auf korrekte Ausführung prüfbar, zum Ergebnis anstatt einn große Join-Abfrage zu formulieren, die man bei Problemen schlecht auseinandernehmen kann. Und ein Group By kommt im besten Fall auch nicht in der Lösung vor.

Um das mal für den ersten Schritt anhand deiner Datenstruktur aufzuzeigen:

Teilaufgabe Lagerbestand für einen Artikel ermitteln:

SELECT Menge FROM artikel_lager WHERE atrnr = 1;

Falls mehrere Datensätze pro Artikel vorhanden sind, hier bereits die Aggregatfunktion anwenden

SELECT SUM(Menge) FROM artikel_lager WHERE atrnr = 1;

Im weiteren Verlauf gehe jedoch ich von nur einem Datensatz pro Artikel aus.

Hauptquery:

SELECT atrnr, ek FROM artikel;

Teilaufgabe in Hauptquery einfügen:

SELECT atrnr, ek, (SELECT Menge FROM artikel_lager WHERE atrnr = artikel.atrnr) FROM artikel;

Ausrechnen des Wertes:

SELECT atrnr, ek, (SELECT Menge FROM artikel_lager WHERE atrnr = artikel.atrnr) * ek AS wert FROM artikel;

Fertig.

Um mal die Anwendung einer zweiten Teilquery zu zeigen, ohne die konkreten Begebenheiten bei dir zu berücksichtigen. Von der aktuellen Lagermenge wird hier der Bestand vom 31.12. des Vorjahres abgezogen und von dieser Differenz der Einkaufswert ermittelt. Die gesamte Herleitung spare ich mir, aber das ist der letzte Zwischenschritt:

SELECT 
  atrnr, 
  ek, 
  (SELECT Menge FROM artikel_lager WHERE atrnr = artikel.atrnr) AS lagerbestand,
  (SELECT Menge FROM artikel_history WHERE date = '2016-12-31' AND atrnr = artikel.atrnr) AS historische_menge
FROM artikel;

und dies die fertige Query:

SELECT
  atrnr, 
  ek, 
  ( 
     (SELECT Menge FROM artikel_lager WHERE atrnr = artikel.atrnr) - 
     (SELECT Menge FROM artikel_history WHERE date = '2016-12-31' AND atrnr = artikel.atrnr) 
  ) * ek AS wert
FROM artikel;

dedlfix.