QueryBuilder

Verwendung

Da es relativ nervig und fehleranfällig ist jedes mal ein eigenen Query zu schreiben wurde der QueryBuilder in Version 1.10.2.0 eingeführt und im Laufe der Zeit immer weiter verbessert. Der QueryBuilder kann nicht alle möglichen Fälle von Queries ablösen (dafür ist SQL viel zu komplex), jedoch kann man viele üblichen Probleme damit schnell und einfach lösen.

Es gibt momentan zwei verschiedene Arten den QueryBuilder zu verwenden: per Objekt-Konfiguration (als Magic-Fields) und per ManualQueryBuilder.

Was genau macht der QueryBuilder anders als SQL?

SQL ist sehr vielzeitig einsetzbar, jedoch benötigen wir in Konquadrat viele Features meistens nicht. Was wir stattdessen brauchen ist ein schneller und einfacher Zugriff auf Objekte und ihre Werte.

In SQL müsste man erstmal alle Tabellen, die zu dem Objekt gehören, verjoinen (bzw. alle benötigten Tabellen), anschließend müsste man noch auf den Objekt-Typ und meistens auch noch auf das Deleted-Flag von dem Objekt achten. Falls nun noch Referenzen auflösen will müsste man das für jede weitere Ebene machen. In der Vergangenheit hatten sich hierdurch oft Fehler eingeschlichen.

Im QueryBuilder beschreibt man den direkten Zugriff auf die Werte anstatt sich um das ganze "Drumherum" zu kümmern. Man beschreibt z.B. zähle alle Teilnehmerobjekte bei denen der Status auf "Zusage" steht. Die Syntax vom QueryBuilder ist komplett in XML gehalten um eine einfache Autovervollständigung + Dokumentation per XSD zur Verfügung zu haben.

Objekt-Konfiguration

Es ist möglich den QueryBuilder für Magic-Felder zu verwenden. Ein Beispiel:

<magic name="Name">
    <object:column xmlns="http://www.setasign.com/Konquadrat/Grid">
        <queryBuilder>
            <thisObject>
                <selector>
                    <concat>
                        <fields>
                            <field>Firstname</field>
                            <value> </value>
                            <field>Lastname</field>
                        </fields>
                    </concat>
                </selector>
            </thisObject>
        </queryBuilder>
    </object:column>
</magic>

ManualQueryBuilder

Über den ManualQueryBuilder ist es möglich den QueryBuilder auch innerhalb von Code zu verwenden ohne vorher ein extra Feld konfigurieren zu müssen. Beispiel:

PHP
/**
 * @var ManualQueryBuilder $queryBuilder
 */
$id = 13;
$name = $queryBuilder->fetch(<<<XML
<queryBuilder xmlns="http://www.setasign.com/Konquadrat/QueryBuilder" xmlns:grid="http://www.setasign.com/Konquadrat/Grid">
    <thisObject id="$id" type="Veranstaltung">
        <selector>
            <grid:field>Name</grid:field>
        </selector>
    </thisObject>
</queryBuilder>
XML
);

Syntax und Aufbau

Es gibt drei verschiedene Arten von QueryBuildern: thisObject, referencingObject und allObjects (nur im ManualQueryBuilder möglich).

Alle Arten haben einen Selektor. referencingObject und allObjects haben zusätzlich noch einen Filter. Und im ManualQueryBuilder haben alle Varianten auch noch die Möglichkeit SubQueries zu hinterlegen (mehr dazu weiter unten).

Grundsätzlich können alle generierten Queries nur einen Wert zurückgeben. Falls mehr Ergebnisse zurück kommen könnten wird der Query durch ein "LIMIT 1" limitiert.

thisObject

ThisObject startet die Datenabfrage von dem Objekt von dem man gerade startet. Bei einem Objekt mit einem Magic-Feld dass "thisObject" verwendet, greift auf andere Felder von DIESEM Objekt zu.

referencingObject

ReferencingObject fragt nach Objekten die auf das Objekt zeigen von dem man gerade startet. Bei einem Objekt mit einem Magic-Feld dass "referencingObject" verwendet, wird also nach Objekten gefragt die auf DIESES Objekt referenzieren über ein Referenzfeld, wie z.B. "parent".

allObjects

AllObjects fragt alle Objekte eines bestimmten Objekt-Typs ab. Diese Art ist nur im ManualQueryBuilder möglich.

Selektor

Der Selektor definiert was genau angezeigt werden soll. Es gibt unter anderem: field, value, dateFormat, count, countDistinct, min, max, std, sum, avg, concat, groupConcat, greatest, least, if, ifNull, arithmetic

Eine genau Auflistung ist in der XML-Dokumentation zu finden.

Filter

Filter reduzieren die Datenmenge anhand bestimmter Vorgaben (und sind deswegen in "thisObject" nicht möglich und unsinnig).

Besonderheiten

Für "Value"-Einträge gibt es einige zusätzliche Möglichkeiten:

  • Das Wort "null" wird als NULL gewertet. (Vergleiche auf NULL können mit "=" gemacht werden)
  • "NOW()" wird als SQL-Expression gewertet.

Für "Field"-Einträge gibt es folgende zusätzliche Möglichkeiten:

  • Man kann auf alle fast Felder vom Objekt zugreifen inkls. MultiLang-Felder und Magic-Felder.
  • Es ist möglich Referenzfelder aufzulösen, sogar über mehrere Ebenen: z.B. parent.Event.Name
  • Beim ManualQueryBuilder ist es möglich hier einen SubQuery einzubinden mit "!NameVomSubQuery!".
  • Innerhalb eines SubQueries ist es möglich mit "_parentScope_" auf den drüberliegenden Scope zuzugreifen.

Beispiele

<!-- Liste alle Veranstaltungspunkte von der Veranstaltung auf -->
<magic name="Veranstaltungspunkte">
    <object:column xmlns="http://www.setasign.com/Konquadrat/Grid">
        <queryBuilder>
            <referencingObject type="Veranstaltungspunkt">
                <selector>
                    <groupConcat separator=", ">
                        <fields>
                            <field>Name</field>
                        </fields>
                    </groupConcat>
                </selector>
            </referencingObject>
        </queryBuilder>
    </object:column>
</magic>
<!-- Anzahl an Zusagen zu dieser Veranstaltung die auch bezahlt sind auf -->
<magic name="Zusagen">
    <object:column xmlns="http://www.setasign.com/Konquadrat/Grid">
        <queryBuilder>
            <referencingObject type="Veranstaltungsverknuepfung" field="Event">
                <selector>
                	<count/>
                </selector>
                <filters>
                    <filter field="Status" operator="=">
                    	<value>zusage</value>
                    </filter>
                    <filter field="parent.Status" operator="=">
                    	<value>zusage</value>
                    </filter>
                    <filter field="parent.Bezahlt" operator="=">
                    	<value>1</value>
                    </filter>
                </filters>
            </referencingObject>
        </queryBuilder>
    </object:column>
</magic>
PHP
// Anzahl an Personen die zu der Veranstaltung Katzen mitbringen dürften
$katzenErlaubtFlag = VeranstaltungObject::OPTIONS_ALLOWED_PETS['Katzen'];
$countOfPersonsWhichCouldBringCats = $manualQueryBuilder->fetch(<<<XML
<queryBuilder xmlns="http://www.setasign.com/Konquadrat/QueryBuilder"  
              xmlns:grid="http://www.setasign.com/Konquadrat/Grid"
>
    <allObjects type="Person">
        <selector>
            <grid:count/>
        </selector>
        <filters>
            <grid:filter field="!HatEventDassKatzenErlaubt!" operator="=">
                <grid:value>1</grid:value>
            </grid:filter>
        </filters>
        <subQueries>
            <subQuery name="HatEventDassKatzenErlaubt">
                <referencingObject type="Veranstaltungsverknuepfung">
                    <selector>
                        <grid:ifNull>
                            <grid:value><grid:value>1</grid:value></grid:value>
                            <grid:else><grid:value>0</grid:value></grid:else>
                        </grid:ifNull>
                    </selector>
                    <filters>
                        <grid:filter field="Status" operator="=">
                            <grid:value>committed</grid:value>
                        </grid:filter>
                        <grid:filter field="!KatzenErlaubt!" operator="=">
                            <grid:value>1</grid:value>
                        </grid:filter>
                    </filters>
                    <subQueries>
                        <subQuery name="KatzenErlaubt" scope="Veranstaltung">
                            <thisObject>
                                <selector>
                                    <grid:if>
                                        <grid:condition>
                                            <grid:bitmaskFilter field="AllowedPets" 
                                                                value="$katzenErlaubtFlag"
                                            />
                                        </grid:condition>
                                        <grid:then>
                                            <grid:value>1</grid:value>
                                        </grid:then>
                                        <grid:else>
                                            <grid:value>0</grid:value>
                                        </grid:else>
                                    </grid:if>
                                </selector>
                            </thisObject>
                        </subQuery>
                    </subQueries>
                </referencingObject>
            </subQuery>
        </subQueries>
    </allObjects>
</queryBuilder>
XML
);