mirror of
https://github.com/yiisoft/yii.git
synced 2026-03-04 07:14:06 +01:00
584 lines
22 KiB
Plaintext
584 lines
22 KiB
Plaintext
Relationale ActiveRecords
|
|
=========================
|
|
|
|
Wie man mit ActiveRecord Tabellendaten ausliest, haben wir bereits gezeigt. In
|
|
diesem Abschnitt soll es darum gehen, wie man mit AR Abfragen über
|
|
mehrere verknüpfte Tabellen ausführt.
|
|
|
|
Will man mit relationalen AR arbeiten, sollten in der Datenbank
|
|
[Constraints](http://de.wikipedia.org/wiki/Constraint) (sinngem.:
|
|
Zwangsbedingungen) für Fremdschlüssel definiert sein, um die Konsistenz
|
|
und Integrität der Daten zu gewährleisten.
|
|
|
|
Für die Beispiele in diesem Abschnitt verwenden wir dieses einfache
|
|
Beziehungsmodell:
|
|
|
|

|
|
|
|
> Info|Info: Je nach DBMS werden Fremdschlüssel-Constraints auf
|
|
> unterschiedliche Arten unterstützt. So verwendet z.B. SQLite
|
|
> vor Version 3.6.19 keine Constraints. Man kann sie aber trotzdem
|
|
> beim Erstellen einer Tabelle angeben.
|
|
|
|
Angeben von Beziehungen
|
|
-----------------------
|
|
|
|
Bevor man relationale Abfragen mit AR durchführen kann, müssen die Beziehungen
|
|
zwischen den beteiligten AR-Klassen definiert werden.
|
|
|
|
Diese AR-Beziehungen gleichen denen der entsprechenden Datenbanktabellen.
|
|
In der Datenbank können zwei Tabellen A und B auf drei Arten miteinander
|
|
verknüpft sein: eins-zu-viele (1:n, z.B. zwischen `tbl_user` und `tbl_post`),
|
|
eins-zu-eins (1:1, z.B. zwischen `tbl_user` und `tbl_profile`) und
|
|
viele-zu-viele (n:m, z.B. zwischen `tbl_category` und
|
|
`tbl_post`). In AR sind vier Typen möglich:
|
|
|
|
- `BELONGS_TO` (gehört): Wenn die Tabellen A und B 1:n-verknüpft sind, dann
|
|
gilt: B gehört A (z.B. `Post` gehört `User`).
|
|
|
|
- `HAS_MANY` (hat viele): Auf der anderen Seite gilt bei der selben
|
|
1:n-Verknüpfung zwischen A und B: A hat viele B (z.B. `User` hat viele `Post`).
|
|
|
|
- `HAS_ONE` (hat ein): Dies ist ein Spezialfall von `HAS_MANY`, wobei A höchstens ein
|
|
B hat (z.B. `User` hat höchstens ein `Profile`).
|
|
|
|
- `MANY_MANY` (viele viele): Dies entspricht der n:m-Beziehung in der
|
|
Datenbank. Eine Verbindungstabelle wird benötigt, um die n:m-Beziehung mit
|
|
zwei 1:n-Beziehungen abzubilden, da die meisten DBMS n:m-Beziehungen nicht
|
|
direkt unterstützen. In unserem Beispiel ist `tbl_post_category` eine solche
|
|
Tabelle. In AR-Terminologie kann man eine `MANY_MANY`-Beziehung als
|
|
Kombination einer `BELONGS_TO`- und einer `HAS_MANY`-Beziehung sehen. Beispielsweise gehört
|
|
`Post` zu vielen `Category` und `Category` hat viele `Post`.
|
|
|
|
Beziehungen werden in AR definiert, indem man die
|
|
[relations()|CActiveRecord::relations]-Methode von [CActiveRecord]
|
|
überschreibt. Sie liefert ein Array zurück, in dem jedes Element für eine
|
|
einzelne Beziehung steht:
|
|
|
|
~~~
|
|
[php]
|
|
'VarName'=>array('RelationsTyp', 'KlassenName', 'FremdSchlüssel', ...Zusätzliche Optionen)
|
|
~~~
|
|
|
|
`VarName` gibt den Namen, `RelationsTyp` die Art der Beziehung an. Sie wird
|
|
mit einer der vier Konstanten `self::BELONGS_TO`, `self::HAS_ONE`,
|
|
`self::HAS_MANY` und `self::MANY_MANY` festgelegt. `KlassenName` ist der Name
|
|
der AR-Klasse, zu der die Beziehung besteht. `FremdSchlüssel` gibt den/die zu
|
|
verwendenden Fremdschlüssel an. Schließlich können für jede Definition noch
|
|
weitere Optionen angegeben werden (worauf wir weiter unten noch genauer eingehen).
|
|
|
|
Hier ein Beispiel, wie die Beziehung zwischen `User` und `Post` angegeben
|
|
wird:
|
|
|
|
~~~
|
|
[php]
|
|
class Post extends CActiveRecord
|
|
{
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
|
|
'categories'=>array(self::MANY_MANY, 'Category',
|
|
'tbl_post_category(post_id, category_id)'),
|
|
);
|
|
}
|
|
}
|
|
|
|
class User extends CActiveRecord
|
|
{
|
|
......
|
|
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
|
|
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
> Info|Info: Es gibt auch zusammengesetzte Fremdschlüssel, die zwei oder
|
|
> mehr Spalten verwenden. In diesem Fall müssen alle Spaltennamen des
|
|
> Fremdschlüssels hintereinander mit Komma getrennt angegeben
|
|
> werden. Außerdem benötigt die MANY_MANY-Beziehung zusätzlich den Namen der
|
|
> Verknüpfungstabelle. Er wird zusammen mit dem Fremdschlüssel wie in diesem
|
|
> Beispiel für die `categories`-Beziehung in `Post` angegeben:
|
|
> `tbl_post_category(post_id, category_id)`.
|
|
|
|
Für jede dieser Beziehungsdefinitionen wird automatisch eine implizite
|
|
Eigenschaft in der AR-Klasse erzeugt, über die man auf die entsprechend
|
|
verknüpften AR-Objekte zugreifen kann, nachdem eine relationale Abfrage
|
|
durchgeführt wurde. Ist `$author` z.B. Instanz der AR-Klasse `User`,
|
|
stehen in `$author->posts` verknüpften `Post`-Instanzen zur Verfügung.
|
|
|
|
Ausführen von relationalen Abfragen
|
|
-----------------------------------
|
|
|
|
Am einfachsten führt man eine relationale Abfrage aus, indem man eine der
|
|
eben erwähnten relationalen Eigenschaften eines AR-Objekts ausliest. Falls
|
|
es sich um den ersten Zugriff handelt, wird automatisch eine weitere Abfrage
|
|
(gefiltert nach dem Primärschlüssel der AR-Instanz) auf die verknüpfte Tabelle
|
|
durchgeführt. Das Ergebnis wird dann in Form einer (oder mehrerer) AR-Instanz
|
|
(bzw. Instanzen) in der entsprechenden Eigenschaft gespeichert. Man spricht hier auch
|
|
von *lazy loading* (träges Nachladen), da die Abfrage erst bei Zugriff auf die
|
|
relationale Eigenschaft erfolgt. Hier ein Beispiel:
|
|
|
|
~~~
|
|
[php]
|
|
// Frage den Beitrag mit der ID 10 ab
|
|
$post=Post::model()->findByPk(10);
|
|
// Frage den Autor des Beitrags ab: Hier wird eine relationale Abfrage durchgeführt
|
|
$author=$post->author;
|
|
~~~
|
|
|
|
> Info|Info: Wurden keine verknüpften Einträge gefunden, so ist die
|
|
> entsprechende relationale Eigenschaft entweder `null` (für `BELONGS_TO`- und
|
|
> `HAS_ONE`-Beziehungen) oder ein leeres Array (für `HAS_MANY`- und
|
|
> `MANY_MANY`-Beziehungen). Beachten Sie bitte auch, dass Sie bei `HAS_MANY`- und
|
|
> `MANY_MANY`-Beziehungen die relationalen Eigenschaften in einer Schleife durchlaufen
|
|
> müssen, um auf die verknüpften AR-Objekte zugreifen zu können. Andernfalls
|
|
> erhalten Sie die typische "Trying to get property of
|
|
> non-object"-Fehlermeldung.
|
|
|
|
Mit der `lazy loading`-Methode lässt es sich bequem arbeiten. Allerdings ist
|
|
sie nicht immer sehr effizient. Wollte man zum Beispiel auf die
|
|
`author`-Information von `N` Posts zugreifen, müssten `N` relationale
|
|
Abfragen durchgeführt werden. Hier bietet sich daher stattdessen die
|
|
sogenannte *eager loading*-Methode (sinngem.: begieriges Laden) an.
|
|
|
|
Beim `Eager loading` werden die verknüpften AR-Instanzen gemeinsam mit dem
|
|
Hauptobjekt abgefragt. Dazu muss man einem [find|CActiveRecord::find]-
|
|
oder [findAll|CActiveRecord::findAll]-Aufruf lediglich einen
|
|
[with()|CActiveRecord::with]-Aufruf voranstellen:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('author')->findAll();
|
|
~~~
|
|
|
|
Das Ergebnis ist ein Array von `Post`-Objekten, bei denen jeweils - anders als beim
|
|
`lazy loading` bereits vor dem ersten Zugriff - die `author`-Eigenschaft mit
|
|
der verknüpften `User`-Instanz befüllt wurde. Anstatt für jeden Post eine
|
|
weitere Abfrage durchzuführen, liefert der `eager loading`-Ansatz sämtliche
|
|
Beiträge zusammen mit ihren Autorne in einer einzigen JOIN-Abfrage zurück.
|
|
|
|
Die [with()|CActiveRecord::with]-Methode akzeptiert auch mehrere
|
|
Beziehungsnamen, die dann alle auf einmal abgefragt werden. Mit diesem Aufruf
|
|
erhält man zum Beispiel alle Beiträge inklusive ihrer Autoren und Kategorien:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('author','categories')->findAll();
|
|
~~~
|
|
|
|
Man kann `eager loading` sogar verschachteln. Dazu werden die Beziehungsnamen
|
|
in hierarchischer Form an die [with()|CActiveRecord::with]-Methode übergeben:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with(
|
|
'author.profile',
|
|
'author.posts',
|
|
'categories')->findAll();
|
|
~~~
|
|
|
|
Dieses Beispiel liefert alle Beiträge zusammen mit ihrem Autor und den
|
|
Kategorien zurück. Zusätzlich wird zu jedem Autor auch gleich sein Profil
|
|
sowie die Liste aller seiner Beiträge abgefragt.
|
|
|
|
Seit Version 1.1.0 kann `eager loading` auch über die
|
|
[CDbCriteria::with]-Eigenschaft angestoßen werden:
|
|
|
|
~~~
|
|
[php]
|
|
$criteria=new CDbCriteria;
|
|
$criteria->with=array(
|
|
'author.profile',
|
|
'author.posts',
|
|
'categories',
|
|
);
|
|
$posts=Post::model()->findAll($criteria);
|
|
~~~
|
|
|
|
oder
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->findAll(array(
|
|
'with'=>array(
|
|
'author.profile',
|
|
'author.posts',
|
|
'categories',
|
|
)
|
|
);
|
|
~~~
|
|
|
|
|
|
Optionen für relationale Abfragen
|
|
---------------------------------
|
|
|
|
Wie erwähnt, können bei der Definition einer Beziehung weitere Optionen als
|
|
Name-Wert-Paare angeben werden, um die zugehörige relationale Abfrage
|
|
seinen Wünschen anzupassen. Diese Optionen stehen zur Verfügung:
|
|
|
|
- `select`: Liste der abzufragenden Spalten der verknüpften AR-Klasse.
|
|
Der Standardwert ist '*', d.h. alle Spalten. Spaltennamen sollten
|
|
vereindeutigt werden.
|
|
|
|
- `condition`: Die `WHERE` Klausel, standardmäßig leer. Spaltennamen
|
|
sollten vereindeutigt werden.
|
|
|
|
- `params`: Parameter (als Name-Wert-Paare), die an die erzeugte SQL Abfrage gebunden werden
|
|
sollen. Verfügbar seit Version 1.0.3.
|
|
|
|
- `on`: Zusätzliche `ON` Klausel, die an die JOIN-Kondition
|
|
mit einem `AND` Operator angehängt werden soll. Die Spaltennamen sollten
|
|
vereindeutigt werden. Bei `MANY_MANY`-Beziehungen wird diese Option ignoriert.
|
|
Sie ist seit Version 1.0.2 verfügbar.
|
|
|
|
- `order`: Die `ORDER BY` Klausel, standardmäßig leer. Spaltennamen
|
|
sollten vereindeutigt werden.
|
|
|
|
- `with`: Eine Liste von Relationen, die zusammen mit diesem Objekt
|
|
geladen werden sollen. Beachten Sie, dass bei falscher Verwendung dieser
|
|
Option eine Endlosschleife entstehen kann.
|
|
|
|
- `joinType`: Der Typ des JOINs für diese Beziehung. Der Standardwert ist `LEFT
|
|
OUTER JOIN`.
|
|
|
|
- `alias`: Der Alias für die verknüpfte Tabelle. Diese Option ist seit Version 1.0.1 verfügbar.
|
|
Mit dem Standardwert `null` wird der Beziehungsname als Alias verwendet.
|
|
|
|
- `together`: Ob ein JOIN der verknüpften Tabelle mit der Haupt- bzw. anderen Tabellen
|
|
erzwungen werden soll. Diese Option ist nur für `HAS_MANY` und `MANY_MANY` von Bedeutung.
|
|
Setzt man diesen Wert auf `false`, wird eine separate SQL-Abfrage für die verknüpfte Tabelle
|
|
ausgeführt, wodurch die ganze Abfrage in manchen Fällen beschleunigt wird, da
|
|
weniger gleiche Daten pro Zeile übertragen werden müssen. Ist die Option `true`
|
|
wird die verknüpfte Tabelle immer mittels JOIN zusammen mit der Haupttabelle
|
|
abgefragt, auch wenn diese seitenweise ausgelesen wird (Pagination).
|
|
Bleibt die Option leer, wird die verbundene Tabelle nur dann
|
|
zusammen mit der Haupttabelle in einer einzigen Abfrage eingelesen, falls
|
|
diese keine Seitenblätterung verwendet. Für weitere Informationen
|
|
lesen Sie bitte auch den Abschnitt "Geschwindigkeit von relationalen Abfragen".
|
|
Diese Option ist seit Version 1.0.3 verfügbar.
|
|
|
|
- `join`: Zusätzliche `JOIN`-Ausdrück, standardmäßig leer. Diese Option ist seit
|
|
Version 1.1.3 verfügbar.
|
|
|
|
- `group`: Die `GROUP BY` Klausel, standardmäßig leer. Spaltennamen
|
|
sollten vereindeutigt werden.
|
|
|
|
- `having`: Die `HAVING` Klausel, standarmäßig leer. Spaltennamen
|
|
sollten vereindeutigt werden. Diese Option ist seit Version 1.0.1. verfügbar.
|
|
|
|
- `index`: Der Spaltenname, deren Wert als Schlüssel für den Array mit
|
|
relationalen Objekten verwendet werden soll. Wird diese Option nicht gesetzt,
|
|
wird ein 0-basierter ganzzahliger Index verwendet.
|
|
Diese Option kann nur für `HAS_MANY`- und `MANY_MANY`-Beziehungen gesetzt
|
|
werden. Diese Option ist seit Version 1.0.7 verfügbar.
|
|
|
|
Zusätzlich sind beim `lazy loading` folgende Optionen für bestimmte Beziehungen
|
|
verfügbar:
|
|
|
|
- `limit`: Limit der abzufragenden Zeilen. Kann NICHT mit `BELONGS_TO`
|
|
verwendet werden.
|
|
|
|
- `offset`: Offset der abzufragenden Zeilen. Kann NICHT mit `BELONGS_TO`
|
|
verwendet werden.
|
|
|
|
|
|
Hier sehen Sie am Beispiel der `posts`-Beziehung in `User`, wie man einige dieser
|
|
Optionen verwenden kann:
|
|
|
|
~~~
|
|
[php]
|
|
class User extends CActiveRecord
|
|
{
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
|
|
'order'=>'posts.create_time DESC',
|
|
'with'=>'categories'),
|
|
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
Greift man auf `$author->posts` zu, erhält man die `posts` des
|
|
`author` absteigend sortiert nach ihrer `creation time` (Erstellzeitpunkt).
|
|
Außerdem wurden die Kategorien zu jedem Post mit geladen.
|
|
|
|
|
|
Vereindeutigung von Spaltennamen
|
|
--------------------------------
|
|
|
|
Wenn ein Spaltenname in zwei oder mehr Tabellen auftaucht, die gemeinsam
|
|
abgefragt werden sollen, so muss dieser Name vereindeutigt werden. Dies
|
|
geschieht, indem man den Tabellenalias vor den Spaltennamen stellt.
|
|
|
|
Bei relationalen Abfragen hat die Haupttabelle den festen Alias `t`, während
|
|
alle relationalen Tabellen standardmäßig den Namen der
|
|
entsprechenden Beziehung verwenden. Im folgenden Beispiel stehen die
|
|
Aliase `t` und `comments` für die Tabellen `Post` und `Comment`:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('comments')->findAll();
|
|
~~~
|
|
|
|
Nehmen wir nun an, dass es sowohl in `Post`, als auch in `Comment` eine
|
|
Spalte namens `create_time` gibt, die den Erstellzeitpunkt des jeweiligen
|
|
Eintrags enthält. Wenn man alle Beiträge zusammen mit ihren Kommentaren
|
|
abfragen möchten und die Ergebnisse nacheinander nach der Erstellzeit der Beiträge und
|
|
der der Kommentare sortieren will, muss `create_time` wie folgt vereindeutigt
|
|
werden:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('comments')->findAll(array(
|
|
'order'=>'t.create_time, comments.create_time'
|
|
));
|
|
~~~
|
|
|
|
> Note|Hinweis: Die Art und Weise, wie Spaltennamen eindeutig angesprochen
|
|
> werden, hat sich seit Version 1.1.0 geändert. In früheren 1.0.x-Versionen hat
|
|
> Yii automatisch ein Alias für jede verknüpfte Tabelle erstellt und man
|
|
> musste die Präfix `??.` als Platzhalter für diesen Alias verwenden. Der
|
|
> Alias der Haupttabelle entsprach deren Namen.
|
|
|
|
|
|
Dynamische Optionen für relationale Abfragen
|
|
--------------------------------------------
|
|
|
|
Seit Version 1.0.2 kann man sowohl beim [with()|CActiveRecord::with]-Aufruf
|
|
als auch bei der `with`-Eigenschaft dynamische Optionen angeben. Diese
|
|
überschreiben dann jene Optionen, die fest in
|
|
[relations()|CActiveRecord::relations] definiert wurden. Möchte man zum
|
|
Beispiel beim obigen `User`-Model per `eager loading` gemeinsam alle Beiträge in
|
|
*aufsteigender Reihenfolge* abfragen, kann man die `order`-Option wie folgt
|
|
überschreiben:
|
|
|
|
~~~
|
|
[php]
|
|
User::model()->with(array(
|
|
'posts'=>array('order'=>'post.create_time ASC'),
|
|
'profile',
|
|
))->findAll();
|
|
~~~
|
|
|
|
Seit Version 1.0.5 kann man auch beim `lazy loading` dynamisch Optionen
|
|
überschreiben, indem man den Beziehungsnamen als Methode aufruft, und die
|
|
dynamischen Optionen als Parameter übergibt. Dieser Aufruf liefert zum
|
|
Beispiel alle Beiträge mit `status` 1 eines Benutzers:
|
|
|
|
~~~
|
|
[php]
|
|
$user=User::model()->findByPk(1);
|
|
$posts=$user->posts(array('condition'=>'status=1'));
|
|
~~~
|
|
|
|
|
|
Geschwindigkeit von relationalen Abfragen
|
|
-----------------------------------------
|
|
|
|
Wie erwähnt bietet sich der `eager loading`-Ansatz für Fälle an, in denen auf viele
|
|
verknüpfte Objekte zugegriffen werden muss. Er erzeugt einen langen komplizierten
|
|
SQL-Ausdruck in dem alle benötigten Tabellen mit JOIN eingebunden werden.
|
|
In vielen Fällen ist ein solcher langer SQL-Ausdruck vorzuziehen, da es damit
|
|
einfacher wird, nach Spalten in verbundenen Tabellen zu filtern. In einigen
|
|
Fällen kann das jedoch auch uneffizient werden.
|
|
|
|
Angenommen, man möchte die letzten Blogbeiträge zusammen mit den verknüpften Kommentaren
|
|
abfragen. Falls jeder Beitrag 10 Kommentare hat, enthielten viele Ergebniszeilen
|
|
redundante Daten. Mit jedem Beitragskommentar würden nämlich die selben
|
|
Beitragsdaten nochmal übermittelt werden. Ein anderer Ansatz wäre daher,
|
|
zunächst die letzten Blogbeiträge und danach erst die zugehörigen
|
|
Kommentare abzufragen. Dafür werden zwar zwei SQL-Abfragen benötigt, allerdings mit
|
|
dem Vorteil, keine redundanten Daten mehr übertragen zu müssen.
|
|
|
|
Welche Methode ist nun effizienter? Darauf gibt es keine endgültige Antwort.
|
|
Eine einzelne große SQL-Abfrage kann schneller sein, da die Datenbank weniger
|
|
zusätzliche Zeit für das Analysieren und Ausführen einzelner SQL-Abfragen benötigt.
|
|
Andererseits führt ein großer SQL-Ausdruck zu mehr redundanten Daten und
|
|
benötigt daher mehr Zeit diese zu übertragen und zu verarbeiten.
|
|
|
|
Um je nach Bedarf zwischen beiden Varianten wählen zu können, bietet Yii die
|
|
`together`-Option. Standardmäßig verfolgt Yii den ersten Ansatz, verwendet
|
|
also einen einzelnen SQL-Ausdruck bei `eager loading`. Setzt man `together`
|
|
bei der Definition einer Beziehungen auf `false`, werden die entsprechenden Tabellendaten
|
|
mit separaten Abfragen eingeholt. Will man also den zweiten Ansatz verwenden,
|
|
um die letzten Blogbeiträge mit ihren Kommentaren einzulesen, kann man die
|
|
Beziehung `comments` in `Post` so definieren:
|
|
|
|
~~~
|
|
[php]
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'comments' => array(self::HAS_MANY, 'Comment', 'post_id', 'together'=>false),
|
|
);
|
|
}
|
|
~~~
|
|
|
|
Man kann diese Option auch dynamisch beim eager loading setzen:
|
|
|
|
~~~
|
|
[php]
|
|
$posts = Post::model()->with(array('comments'=>array('together'=>false)))->findAll();
|
|
~~~
|
|
|
|
> Note|Hinweis: In Version 1.0.x führt Yii bei `N` `HAS_MANY`- oder `MANY_MANY`-Beziehungen
|
|
> standardmäßig `N+1` SQL-Abfragen aus. Jede dieser Beziehungen führt also zu einer weiteren
|
|
> SQL-Abfrage. Um auch hier eine einzelne Abfrage zu erzwingen, kann man die `together()`-Methode
|
|
> nach `with()` aufrufen:
|
|
>
|
|
> ~~~
|
|
> [php]
|
|
> $posts=Post::model()->with(
|
|
> 'author.profile',
|
|
> 'author.posts',
|
|
> 'categories')->together()->findAll();
|
|
> ~~~
|
|
>
|
|
|
|
|
|
Statistische Abfragen
|
|
---------------------
|
|
|
|
> Note|Hinweis: Statistische Abfragen werden seit Version 1.0.4 unterstützt.
|
|
|
|
Neben den oben beschriebenen relationalen Abfragen unterstützt Yii auch sogenannte
|
|
statistische (auch: aggregierte) Abfragen. Damit können statistische
|
|
Informationen über verknüpfte Objekte abgefragt werden, wie
|
|
z.B. die Anzahl von Kommentaren zu einem Beitrag, die durchschnittliche
|
|
Bewertung eines Produkts, etc. Statistische Abfragen können nur mit
|
|
`HAS_MANY`- (z.B. ein Beitrag hat viele Kommentare) oder einer
|
|
`MANY_MANY`-Beziehungen (z.B. ein Beitrag gehört zu
|
|
vielen Kategorien und eine Kategorie hat viele Beiträge) verwendet werden.
|
|
|
|
Statistische Abfragen werden ähnlich wie relationale Abfrage durchgeführt und
|
|
müssen analog zunächst in [relations()|CActiveRecord::relations] definiert werden:
|
|
|
|
~~~
|
|
[php]
|
|
class Post extends CActiveRecord
|
|
{
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'commentCount'=>array(self::STAT, 'Comment', 'post_id'),
|
|
'categoryCount'=>array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
Damit werden zwei statistische Abfragen angegeben: `commentCount` errechnet die
|
|
Anzahl der Kommentare zu einem Beitrag und `categoryCount` die Anzahl von
|
|
Kategorien, denen ein Beitrag zugeordnet wurde. Beachten Sie, dass `Post` und
|
|
`Comment` in einer `HAS_MANY`-Beziehung zueineander stehen, während `Post` und
|
|
`Category` über eine `MANY_MANY`-Beziehung (über die Tabelle `post_category`)
|
|
verknüpft sind.
|
|
|
|
So deklariert kann man über `$post->commentCount` die Anzahl der Kommentare
|
|
eines Beitrags auslesen. Beim ersten Zugriff auf diese Eigenschaft wird
|
|
dazu implizit eine SQL-Abfrage durchgeführt. Dies entspricht wieder dem
|
|
bereits bekannten *lazy loading*-Ansatz. Soll die Anzahl der Kommentare für
|
|
mehrere Beiträge bestimmt werden, kann man stattdessen auch die
|
|
*eager loading*-Methode verwenden:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
|
|
~~~
|
|
|
|
Dieser Befehl führt drei SQL-Anweisungen aus um alle Beiträge zusammen mit der Anzahl
|
|
ihrer Kommentare und Kategorien zurückzuliefern. Würden man den *lazy
|
|
loading*-Ansatz verwenden, würde das bei `N` Beiträgen in `2*N+1` SQL-Abfragen resultieren.
|
|
|
|
Standardmäßig wird bei einer statistische Abfrage ein `COUNT`-Ausdruck
|
|
berechnet (und damit in obigem Beispiel die Anzahl der Kommentare und
|
|
Kategorien). Man kann auch dies über zusätzliche Beziehungsoptionen in
|
|
[relations()|CActiveRecord::relations] anpassen. Diese Optionen stehen zur
|
|
Verfügung:
|
|
|
|
- `select`: Der statistische Ausdruck. Vorgabewert ist `COUNT(*)`, was der
|
|
Anzahl der Kindobjekte entspricht.
|
|
|
|
- `defaultValue`: Standardwert für Zeilen, deren statistische Abfrage
|
|
"leer" ist. Hat ein Beitrag z.B. keine Kommentare, würde sein `commentCount`
|
|
diesen Wert erhalten. Vorgabewert ist 0.
|
|
|
|
- `condition`: Die `WHERE`-Bedingung. Standardmäßig leer.
|
|
|
|
- `params`: Die Parameter, die an die erzeugte SQL-Anweisung gebunden
|
|
werden sollen. Sie müssen als Array aus Name-Wert-Paare angegeben werden.
|
|
|
|
- `order`: Die `ORDER BY`-Anweisung. Standardmäßig leer.
|
|
|
|
- `group`: Die `GROUP BY`-Anweisung. Standardmäßig leer.
|
|
|
|
- `having`: Die `HAVING`-Anweisung. Standarmäßig leer.
|
|
|
|
|
|
Relationale Abfragen mit Scopes
|
|
-------------------------------
|
|
|
|
> Note|Hinweis: Scopes werden seit Version 1.0.5 unterstützt.
|
|
|
|
Auch relationale Abfragen können mit [Scopes](/doc/guide/database.ar#named-scopes)
|
|
kombiniert werden. Dabei unterscheidet man zwei Fälle: Scopes die auf das Hauptobjekt
|
|
und Scopes die auf die verknüpften Objekte angewendet werden.
|
|
|
|
Der folgende Code zeigt, wie Scopes mit dem Hauptobjekt verwendet werden:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->veroeffentlicht()->kuerzlich()->with('comments')->findAll();
|
|
~~~
|
|
|
|
Dies unterscheidet sich kaum vom Vorgehen bei nicht-relationalen Abfragen. Der einzige
|
|
Unterschied besteht im zusätzlichen Aufruf von `with()` nach der Kette von
|
|
Scopes. Diese Abfrage würde also die kürzlich veröffentlichten
|
|
Beiträge zusammen mit ihren Kommentaren zurückliefern.
|
|
|
|
Bei verknüpften Objekten können Scopes so verwendet werden:
|
|
|
|
~~~
|
|
[php]
|
|
$posts=Post::model()->with('comments:kuerzlich:freigegeben')->findAll();
|
|
~~~
|
|
|
|
Diese Abfrage liefert alle Beiträge zusammen mit ihren freigegebenen
|
|
Kommentaren zurück. Beachten Sie, dass `comments` sich auf den Namen der
|
|
Beziehung bezieht, während `kuerzlich` und `freigegeben` zwei Scopes
|
|
sind, die in der Modelklasse `Comment` deklariert sind. Beziehungsname und
|
|
Scopes sollten durch Doppelpunkte getrennt werden.
|
|
|
|
Scopes können auch in den `with`-Optionen in [CActiveRecord::relations()]
|
|
angegeben werden. So würde man im folgenden Beispiel bei Zugriff auf
|
|
`$user->posts`, alle *freigegebenen* Kommentare eines Beitrags erhalten.
|
|
|
|
~~~
|
|
[php]
|
|
class User extends CActiveRecord
|
|
{
|
|
public function relations()
|
|
{
|
|
return array(
|
|
'posts'=>array(self::HAS_MANY, 'Post', 'author_id',
|
|
'with'=>'comments:freigegeben'),
|
|
);
|
|
}
|
|
}
|
|
~~~
|
|
|
|
> Note|Hinweis: Bei relationalen Abfragen können nur Scopes
|
|
verwendet werden, die in [CActiveRecord::scopes] definiert wurden. Man kann
|
|
daher keine parametrisierten Scopes verwenden.
|
|
|
|
<div class="revision">$Id: database.arr.txt 2782 2010-12-28 16:18:06Z qiang.xue $</div>
|