Erweiterte Abfragen

NQL Criteria API

Die Criteria API und NQL sind deine Werkzeuge, um Datenbank-Abfragen auf Basis von Entities und ihrer Eigenschaften zu formulieren. Sie bieten dir dabei dieselben Möglichkeiten wie normales SQL, weshalb du im Normalfall auf native SQL-Abfragen verzichten kannst.

  1. Criteria API und CriteriaItem
  2. Joins
  3. Sub-Queries
  4. Placeholders
  5. IS NULL = null

Criteria API und CriteriaItem

Mit der Criteria API werden Vergleiche (z. B. im WHERE- HAVING- oder ON-Abschnitt) über den n2n\persistence\orm\criteria\compare\CriteriaComparator beschrieben. Eine Instanz dieses Typs erhältst du zum Beispiel als Rückgabewert von Criteria::where() oder Criteria::join().

Vergleichs-Methoden wie CriteriaComparator::match() erwarten als ersten Parameter den Namen der Eigenschaft und als zweiten Parameter den Vergleichsoperator. Nutzt du den CriteriaComparator, um einen WHERE- und HAVING-Abschnitt zu beschreiben, so erwartet CriteriaComparator::match() als letzten Parameter den Wert, mit dem die Eigenschaft verglichen werden soll. Beschreibst du hingegen einen ON-Abschnitt (Joins), so erwartet er als letzten Parameter den Namen der Eigenschaft, die mit der ersten Eigenschaft verglichen werden soll.

        $criteria = $this->em->createCriteria();
        $criteria->select('a')
                ->from(Article::getClass(), 'a')
                ->where()->match('a.title', '=', 'Holeradio');

Um in einem WHERE- oder HAVING-Abschnitt zwei Eigenschaften miteinander zu vergleichen, musst du die Parameter-Typen genauer spezifizieren. Dies erreichst du, indem du CriteriaComparator::match() Objekte vom Typ n2n\persistence\orm\criteria\item\CriteriaItem übergibst. Diese kannst du über n2n\persistence\orm\criteria\item\CrIt generieren.

Es existieren folgende CriteriaItem-Typen:

Typ CrIt Beispiel
n2n\persistence\orm\criteria\item\CriteriaProperty CrIt::p() CrIt::p('a.text')
n2n\persistence\orm\criteria\item\CriteriaFunction CrIt::f() CrIt::f('COUNT', 'a.id')
n2n\persistence\orm\criteria\item\CriteriaConstant CrIt::c() CrIt::c('holeradio')

Möchten wir im WHERE-Abschnitt die Eigenschaften Article::$title und Article::$text über CriteriaComparator::match() miteinander vergleichen, so müssen wir mindestens für den dritten Parameter ein CriteriaItem generieren.

        $criteria->select('a')
                ->from(Article::getClass(), 'a')
                ->where()->match('a.title', '=', CrIt::p('a.text'));

Du kannst CriteriaComparator::match(), anstelle eines Eigenschaftsnamens, auch einen Ausdruck einer Funktion übergeben. Das Selbe gilt für Criteria::select().

        $criteria->select('COUNT(a)')->from(Article::getClass(), 'a');

Um ein Criteria einheitlich zu beschreiben, kannst du natürlich einfach für alle Parameter CriteriaItem-Objekte verwenden. Übrigens ermöglicht es dir CriteriaComparator::endClause() den WHERE-Abschnitt zu beenden und mit der Beschreibung der Criteria fortzufahren.

        $criteria->select(CrIt::f('COUNT', 'a'))
                ->from(Article::getClass(), 'a')
                ->where()->match(CrIt::p('a.title'), '=', CrIt::c('holeradio'))->endClause()
                ->order(CrIt::f('RAND'));

Dieses Criteria lässt sich über NQL folgendermassen beschreiben:

        $criteria = $this->em->createNqlCriteria(
                'SELECT COUNT(a) FROM Article a WHERE a.title = :title ORDER BY RAND()',
                array('title' => 'holeradio'));

Joins

Criteria::join() ermöglicht es dir Entities miteinander zu verknüpfen. Als ersten Parameter erwartet diese Methode die zu verknüpfende Entity und als zweiten deren Alias. Über den dritten Parameter kannst du optional den entsprechenden Join-Typ (INNER, LEFT, RIGHT) definieren.  Criteria::join() gibt dir dabei ein CriteriaComparator-Objekt zurück, über welches du den zugehörigen ON-Abschnitt beschreiben kannst. Mit CriteriaComparator::endClause() kannst du den ON-Abschnitt beenden und mit der Beschreibung der Criteria fortfahren.

        $criteria = $em->createCriteria();
        $criteria->select('ba')
              ->from(BlogArticle::getClass(), 'ba')
              ->join(Category::getClass(), 'cat')->match('ba.title', '=', 'cat.name')->endClause()
              ->order('ba.title', 'ASC');

Über NQL lässt sich dieselbe Abfrage folgendermassen beschreiben:

        $criteria = $this->em->createNqlCriteria(
              'SELECT ba FROM BlogArticle ba JOIN Category cat ON ba.title = cat.name ORDER BY ba.title ASC');

Sub-Queries

Criteria::fromCriteria() und Criteria::joinCriteria() ermöglichen es dir Sub-Queries zu realisieren. Sie erwarten beide als ersten Parameter ein  Criteria-Objekt, welches das Sub-Query beschreibt. Dieses kannst du über Criteria::subCriteria() generieren. Ansonsten unterscheiden sich  Criteria::fromCriteria() und Criteria::joinCriteria() nicht von Criteria::from() und Criteria::join().

        $criteria = $this->em->createCriteria();
        $criteria->select('ba')->select('lc.numComments')
                ->from(BlogArticle::getClass(), 'ba')
                ->joinCriteria($criteria->subCriteria()
                        ->select('COUNT(c)', 'numComments')->select('c.blogArticle', 'blogArticle')
                        ->from(Comment::getClass(), 'c')
                        ->group('c.blogArticle'), 'lc', 'LEFT')->match('lc.blogArticle', '=', 'ba')->endClause()
                ->order('ba.id', 'DESC');

Die oben stehende Abfrage liefert alle Artikel und deren Anzahl Kommentare zurück.

Sub-Queries mit der Criteria API zu beschreiben, macht die Abfrage schnell unübersichtlich. Deshalb ist es zu empfehlen, in solchen Situationen NQL zu verwenden.

        $criteria = $this->em->createNqlCriteria('
                SELECT ba, lc.numComments
                FROM BlogArticle ba
                LEFT JOIN (
                    SELECT COUNT(c.id) AS numComments, c.blogArticle AS blogArticle
                    FROM Comment c
                    GROUP BY c.blogArticle) lc ON lc.blogArticle = ba
                ORDER BY ba.id DESC');

Sub-Queries kannst du, wie aus SQL gewohnt, auch im WHERE-, HAVING- oder ON-Abschnitt nutzen.

        $deniedTitle = 'Test';
        
        $criteria = $em->createCriteria();
        $criteria->select('c')
                ->from(Comment::getClass(), 'c')
                ->where()->match('c.blogArticle', 'NOT IN', $criteria->subCriteria()
                        ->select('ba')
                        ->from(BlogArticle::getClass(), 'ba')
                        ->where()->match('ba.title', '=', $deniedTitle)->endWhere());
        $deniedTitle = 'Test';
        
        $criteria = $em->createNqlCriteria(
                'SELECT c FROM Comment c WHERE c.blogArticle NOT IN (
                        SELECT ba FROM BlogArticle ba WHERE ba.title = :deniedTitle)',
                array('deniedTitle' => $deniedTitle));

Möchtest du im WHERE-, HAVING- oder ON-Abschnitt auf EXISTS- und NOT EXISTS-Bedinungen zurückgreifen, so kannst du diese über die Methode CriteriaComparator::test() beschreiben.

        $deniedTitle = 'Test';
        
        $criteria = $this->em->createCriteria();
        $criteria->select('c')
                ->from(Comment::getClass(), 'c')
                ->where()->test('NOT EXISTS', $criteria->subCriteria()
                        ->select('ba')
                        ->from(BlogArticle::getClass(), 'ba')
                        ->where()->match('ba.title', '=', $deniedTitle)->andMatch('c.blogArticle', '=', CrIt::p('ba'))
                        ->endClause());

EXISTS- und NOT EXISTS-Bedinungen lassen sich natürlich auch in NQL-Statements integrieren.

        $deniedTitle = 'Test';
        
        $criteria = $em->createNqlCriteria(
                'SELECT c FROM Comment c WHERE NOT EXISTS (
                        SELECT ba FROM BlogArticle ba WHERE ba.title = :deniedTitle AND c.blogArticle = ba)',
                array('deniedTitle' => $deniedTitle));
        
        return $criteria->toQuery()->fetchArray();

Placeholders

 Platzhalter kommen mit n2n Version 7.1.5 und sind noch nicht dokumentiert.
        $criteria = $this->em->createSimpleCriteria(Article::getClass(),
                array('type' => new CriteriaPlaceholder('type')));
        
        $query->setParameter('type', 'news');
        $newsArticles = $query->fetchArray();
        
        $query->setParameter('type', 'event');
        $eventArticles = $query->fetchArray();
        $criteria = $this->em->createNqlCriteria('SELECT a FROM Article a WHERE a.type = :type');
        $query = $criteria->toQuery();
        
        $query->setParameter('type', 'news');
        $newsArticles = $query->fetchArray();
        
        $query->setParameter('type', 'event');
        $eventArticles = $query->fetchArray();

IS NULL = null

Solange du auf Placeholders verzichtest, werden = "null"-Bedinungen automatisch in IS NULL umgewandelt.

Criteria API

        $critera = $this->em->createSimpleCriteria(Article::getClass(), array('type' => null));

NQL

        $critera = $this->em->createNqlCriteria('SELECT a FROM Article WHERE type = :type',
              array('type' => null));

Nutzt du Placeholders, so musst du dich selbst darum kümmern, da die Parameter-Werte dadurch erst nach dem Kompilieren der Abfrage (Criteria::toQuery()) bekannt werden.

Criteria API

        $critera = $this->em->createCriteria();
        $critera->select('a')->from(Article::getClass(), 'a')->where()
              ->match('type', 'IS', new CriteriaPlaceholder('type'));
        $critera->setParameter('type', null);

 NQL

       $critera = $this->em->createNqlCriteria('SELECT a FROM Article WHERE type IS :type');
       $critera->setParameter('type', null);
« Beziehungen Eigenschafts-Typen »

comments_title

post_login_to_create

questions_title