kombinierte Datenabfrage aus mehreren Tabellen bzw aus zwei DatabaseObjectList-Objekten?

  • Hallo und weiter geht's :)

    Mit den bisherigen Tips habe ich den größten Teil des Plugins auf der ACP-Seite fertig - bis auf das letzte Modul.

    In der Tabelle wcf1_room sind alle jemals verfügbaren Räume mit Namen und Größe hinterlegt; die Tabelle wcf1_treffen_room enthält nur Kreuzverweise, welcher Raum (roomID) in welchem Treffen (treffenID) verfügbar ist. Für beide Tabellen existieren die jeweiligen Datenklassen und Listklassen (sowie ListPages und Add-/EditForms).

    Wenn ich für das Anlegen bzw. Bearbeiten einer Raumbuchung für ein bestimmtes Treffen nicht nur die roomID anzeigen will (trivial, geht schon), sondern den Namen, dann brauche ich den aus der anderen Tabelle.

    Klassisch kann ich das mit einem JOIN machen oder mit SELECT * from wcf1_room r, wcf1_treffen_room t where t.treffenID=xx and t.roomID = r.roomID. In der resultierenden Tabelle habe ich nur die tatsächlich im Treffen verfügbaren Räume, aber jeweils mit allen "Stammdaten" des jeweiligen Raums, wie Bezeichnung, Größe usw.

    Ich könnte das über direkten DB-Zugriff (prepared statement->execute) machen. Etwas Bastelei, um das Ergebnis als assoziatives Array im Template für eine <select...> Auswahl zu hinterlegen, ist dann noch machbar.

    Bekomme ich das auch mit zwei Listklassen hin? Oder gibt es die Möglichkeit, eine "kombinierte" ListKlasse zu bauen, die nicht auf einer Tabelle, sondern einer "Ergebnistabelle" der obigen SQL-Abfrage arbeitet?

    Puh.. ich hoffe, das ist nicht zu kompliziert :D

    Danke, falls es jemand bis hier schafft :thumbup:

  • Hi,

    Verwendet ich öfters, seitens Woltlab i.d.R. in den sogenannten "View..."-Klassen realisiert. In deinem Fall also z.B. ViewableRoomList. Diese erbt von RoomList.

    In der neuen Klassen wird der Konstruktor um das entsprechende Select und Join erweitert.

    VG

    VG

    Fr33chen

  • Das ist ja schon fast unheimlich, wie schnell und auf den Punkt hier geholfen wird... Respekt!

    Jetzt habe ich erstmal was zu Lesen und zu Verstehen.... danke! <3

  • Das kommt meist darauf an, wie beliebt man sich hier macht, indem man Tipps annimmt und sich dann damit näher beschäftigt oder nur nach fertigen Codes brüllt. So macht das allen mehr Spaß und mindestens einer lernt was dabei. ;)

  • Hehe :)

    Zum Thema Lernen... ;)

    Ich habe jetzt versuche, die neue Klasse BookingRoomList extends TreffenRoomList zu definieren. Mit entsprechenden sqlSelects und sqlJoins habe ich die Strukturen, die ich erwarte, (hoffentlich korrekt) definiert.

    Ich habe dabei im Wesentlichen die Klasse UserFollowingList als Denkvorlage genutzt, wie vorgeschlagen. (Die View-Klassen probiere ich ggf. noch parallel).

    Wenn ich mich nicht irgendwo verheddert habe, habe ich es noch nicht ganz verstanden.

    Das Anzeigen der Buchungen braucht die Konstruktion noch nicht und geht so. Wenn ich jetzt auf "Buchung hinzufügen" klicke, bekomme ich den Fehler

    Wenn ich versuche, die Fehlermeldung und den referenzierten Code in DatabaseObjectList zu verstehen, dann versucht die DatabaseObjectList-Klasse, den Tabellennamen zu ermitteln (bzw. zurückzugeben) und verweist dafür (im Normalfall logisch) auf den Klassennamen.

    Ich habe in diesem Fall aber gar keine entsprechende "Basisklasse" definiert, da ich die nicht brauche - und der Tabellenname der entsprechenden Basisklasse wäre ja auch falsch, da ich den Tabellennamen der Klasse "TreffenRoom" brauche, der ist ja ganz woanders definiert.

    Was mich irritiert ist, dass bei der entsprechenden Klasse UserFollowingList auch keine Klasse UserFollowing existiert - an der Stelle scheint das (anders?) zu funktionieren.

    Habt ihr einen Tip für mich? (Braucht ihr ggf. Codeschnipsel? Welche?)

  • Also - da sind mal die zwei aktuell "wichtigsten" Dateien. Ich habe versucht, Kommentare einzubringen, um zu erläutern, was ich mit den jeweiligen Codeschnipseln vorhabe.

    Weil ich die Tabellen "zusammenhalten" wollte, ist die Benennung der Tabellen in MySQL (alle wcf1_treffen_<xxx>) und der Klassen (ohne zusätzliches 'treffen') nicht ganz stringent. Vielleicht ändere ich das noch; ich hatte auch überlegt, das Plugin als App zu schreiben, so dass die Tabellen mit treffen1_ anfangen, aber eins nach dem Anderen.

    class Treffen: table wcf1_treffen_event (-> TreffenList)

    class Room: table wcf1_treffen_room (-> RoomList)

    class TreffenRoom: table wcf1_treffen_event_room (-> TreffenRoomList)

    class Booking: table wcf1_treffen_booking (-> BookingList)

    neu: class BookingRoomList: s.u., eigentlich ohne eigene "Datenklasse" und daher ohne eigene Tabelle.

    Die Seite "BookingListPage" (basierend auf Klasse Booking) soll die Buchungen auflisten, dazu die Details der belegten Räume in der kombinierten Abfrage aus wcf1_treffen_room und wcf1_treffen_event_room, siehe SQL-Statements im Konstruktor oben.

    Wenn ich nur die Klasse "BookingRoomList" auf "RoomList" ändere (und damit alle Räume anzeige, statt nur derjenigen, die im jeweiligen Treffen verfügbar sind), funktioniert alles problemlos. Anzeigen, neue Buchung hinzufügen, vorhandene Buchung bearbeiten.

    Sobald ich die Klasse auf BookingRoomList ändere, scheitert es beim Aufruf des Konstruktors (Fehlermeldung siehe mein vorheriger Beitrag).

    Das Template kann ich gern noch liefern, das dürfte aber an der Stelle keine Rolle spielen, weil es zum Zeitpunkt des Fehlers noch gar nicht in Betracht gekommen ist. Wenn ihr zur weiteren Prüfung noch weitere Klassen oder Seiten braucht, kann ich die gern einstellen.

  • Die TreffenRoomList wird nicht gefunden (du hast TreffenRoom eingebunden), eigentlich hätte ich hier ne Exception erwartet.

    Davon abgesehen erfordert eine List immer auch die zugehörige Klasse ohne List, also um das Anlegen einer (fast leeren) BookingRoom kommst du nicht herum (außer du setzt die Basis-Klasse manuell).

    Code
    class BookingRoom extends TreffenRoom {
        ...
    }

    Schau mal, wie weit du damit kommst :)

    VG

    Fr33chen

  • Erstmal weiter :)

    Jetzt muss ich noch den Konstruktor so umbauen, dass ich beim Erzeugen der Instanz die ID als Filterargument übergeben kann. Die Vorlage dazu hole ich mir erstmal aus DatabaseObject.class.php.

    Ihr hört von mir, so oder so :)

    Achja - und, danke! :)

  • So, der gesamte Unterbau und die Anzeige- und Verwaltungsseiten im ACP sind fertig und funktionieren :)

    Jetzt sollen die Liste der (aktiven) Treffen (fertig), die Liste der verfügbaren Räume (in Arbeit) und die Formulare für die Anmeldung und Stornierung (todo) in die Benutzeroberfläche.

    Problem: Ich brauche eine List-Klasse mit einer etwas verzwickten Abfrage, die ich mit $sqlSelects, $sqlJoins und ConditionBuilder nicht hinbekomme.

    Basisklasse ist MeetingRoom extends DatabaseObject, Tabelle ist treffen_meeting_room

    Listklasse ist MeetingRoomList extends DatabaseObjectList

    Seite ist MeetingRoomListPage extends SortablePage

    Die SQL-Abfrage ist

    (Dass die Datenbankmodule vom WSC die partial groups nicht akzeptieren, tut erstmal nichts zur Sache; mit full groups wird es aber sehr unübersichtlich).

    Mit ConditionBuilder kann ich nur Where-Bedingungen einbauen; wenn ich die GROUP BY-Klausel in die $sqlSelects oder $sqlJoins aufnehmen, kommen sie an die falsche Stelle.

    Wie gehe ich das am besten an?

    Alternativ könnte ich die verfügbaren Betten (das Feld mit der SUM()-Formel) auch per Database Access abfragen und manuell (mit Schleife im Template) zuordnen und ausgeben, das ist nur nicht so schön...

  • Ein group by geht m.M.n. etwas an der üblichen DatabaseObjectList vorbei.

    Aber trotzdem keine große Sache.

    Das dürftest du ja bereits durchgeführt haben:

    PHP: MeetingRoomList.class.php
    public function __construct() {
        parent::__construct ();
        if (!empty($this->sqlSelects)) $this->sqlSelects .= ',';
        $this->sqlSelects .= // todo
        $this->sqlJoins .= // todo
        $this->getConditionBuilder()->add( // todo
    }

    Und nun als Denkanstoß:

    PHP: MeetingRoomList.class.php
    public function readObjects() {
        // don't call parent method here (real overwrite)
    
        // do something including GROUP BY
    }

    Wenn sich group by die Anzahl an Treffern verändern sollte, dann müsstest du auch an die Methoden countObjects() und readObjectIDs() ran.

    VG

    VG

    Fr33chen

  • Okay, das wäre eine Möglichkeit.

    Mein Problem war u.a., dass beim "Reinmogeln" der GROUP BY-Klausel z.B. countObjects() an der Verknüpfung von sqlSelects und GROUP BY bzw. kein GROUP BY scheiterte...

    Ich muss mal sehen, ob sich das lohnt, das so zu machen, oder ob es "außenrum" einfacher ist.

    Zum Inhalt: ich will in der Liste aller für ein Treffen verfügbaren Räume sehen, wieviele der Betten schon belegt sind - und es können bei Räumen mit 4 Betten eben vier Buchungen auf denselben Raum erfolgen. Darum das SELECT sum(beds) ...GROUP BY . Genauer berechne ich ja schon im SELECT, wieviele Betten noch frei sind - und wenn keine mehr frei sind, kann der Raum nicht mehr gebucht werden.

    Trotzdem erstmal danke für den Denkanstoß. Den parent-Konstruktor nicht aufzurufen hatte ich schon überlegt, habe es dann aber falsch implementiert (weil ich doch wieder nur das Gleiche gemacht habe...)

  • Darf ich mal fragen weshalb du überhaupt ein group by benötigst ?

    Rein von (meiner) logik her kann doch ein meeting einen raum nur genau einmal buchen, damit wäre es mMn unnötig danach zu gruppieren.

    Deine Datenbank struktur scheint mir wie folgt zu sein:

    treffen_room => alle räume die ihr habt

    treffen_meeting_room => alle räume die zu einem meeting gehören. Hier sollte normalerweise ein unique key über meetingID und roomID liegen da wie ich denke ein Raum nur einmal pro meeting gebucht sein kann.

    treffen_booking => die aktiven buchungen

    EDIT.: OK, nun verstehe ich was du zu erreichen versuchst, ich denke am besten fährst du wenn du sowas hier nutzt:

    https://github.com/WoltLab/WCF/bl…e.class.php#L68

    Und den Join auf die booking table weg lässt, dann sollte alles passen und du kannst das ganze ohne group by ausführen.

    Einmal editiert, zuletzt von Morik (27. Mai 2019 um 22:32)

  • Nur falls es noch nicht klar ist - gebucht werden nicht Räume, sondern Betten. Somit können maximal so viele Buchungen auf einen Raum lauten, wie es Betten gibt.

    In der Datenstruktur

    verfügbare Räume (Name, Anzahl Betten) <-> zugewiesene Räume (Preis) <-> Buchungen (User, Anzahl Betten)

    brauche ich kein GROUP BY. Der JOIN auf Buchungen ist nötig, weil es ja um die Buchungen geht und nicht um die Räume ...

    Ich möchte aber zusätzlich

    Buchungen (SUM(Betten) GROUP BY Räume(ID))

    Damit kann ich dann angeben, wie viele Betten in jedem Raum schon belegt sind, und wieviele Buchungen ich in dem jeweiligen Raum noch zulassen kann. Ich sehe nicht, wie ich da ohne die Summenfunktion rankomme - und die muss ich mit GROUP BY auf die einzelnen Räume beschränken, wenn ich nicht mit WHERE nur den einzelnen Raum betrachte. Das kann ich aber nicht im Objekt (und das ist ja eigentlich auch nicht Sinn des DBO-List-Objektes...)

    Die GroupListPage habe ich mir angeschaut - ich muss aber gestehen, dass ich die Parallele bzw. die Anwendbarkeit noch nicht verstanden habe. Da ich aber die letzten Stunden nur DatabaseObjectList-Klassen und templates um die Ohren hatte, bin ich da vielleicht etwas... durcheinander :D

    Morgen schaue ich nochmal in Ruhe drüber, erstmal danke soweit.

  • $this->objectList->sqlSelects .= "(SELECT SUM(treffen_booking.beds) FROM wcf".WCF_N."_treffen_room WHERE roomID = treffen_meeting_room.roomid) AS bookedBeds";

    Damit selektierst du den gewünschten Wert ohne einen Join auf die entsprechende Tabelle zu benötigen, und das ganze ohne die Fallstricke die ein Group By beinhaltet, insbesondere da das WSC eben full group by erzwingt.

    Es handelt sich dabei um einen Subquery, ist zwar unschön aber an der Stelle mMn das sinnvollste.

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!