Vi har redan sett hur man kan anvÀnda Active Record (AR) till att selektera data frÄn en enstaka databastabell. I det hÀr avsnittet, beskrivs hur man anvÀnder AR till att sammanfoga (join) ett antal relaterade databastabeller och lÀmna den resulterande datamÀngden i retur.
För att relationell AR skall kunna anvÀndas, krÀvs det att vÀldefinierade samband etablerats mellan primÀrnyckel resp. referensattribut (foreign key) för de tabeller som behöver förenas. AR förlitar sig pÄ metadata om dessa samband för att avgöra hur tabellerna skall sammanfogas.
MÀrk: FrÄn och med version 1.0.1, Àr det möjligt att anvÀnda relationell AR Àven utan att referensattributrestriktioner (foreign key constraints) har definierats i databasen.
För enkelhets skull kommer databasschemat som visas i följande entity- relationshipdiagram (ER-diagram) att anvÀndas för att illustrera exempel i detta avsnitt.
ER-diagram

Info: Stödet för referensattributrestriktioner varierar mellan olika databashanterare.
SQLite stöder inte referensattributrestriktioner, men det gÄr ÀndÄ att deklarera restriktionerna nÀr tabeller skapas. AR kan dra fördel av dessa deklarationer för att korrekt stödja frÄgor som involverar tabellsamband.
MySQL stöder referensattributrestriktioner med InnoDB-motorn, men inte med MyISAM. DÀrför rekommenderas anvÀndning av InnoDB för MySQL databaser. NÀr MyISAM anvÀnds kan man dra fördel av följande trick, sÄ att frÄgor som involverar tabellsamband kan utföras med hjÀlp av AR:
CREATE TABLE Foo ( id INTEGER NOT NULL PRIMARY KEY ); CREATE TABLE bar ( id INTEGER NOT NULL PRIMARY KEY, fooID INTEGER COMMENT 'CONSTRAINT FOREIGN KEY (fooID) REFERENCES Foo(id)' );I exemplet ovan, anvÀnds nyckelordet
COMMENTför att beskriva referensattributrestriktionen som sedan kan lÀsas och ge AR insikt om det beskrivna sambandet.
Innan AR kan anvÀndas till att genomföra relationella frÄgor, mÄste AR fÄ veta hur en AR-klass relaterar till en annan.
Samband mellan tvÄ AR-klasser Àr direkt förknippat med sambandet mellan
databastabellerna som AR-klasserna representerar. FrÄn databasens synvinkel kan
sambandet mellan tvÄ tabeller A and B ha tre typer: en-till-mÄnga (t.ex. User
och Post), en-till-en (t.ex. User och Profile) samt mÄnga-till-mÄnga
(t.ex. Category och Post). Inom AR finns det fyra sorters samband:
BELONGS_TO: om sambandet mellan tabellerna A och B Àr en-till-mÄnga, sÄ
Àr B tillhörig A (t.ex. Post tillhör User);
HAS_MANY: om sambandet mellan tabellerna A och B Àr en-till-mÄnga, sÄ har
A mÄnga B (t.ex. User har mÄnga Post);
HAS_ONE: detta Àr ett specialfall av HAS_MANY dÀr A har som mest en B
(t.ex. User har som mest en Profile);
MANY_MANY: detta motsvarar mÄnga-till-mÄngasambandet i databasen. En
assisterande tabell erfordras för att bryta upp ett mÄnga-till-mÄngasamband i
ett-till-mÄngasamband, eftersom de flesta databashanterare saknar direkt stöd
för mÄnga-till-mÄngasamband. I vÄrt exempelschema, tjÀnar PostCategory
detta syfte. Med AR terminology kan MANY_MANY förklaras som kombinationen
av BELONGS_TO och HAS_MANY. Till exempel, Post tilhör mÄnga Category
och Category har mÄnga Post.
Tabellsamband deklareras i AR genom att metoden relations() i CActiveRecord ÄsidosÀtts. Denna metod returnerar en array med sambandskonfigurationer. Varje element i denna array representerar ett enstaka samband, pÄ följande format:
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...additional options)dÀr VarName Àr sambandets namn; RelationType specificerar sambandets typ,
som kan vara en av de fyra konstanternas: self::BELONGS_TO, self::HAS_ONE,
self::HAS_MANY samt self::MANY_MANY; ClassName Àr namnet pÄ den AR-klass
som har samband med denna AR-klass; ForeignKey specificerar det eller de
referensattribut som Àr involverade i sambandet. Ytterligare alternativ kan
specificeras i slutet av varje sambandsdeklaration (beskrivs lÀngre fram).
Följande kod visar hur sambandet mellan klasserna User och Post deklareras.
class Post extends CActiveRecord
{
public function relations()
{
return array(
'author'=>array(self::BELONGS_TO, 'User', 'authorID'),
'categories'=>array(self::MANY_MANY, 'Category', 'PostCategory(postID, categoryID)'),
);
}
}
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'authorID'),
'profile'=>array(self::HAS_ONE, 'Profile', 'ownerID'),
);
}
}Info: Ett referensattribut kan vara sammansatt och bestÄ av tvÄ eller flera kolumner. I det fallet skall namnen pÄ kolumner som ingÄr i referensattributet skrivas efter varandra, separerade av blanksteg eller komma. För samband av typen
MANY_MANYmÄste namnet pÄ den assisterande tabellen ocksÄ specificeras i referensattributet. Till exempel, sambandetcategoriesiPostÀr specificerat med referensattributetPostCategory(postID, categoryID).
Deklarationen av samband i en AR-klass lÀgger underförstÄtt till en property i
klassen för varje samband. NÀr en relationell frÄga har utförts kommer den
motsvarande propertyn att innehÄlla den relaterade AR-instansen(-erna). Till
exempel, om $author representerar en AR-instans User, kan $author->posts
anvÀndas för tillgÄng till dess relaterade Post-instans.
Det enklaste sÀttet att utföra en relationell frÄga Àr genom att lÀsa en relationell property i en AR-instans. Om denna property inte har lÀsts tidigare kommer en relationell frÄga att initieras, som slÄr samman de tvÄ relaterade tabellerna och filtrerar med primÀrnyckeln i aktuell AR-instans. FrÄgeresultatet kommer att sparas i propertyn som en eller flera instanser av den relaterade AR- klassen. Detta förfarande Àr kÀnt som lazy loading, dvs den relationella frÄgan utförs först nÀr relaterade objekt refereras till första gÄngen. Exemplet nedan visar hur man anvÀnder detta tillvÀgagÄngssÀtt:
// retrieve the post whose ID is 10
$post=Post::model()->findByPk(10);
// retrieve the post's author: a relational query will be performed here
$author=$post->author;Info: Om det saknas en relaterad instans i ett samband kan den motsvarande propertyn anta vÀrdet null eller en tom array. För sambanden
BELONGS_TOochHAS_ONE, Àr resultatet null; förHAS_MANYochMANY_MANY, Àr det en tom array. MÀrk att sambandstypernaHAS_MANYochMANY_MANYreturnerar arrayer av objekt, dÀrför behöver man iterera över resultatet för att komma Ät propertyn. Om man inte gör detta erhÄlls felet "Trying to get property of non-object".
TillvÀgagÄngssÀttet med lazy loading Àr mycket bekvÀmt att anvÀnda, men har
lÀgre prestanda i vissa scenarier. Till exempel, om vi vill fÄ tillgÄng till
information om författare för N postningar, kommer tillvÀgagÄngssÀttet lazy
att omfatta körning av N join-frÄgor. Under dessa omstÀndigheter bör det
alternativa tillvÀgagÄngssÀttet, kallat eager loading, anvÀndas.
TillvÀgagÄngssÀttet eager loading hÀmtar in relaterade AR-instanser tillsammans med huvudinstansen (-instanserna). Detta Ästadkommes genom anvÀndning av metoden with() tillsammans med en av find- eller findAll-metoderna i AR. Till exempel,
$posts=Post::model()->with('author')->findAll();OvanstÄende kod returnerar en array bestÄende av Post-intanser. Till skillnad
frÄn tillvÀgagÄngssÀttet lazy, Àr propertyn author i varje instans av Post
redan laddad med den relaterade User-instansen redan innan vi refererar till
propertyn. I stÀllet för att exekvera en join-frÄga för varje postning, hÀmtar
tillvÀgagÄngssÀttet eager loading in samtliga postningar tillsammans med deras
respektive författare, alltsammans i en enda join-frÄga!
Man kan specificera flera sambandsnamn till metoden with() och tillvÀgagÄngssÀttet eager loading kommer att hÀmta in dem alla i ett moment. Till exempel, följande kod hÀmtar in postningar tillsammans med deras repektive författare och kategorier:
$posts=Post::model()->with('author','categories')->findAll();Det gÄr att anvÀnda nÀstlad eager loading. I stÀllet för en lista med sambandsnamn, lÀmnar vi med en hierarkisk representation av sambandsnamnen till metoden with(), som i följande exempel,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();OvanstÄende exempel hÀmtar in alla postningar tillsammans med deras respektive författare och kategorier. Det hÀmtar Àven in varje författares profil samt postningar.
MÀrk: SÀttet att anvÀnda metoden with() har Àndrats frÄn och med version 1.0.2. Den tillhörande API-dokumentationen bör lÀsas omsorgsfullt.
AR-implementeringen i Yii Àr mycket effektiv. Vid eager loading av en hierarki
av relaterade objekt omfattande N HAS_MANY- eller MANY_MANY-samband,
behövs N+1 SQL-frÄgor för att uppnÄ önskat resultat. Detta innebÀr att den
behöver exekvera 3 SQL-frÄgor i det förra exemplet, pÄ grund av propertyna
posts och categories. Andra ramverk tar ett mer radikalt grepp genom att
anvÀnda en enda SQL-frÄga. Vid en första anblick, verkar det radikala angreppssÀttet
mer effektivt, pÄ grund av att fÀrre frÄgor behöver avkodas och exekveras av
databashanteraren. Men det Àr i verkligheten opraktiskt av tvÄ skÀl. För det
första, finns det mÄnga repetitiva datakolumner i resultatet, vilka krÀver mer
tid att överföra och bearbeta. För det andra, vÀxer antalet rader i
resultatmÀngden exponentiellt med antalet involverade tabeller, vilket gör saken
ohanterlig i takt med att fler samband omfattas.
Sedan version 1.0.2, gÄr det Àven att tvinga fram att en relationell frÄga utförs med hjÀlp av endast en SQL-frÄga. Detta sker helt enkelt genom att ett anrop till together() lÀggs till efter with(). Till exempel,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->together()->findAll();OvanstÄende frÄga kommer att utföras i en enda SQL-frÄga. Utan anropet till
together, skulle det behövas tre SQL-frÄgor: en slÄr
samman tabellerna Post, User och Profile, en slÄr samman tabellerna
User och Post och en slÄr samman Post, PostCategory och Category.
Som nÀmnts kan ytterligare alternativ anges i sambandsdeklarationer. Dessa alternativ, specificerade i form av namn-vÀrdepar, anvÀnds för att anpassa den relationella frÄgan. De sammanfattas nedan.
select: en lista med med kolumner som skall selekteras till den
relaterade AR-klassen. Den har standardvÀrdet '*', vilket innebÀr alla
kolumner. Kolumnnamn skall göras otvetydiga med hjÀlp av aliasToken om de
anvÀnds i ett uttryck (t.ex. COUNT(??.name) AS nameCount).
condition: motsvarar WHERE-ledet. Det Àr som standard tomt. MÀrk att
kolumnreferenser behöver göras otvetydiga med hjÀlp av aliasToken (t.ex.
??.id=10).
params: parametrarna som skall kopplas ihop med den genererade SQL-satsen.
Dessa skall ges som en array bestÄende av namn-vÀrdepar. Detta alternativ har
varit tillgÀngligt frÄn och med version 1.0.3.
on: motsvarar ON-ledet. Villkoret som specificeras hÀr kommer att lÀggas till
sammanslagningsvillkoret med hjÀlp av AND-operatorn. MÀrk att kolumnreferenser
behöver göras otvetydiga med hjÀlp av aliasToken (t.ex. ??.id=10).
Detta alternativ Àr inte relevant vid MANY_MANY-relationer. Det har varit
tillgÀngligt frÄn och med version 1.0.2.
order: motsvarar ORDER BY-ledet. Det Àr som standard tomt. MÀrk att
kolumnreferenser behöver göras otvetydiga med hjÀlp av aliasToken (t.ex.
??.age DESC).
with: en lista med underordnade relaterade objekt som skall laddas
tillsammans med detta objekt. Var uppmÀrksam pÄ att om detta alternativ
anvÀnds olÀmpligt, kan det leda till en Àndlös slinga av relationer.
joinType: typ av sammanslagning för detta samband. Den Àr som standard
LEFT OUTER JOIN.
aliasToken: platshÄllare för kolumnprefix. Den ersÀtts med motsvarande
tabellalias sÄ att kolumnreferenser kan göras otvetydiga. StandardvÀrde Àr
'??.'.
alias: aliasnamn för tabellen som förknippas med detta samband. Detta
alternativ har varit tillgÀngligt frÄn och med version 1.0.1. StandardvÀrde
Àr null, vilket innebÀr att tabellalias genereras automatiskt. Detta skiljer
sig frÄn aliasToken pÄ sÄ sÀtt att den senare bara Àr en platshÄllare och
ersÀtts med faktiskt tabellalias.
together: huruvida tabellen associerad med detta samband skall tvingas till
en ovillkorlig sammanslagning (join) med den primÀra tabellen. Detta alternativ
Àr endast relevant för samband av typerna HAS_MANY och MANY_MANY. Om alternativet
inte anges eller sÀtts till false, kommer varje HAS_MANY- eller MANY_MANY-samband
att, av prestandaskÀl, ha sin egen JOIN-sats. Detta alternativ har varit tillgÀngligt
frÄn och med version 1.0.3.
group: motsvarar GROUP BY-ledet. Det Àr som standard tomt. MÀrk att
kolumnreferenser behöver göras otvetydiga med hjÀlp av aliasToken (e.g.
??.age).
having: motsvarar HAVING-ledet. Det Àr som standard tomt. MÀrk att
kolumnreferenser behöver göras otvetydiga med hjÀlp av aliasToken (e.g.
??.age). Detta alternativ har varit tillgÀngligt frÄn och med
version 1.0.1.
index: namnet pÄ kolumnen vars vÀrden skall anvÀndas som nycklar
i den array som lagrar relaterade objekt. Om detta alternativ inte sÀtts
kommer en relaterad objektarray att anvÀnda ett nollbaserat heltalsindex.
Detta alternativ kan endast sÀttas för sambandstyperna HAS_MANY och MANY_MANY.
Detta alternativ har varit tillgÀngligt sedan version 1.0.7.
Dessutom Àr följande alternativ tillgÀngliga för vissa samband nÀr lazy loading anvÀnds:
limit: begrÀnsar antalet rader som kan selekteras. Detta alternativ Àr
INTE tillÀmpligt pÄ BELONGS_TO-samband.
offset: offset till rader som skall selekteras. Detta alternativ Àr
INTE tillÀmpligt pÄ BELONGS_TO-samband.
Nedan har deklarationen av sambandet posts i User varierats genom
inkludering av nÄgra av ovanstÄende alternativ:
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'authorID',
'order'=>'??.createTime DESC',
'with'=>'categories'),
'profile'=>array(self::HAS_ONE, 'Profile', 'ownerID'),
);
}
}Om vi nu refererar till $author->posts, kommer vi att erhÄlla författarens
postningar sorterade i fallande ordning efter tid de skapats. Varje instans av
postning har ocksÄ fÄtt sina kategorier laddade.
Info: NÀr ett kolumnnamn upptrÀder i tvÄ eller fler tabeller som slÄs samman (join), behöver det göras otvetydigt. Detta Ästadkoms genom att föregÄ kolumnnamnet med dess tabellnamn. Till exempel,
idblirTeam.id. I AR:s relationella frÄgor dÀremot, saknas denna frihet eftersom SQL-satserna genereras automatiskt av AR, vilket systematiskt ger varje tabell ett alias. Av denna anledning anvÀnds, för att undvika konflikter mellan kolumnnamn, en platshÄllare för att indikera förekomsten av en kolumn som behöver göras otvetydig. AR ersÀtter platshÄllaren med ett passande tabellalias och gör kolumnen otvetydig.
Med start frÄn och med version 1.0.2, gÄr det att anvÀnda alternativ för
dynamisk relationell frÄga bÄde med metoden with() och
med with-alternativet. De dynamiska alternativen skriver över existerande
alternativ som specificerats i metoden relations().
Till exempel, för att, med ovanstÄende User-modell, anvÀnda tillvÀgagÄngssÀttet
eager loading till att hÀmta in postningar tillhörande en författare i stigande
ordningsföljd (order-alternativet i sambandet specificerar fallande
ordningsföljd), kan man göra följande:
User::model()->with(array(
'posts'=>array('order'=>'??.createTime ASC'),
'profile',
))->findAll();Med start fr o m version 1.0.5 kan dynamiska frÄgealternativ Àven anvÀndas med relationella frÄgor som anvÀnder tillvÀgagÄngssÀttet lazy loading. För att göra sÄ, anropa en metod vars namn Àr lika sambandsnamnet och lÀmna med de dynamiska frÄgealternativen som metodparameter. Till exempel returnerar följande kod de av en anvÀndares postningar vars status` Àr lika med 1:
$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));MÀrk: StatistikfrÄgor har understötts fr o m version 1.0.4.
Utöver relationella frÄgor som beskrivits ovan, stöder Yii ocksÄ sÄ kallade statistikfrÄgor
(eller aggregationsfrÄgor). Detta refererar till inhÀmtning av aggregeringsinformation om
relaterade objekt, sÄsom antalet kommentarer till varje postning, den genomsnittliga
poÀngsÀttningen för varje produkt, etc. StatistikfrÄgor kan endast utföras mot objekt som har
sambandstyperna HAS_MANY (t.ex. en postning har mÄnga kommentarer) eller MANY_MANY
(t.ex. en postning tillhör mÄnga kategorier och en kategori har mÄnga postningar).
Att genomföra en statistikfrÄga Àr mycket snarlikt till att utföra en relationell frÄga, som tidigare besrivits. Först deklareras en statistikfrÄga i metoden relations() i CActiveRecord precis som vid en relationell frÄga.
class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'postID'),
'categoryCount'=>array(self::STAT, 'Category', 'PostCategory(postID, categoryID)'),
);
}
}Ovan deklareras tvÄ statistikfrÄgor: commentCount berÀknar antalet kommentarer som tillhör
en postning och categoryCount berÀknar antalet kategorier en postning tillhör.
MÀrk att sambandstypen mellan between Post och Comment Àr HAS_MANY, medan sambandstypen
mellan Post och Category Àr MANY_MANY (med hjÀlp av mellantabellen PostCategory).
Som tydligt framgÄr Àr deklarationen mycket snarlik de sambandsdeklarationer som beskrivits
i tidigare delavsnitt. Den enda skillnaden Àr att sambandstypen STAT anvÀnds hÀr.
Med ovanstÄende deklaration kan vi hÀmta antalet kommentarer till en postning med hjÀlp av
uttrycket $post->commentCount. NÀr vi anvÀnder denna property första gÄngen, kommer en
SQL-sats att exekveras implicit för att hÀmta in det önskade resultatet.
Som bekant Àr detta den sÄ kallade lazy loading-metoden. Vi kan Àven anvÀnda
eager loading-metoden om vi behöver avgöra antalet kommentarer för ett flertal postningar:
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();OvanstÄende programsats exekverar tre SQL-satser för att leverera alla postningar tillsammans
med deras respektive kommentarantal och antal kategorier. Om lazy loading-metoden anvÀnds
blir resultatet att 2*N+1 SQL-frÄgor exekveras givet N postningar.
Som standard kalkylerar en statistikfrÄga COUNT-uttrycket (och dÀrmed kommentarantalet och
antalet kategorier i ovanstÄende exempel). Detta kan vi anpassa genom att ange ytterligare
alternativ nÀr vi deklarerar relations().
De tillgÀngliga alternativen summeras nedan.
select: statistikfrÄgan. Som standard COUNT(*), innebÀrande antalet underordnade objekt.
defaultValue: vÀrde som skall tilldelas de poster som inte erhÄller ett resultat frÄn statistikfrÄgan. Till exempel, om en postning inte har nÄgra kommentarer, kommer dess commentCount att ÄsÀttas detta vÀrde. StandardvÀrde för detta alternativ Àr 0.
condition: WHERE-ledet. Som standard tomt.
params: parametrarna som skall kopplas till den genererade SQL-satsen.
De skall anges som en array av namn-vÀrdepar.
order: ORDER BY-ledet. Som standard tomt.
group: GROUP BY-ledet. Som standard tomt.
having: HAVING-ledet. Som standard tomt.
MÀrk: Stödet för namngivna omfÄng har varit tillgÀngligt sedan version 1.0.5.
Relationella frÄgor kan Àven utföras i kombination med namngivna omfÄng. Detta kan ske i tvÄ former. I den första formen appliceras namngivna omfÄng pÄ huvudmodellen. I den andra formen appliceras namngivna omfÄng pÄ relaterade modeller.
Följande kod visar hur namngivna omfÄng appliceras pÄ huvudmodellen.
$posts=Post::model()->published()->recently()->with('comments')->findAll();Detta Àr mycket snarlikt icke-relationella frÄgor. Den enda skillnaden Àr
anropet av with() efter kedjan av namngivna omfÄng. OvanstÄende frÄga skulle
hÀmta nyligen publicerade postningar tillsammans med dess kommentarer.
FÀljande kod visar hur namngivna omfÄng appliceras pÄ relaterade modeller.
$posts=Post::model()->with('comments:recently:approved')->findAll();OvanstÄende frÄga skulle hÀmta alla postningar tillsammans med deras för publicering
godkÀnda kommentarer. MÀrk att comments refererar till sambandsnamnet,
medan recently och approved refererar till tvÄ namngivna omfÄng som deklarerats
i modellklassen Comment. Sambandsnamnet och de namngivna omfÄngen skall separeras med kolon.
Namngivna omfÄng kan Àven specificeras med alternativet with i sambandsdeklarationen i
CActiveRecord::relations(). I följande exempel kommer - om vi accessar $user->posts -
alla postningarnas godkÀnda (för publicering) kommentarer att hÀmtas.
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'authorID',
'with'=>'comments:approved'),
);
}
}MÀrk: Namngivna omfÄng som appliceras pÄ relaterade modeller mÄste specificeras i CActiveRecord::scopes. Detta innebÀr ocksÄ att de inte kan parametriseras.
Signup or Login in order to comment.