SQL Daten zwischen zwei bestimmten Monaten ausgeben

mischaef

Kassettenkind
Teammitglied
Registriert
Aug. 2012
Beiträge
6.297
Naben zusammen,

zu später Stunde sitze ich noch an einem Problem, welches ich bisher nicht gelöst bekomme.

Ich möchte aus einer Datenbank Einträge abrufen, bei denen der Start- und Endmonat angegeben werden kann. Mit jeweils einem Monat funktionieren die Ausgaben wunderbar, nur bei den Datumsangaben hakt es jetzt.

SQL:
$mysqli_abfrage = "SELECT
                            SUM(einnahmen_betrag) AS einnahmen_gesamt,
                            einnahmen_datum
                        FROM
                            einnahmen
                        WHERE
                            YEAR(einnahmen_datum)= ? AND MONTH(einnahmen_datum) = ?";


                if($mysqli_ergebnis = $mysqli_verbindung->execute_query($mysqli_abfrage, [$zeitraum[0], $zeitraum[1]]))

Die Abfrage möchte ich jetzt soweit ändern, dass ich sowohl den Zeitraum zwischen mehreren Monaten anhand des Jahres und Monats wie auch die Einträge von nur einem Monat (wenn z.B. 2024-05 bis 2024-05) abrufen kann. Ich weiß dass BETWEEN hier anscheinend die richtige Anweisung ist, aber irgendwie enden alle meine Versuche in einem Fatal-Error...^^

Hätte zufällig jemand eine Idee, wie ich das ganze umsetzen könnte?

Besten dank schon mal...
 
Die ev.t richtige Umsetzung hat sich in einem der Kommentare versteckt. Ich habe es jetzt so gemacht:

SQL:
$mysqli_abfrage = "SELECT
                            SUM(einnahmen_betrag) AS einnahmen_gesamt,
                            einnahmen_datum
                        FROM
                            einnahmen
                        WHERE
                            einnahmen_datum >= '2024-05-01' AND einnahmen_datum < '2024-06-31'";

Das funktioniert probeweise schon mal. Leider braucht es für die Umsetzung immer auch einen Tag...also Jahr und Monat alleine reichen nicht. Ich müsste somit immer ein "künstliches" 01 und 31 anfügen. Keine Ahnung ob das so sauber ist oder ob es noch eine andere Möglichkeit gibt...
 
  • Gefällt mir
Reaktionen: DJMadMax
BETWEEN ist die korrekte Lösung.

Achte darauf, bei Angaben wie ab '2024-04' bis '2024-05' erst mal daraus vollständige Daten (in Form von datetime-Werte) zu machen. Und dann musst du die Monatsangaben entsprechend deuten. In meinem Beispiel wäre '2024-04' eigentlich '2024-04-01' und '2024-05' müsste zu '2024-05-31' werden. Dafür würde ich mir eine FirstDayOfMonth- und eine LastDayOfMonth-Funktion schreiben.
Das Endergebnis wäre dann etwas a la WHERE einnahmen_datum BETWEEN FirstDayOfMonth(Startmonat) AND LastDayOfMonth(Endmonat).


Nachtrag:
FirstDayOfMonth könnte zB so definiert sein:
SQL:
CREATE FUNCTION FirstDayOfMonth(jahrMonat AS DATE)
RETURNS DATE
AS
BEGIN
    RETURN DATE_SUB(jahrMonat, INTERVAL DAYOFMONTH(jahrMonat)-1 DAY)
END
Irgendwie so was.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: DJMadMax und mischaef
MySQL hat anscheinend die Funktion LAST_DAY(), die den letzten Tag des Monats zurückgibt. Also könntest du es so ungefähr machen:
SQL:
SELECT
    SUM(einnahmen_betrag) AS einnahmen_gesamt,
    einnahmen_datum
FROM
    einnahmen
WHERE
    einnahmen_datum BETWEEN ? AND LAST_DAY(?)
 
  • Gefällt mir
Reaktionen: mischaef
@Krik
Das war es!

Der Fehler bei mir war anscheinend dass ich ein komplettes Datum gebracht habe, vorher hatte es alleine mit dem Jahr und Monat funnktioniert.

Besten dank!

@cx01
wenn ich das so löse

SQL:
                $mysqli_abfrage = "SELECT
                            SUM(einnahmen_betrag) AS einnahmen_gesamt,
                            einnahmen_datum
                        FROM
                            einnahmen
                        WHERE
                            einnahmen_datum between ? AND LAST_DAY(?)";


                if($mysqli_ergebnis = $mysqli_verbindung->execute_query($mysqli_abfrage, [$datum_haushaltsbuch_beginn, $datum_haushaltsbuch_ende]))
                {

und $datum_haushaltsbuch_ende "2024-06" ist, dann werden keine Einträge gefunden. Lasse ich aber LAST_DAY weg und nehme "2024-06-31" dann funktioniert es...
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Krik
Du müsstest "2024-06-01" reingeben, und das LAST_DAY macht dann automatisch "2024-06-30" draus.
 
  • Gefällt mir
Reaktionen: mischaef
floq0r hat eine gute Lösung genannt. Denn

Code:
WHERE YEAR(date)=2024 AND MONTH(date) BETWEEN 3 AND 5

gibt dir alle Daten vom 01.03.2024 bis zum 31.05.2024.
Ergänzung ()

Du könntest auch zusätzlich mit

Code:
SELECT ... DATE_FORMAT(date, '%Y, %m') 
...
GROUP BY DATE_FORMAT(date, '%Y, %m')

arbeiten um eine Art Verlauf z.B. deiner Einnahmen oder Ausgaben zu bekommen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: mischaef und DJMadMax
Sind Datumsabfragen in SQL nicht auch in der Lage, größenrelevant abgefragt zu werdem, also größergleich oder kleinergleich? Das wäre dann auch noch eine Variante. Die genaue Syntax traue ich mich aus dem Kopf aber nicht, dafür liegt mein SQL schon zu lange zurück, was ich immer nur rein beruflich genutzt habe.
 
@DJMadMax
Das hatte ich ja oben unter 3 umgesetzt, @Krik meinte jedoch, dass es weniger elegant wäre und BETWEEN hier doch richtiger gewesen wäre.

Ich muss gleich mal die Lösung von @floq0r ausprobieren...aber erst wählen gehen...^^
 
  • Gefällt mir
Reaktionen: DJMadMax
Die Lösung von @floq0r hat den Nachteil, dass diese nicht über den Jahreswechsel hinweg funktioniert.

Deine Lösung aus #3 ist schon ein guter Weg. Da Du aber den letzten Tag eines Monats genommen hast, müsste es <= statt < sein. Wenn du aber immer den ersten eines Monats nimmst (der ist immer gleich, da braucht es keine Funktion für), dann ist Lösung #3 einfach und sicher. Persönlich bin ich ein Freund dieser Lösung, da BETWEEN spätestens bei Zeitstempeln mit Subsekunden echt blöd wird.

Für Monat 05 & 06 wäre es dann datum >= 2024-05-01 AND datum < 2024-07-01.

Bin nur am Handy daher keinen kompletten Code.
 
  • Gefällt mir
Reaktionen: mischaef und floq0r
Na klar geht das mit dem Jahreswechsel. Einfach genau wie bei MONTH() mit BETWEEN bei YEAR() arbeiten und subsekunden sind absolut irrelevant bei dieser Datenabfrage.
 
Das mit den Subsekunden ist für diesen Fall egal.Das ist richtig.
Es geht aber eher ums Prinzip (wie immer beim Programmieren). Du hast jetzt Datumsangaben und das erweitert sich dann. Dann versuchst du es auf Datumsangaben mit Zeit umzustellen. Dann hast du eine Methode für GetLastTimeOfDay, dann kommen irgendwann noch Subsekunden dazu. Ich hoffe man versteht, worauf ich hinaus will. Wenn man jetzt aber #3 als Basis nimmt, kann man das auf fast alles andere erweitern. Der große Vorteil: Du hast für verschiedene Probleme/Bereiche eine sehr ähnliche Lösung und musst nicht umdenken. Das kann man jetzt unter Standardisierung, Pattern oder sonst was platzieren. Es hilft aber zukünftig beim Verstehen vom Code ungemein wenn Lösungen identische Vorgehensweisen haben und sich ähnlich sind.
 
  • Gefällt mir
Reaktionen: mischaef
Also wenn die Jahr-Monats-Daten ausschließlich in der Form YYYY-MM kommen, kann man die doch einfach in YYYY-MM-DD umwandeln. Das ist m. E. nach die beste Lösung. Da gibt es dann auch keine Probleme mit dem Jahreswechsel.

SQL:
WHERE einnahmen_datum
BETWEEN
    CAST(CONCAT(Startmonat, '-01') AS DATE)
AND
    LAST_DAY(CAST(CONCAT(Endmonat, '-01') AS DATE))

Was wird gemacht? Er bekommt eine Monatsangabe in Form von '2024-05', fügt den Tag hinzu ('2024-05-01'), wandelt das in den DATE-Datentyp und hat dann den Startmonat in einer Form, mit dem SQL garantiert was anfangen kann.
Beim Monatsende läuft es genauso, nur dass er dann noch den letzten Tag des Monats ermittelt.

Sollte man das irgendwann auf DATETIME und Sekunden ausweiten, dann würde ich das so bauen:
SQL:
WHERE einnahmen_datum
BETWEEN
    CAST(CONCAT(Startmonat, '-01 00:00:00') AS DATETIME)
AND
    DATE_ADD(
        DATE_ADD(
            CAST(CONCAT(Endmonat, '-01 00:00:00') AS DATETIME),
            INTERVAL 1 MONTH),
        INTERVAL -1 SECOND)

Hier addiert er beim Endmonat einen ganzen Monat drauf und zieht dann 1 Sekunde ab. Damit sollte man immer beim Monatsende 23:59:59 laden. Sollte um diese Zeit immer noch etwas relevantes passieren, dann zieht man stattdessen 1 Mikrosekunde ab (MICROSECOND) ab. Das sollte dann garantiert reichen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: mischaef und floq0r
Die Lösung von @Krik wird wahrscheinlich einwandfrei funktionieren.
Hier das Gegenbeispiel, damit man auch bildlich sieht, was ich damit meine, dass der CODE nahezu identisch aussieht.

SQL:
WHERE
CAST(CONCAT(Startmonat, '-01') AS DATE) <= einnahmen_datum
AND
einnahmen_datum < DATEADD(MONTH, 1, CAST(CONCAT(Endmonat, '-01') AS DATE))

Bei einem datetime mit beliebiger Genauigkeit wäre es dann folgender Code:

SQL:
WHERE
CAST(CONCAT(Startmonat, '-01 00:00:00') AS DATETIME) <= einnahmen_datum
AND
einnahmen_datum < DATEADD(MONTH, 1, CAST(CONCAT(Endmonat, '-01 00:00:00') AS DATETIME))

Wahrscheinlich könnte man sich das 00:00:00 im CAST auch sparen, dafür kenne ich MySQL aber zu wenig.

Noch besser wäre es, wenn man der Abfrage direkt die berechneten Datumwerte mitgibt und man in der Abfrage nicht mehr Tage addieren muss. Das ist aber vom aufrufenden Code abhängig.
 
  • Gefällt mir
Reaktionen: Krik
marcOcram schrieb:
Wahrscheinlich könnte man sich das 00:00:00 im CAST auch sparen,
Wahrscheinlich schon. Ich gebe so was aber immer gerne explizit an. Wenn man in 3 Monaten wieder an das Skript geht, ist man sonst vielleicht gezwungen nochmal nachzuschauen, wie das genau funktioniert.
 
.. als Datenbanker (zu Larrys Gnden) zwei Tipps

1. immer versuchen Spalten Nativ anzusprechen (um eventuelle Indexe auch zu nutzen)
2. Samples, Samples, Sample - wie sieht die tabelle aus welche Daten steh drinnen (hab schon Oft Stringfelder mit Datumsartigen Daten geshen und mann wunderts ich dann warum alles komisch ist)

deshalb
https://sqlfiddle.com/mysql/online-compiler
https://www.db-fiddle.com/

da kann man sich gut hinarbeiten
 
  • Gefällt mir
Reaktionen: wilk84
floq0r schrieb:
@wilk84 Wie schreibst du das dann von 11/2023 und 03/2024?

Das wäre dann

Code:
WHERE EXTRACT(YEAR_MONTH FROM date) BETWEEN 202311 AND 202403

Aber natürlich ist auch <=> ein valide Option, genau wie viele andere, wie hier teils aufgeführt. Alles eine Frage der Anforderungen.

Da sind wir wieder bei dem Problem dass der TE nicht das gesamte Problem/Ziel schildert, sondern nur einen Auszug. Was dann eben weitere Fragen aufwirft.

Und nein es geht nicht ums Prinzip beim programmieren, sondern um das Problem und das Ziel.
 
Zuletzt bearbeitet:
Zurück
Oben