Kita sudah melihat bagaimana menggunakan Rekaman Aktif (AR) untuk memilih data dari satu tabel database. Dalam seksi ini, kami jelaskan bagaimana menggunakan AR untuk menggabung beberapa tabel database terkait dan membawa hasil set data gabungan.
Untuk menggunakan AR relasional, diperlukan bahwa hubungan kunci-asing didefinisikan dengan baik antara tabel-tabel yang perlu digabung. AR bergantung pada metadata mengenai hubungan ini untuk menentukan bagaimana menggabung tabel
Catatan: Mulai dari versi 1.0.1, Anda dapat menggunakan AR relasional meskipun Anda tidak mendefinisikan pembatas kunci asing dalam database Anda.
Untuk menyederhanakan, kita akan menggunakan skema database yang ditampilkan dalam diagram entity-relationship (ER) atau hubungan-entitas berikut untuk menggambarkan contoh pada seksi ini.
Diagram ER
Info: Dukungan pembatas kunci asing bervariasi dalam DBMS yang berbeda.
SQLite tidak mendukung pembatas kunci asing, tapi Anda masih dapat mendeklarasikan pembatas saat membuat tabel. AR dapat mengeksploitasi deklarasi ini untuk mendukung queri relasional secara benar.
MySQL mendukung pembatas kunci asing dengan InnoDB engine, tapi tidak dengan MyISAM. Selanjutnya direkomendasikan Anda menggunakan InnoDB untuk MySQL database Anda. Ketika menggunakan MyISAM, Anda dapat mengeksploitasi trik berikut agar Anda bisa melakukan queri relasional menggunakan 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)' );Dalam contoh di atas, kita menggunakan kunci kata
COMMENT
untuk menjelaskan pembatas kunci asing yang dapat dibaca oleh AR agar mengenali hubungan yang dijelaskan.
Sebelum kita menggunakan AR untuk melakukan queri relasional, kita perlu membiarkan AR mengetahui bagaimana satu kelas AR dikaitkan dengan yang lain.
Hubungan antara dua kelas AR secara langsung dikaitkan dengan hubungan
antara tabel-tabel database yang disajikan oleh kelas AR.
Dari sudut pandang database, hubungan antar dua tabel A dan B memiliki
tiga jenis: satu-ke-banyak (misal User
dan Post
), satu-ke-satu (misal
User
dan Profile
) dan banyak-ke-banyak (misal Category
dan Post
). Dalam AR,
ada empat jenis hubungan:
BELONGS_TO
: jika hubungan antara tabel A dan B adalah
satu-ke-banyak, maka B milik A (misal Post
milik User
);
HAS_MANY
: jika hubungan tabel A dan B adalah satu-ke-banyak,
maka A memiliki banyak B (misal User
memiliki banyak Post
);
HAS_ONE
: ini kasus khusus pada HAS_MANY
di mana A memiliki paling banyak satu
B (misal User
memiliki paling banyak satu Profile
);
MANY_MANY
: ini berkaitan dengan hubungan banyak-ke-banyak dalam
database. Tabel asosiatif diperlukan untuk memecah hubungan banyak-ke-banyak
ke dalam hubungan satu-ke-banyak, karena umumnya DBMS tidak mendukung
hubungan banyak-ke-banyak secara langsung. Dalam contoh skema database kita,
PostCategory
melayani keperluan ini. Dalam terminologi AR, kita dapat menjelaskan
MANY_MANY
sebagai kombinasi BELONGS_TO
dan HAS_MANY
. Sebagai contoh,
Post
milik banyak Category
dan Category
memiliki banyak Post
.
Mendeklarasikan hubungan dalam AR mencakup penimpaan metode relations() pada CActiveRecord. Metode mengembalikan array konfigurasi hubungan. Setiap elemen array mewakili satu hubungan dengan format berikut:
'VarName'=>array('RelationType', 'ClassName', 'ForeignKey', ...opsi tambahan)
di mana VarName
adalah nama hubungan; RelationType
menetapkan jenis
hubungan yang bisa berupa salah satu dari empat konstan:
self::BELONGS_TO
, self::HAS_ONE
, self::HAS_MANY
dan
self::MANY_MANY
; ClassName
adalah nama kelas AR terkait dengan
kelas AR ini; dan ForeignKey
menetapkan kunci asing yang terkait dalam
hubungan. Opsi tambahan dapat ditetapkan di akhir setiap hubungan
(dijelaskan nanti).
Kode berikut memperlihatkan bagaimana kita mendeklarasikan hubungan kelas User
dan Post
.
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: Kunci asing bisa berupa gabungan, terdiri dari dua atau lebih kolom. Dalam hal ini, kita harus merangkai nama-nama kolom kunci dan memisahkannya dengan spasi atau koma. Untuk jenis hubungan
MANY_MANY
, nama tabel asosiatif juga harus ditetapkan dalam kunci asing. Contohnya, hubungancategories
dalamPost
ditetapkan dengan kunci asingPostCategory(postID, categoryID)
.
Deklarasi hubungan dalam kelas AR secara implisit menambahkan properti
ke kelas untuk setiap hubungan. Setelah queri relasional dilakukan,
properti terkait akan dipopulasi dengan turunan AR bersangkutan.
Sebagai contoh, jika $author
mewakili turunan AR User
, kita
bisa menggunakan $author->posts
untuk mengakses kaitannya dengan turunan Post
.
Cara termudah melakukan queri relasional adalah dengan membaca properti relasional turunan AR. Jika properti tidak diakses sebelumnya, queri relasional akan diinisiasi, di mana gabungan dua tabel terkait dan filter dengan kunci primer pada turunan AR saat ini. Hasil queri akan disimpan ke properti sebagai turunan kelas AR terkait. Ini dikenal sebagai pendekatan lazy loading, contohnya, queri relasional dilakukan hanya saat obyek terkait mulai diakses. Contoh di bawah memperlihatkan bagaimana menggunakan pendekatan ini:
// ambil tulisan di mana ID adalah 10
$post=Post::model()->findByPk(10);
// ambil penulis tulisan: queri relasional akan dilakukan di sini
$author=$post->author;
Info: Jika tidak ada turunan terkait pada hubungan, properti bersangkutan dapat berupa null atau array kosong. Untuk hubungan
BELONGS_TO
danHAS_ONE
, hasilnya adalah null; untuk hubunganHAS_MANY
danMANY_MANY
, hasilnya adalah array kosong. Catatan bahwa hubunganHAS_MANY
danMANY_MANY
mengembalikan array obyek, Anda harus mengulang melalui hasilnya sebelum mencoba untuk mengakses setiap propertinya. Jika sebaliknya, Anda akan menerima kesalahan "Mencoba untuk mendapatkan properti non-obyek".
Pendekatan lazy loading sangat nyaman untuk dipakai, tetapi tidak efisien
dalam beberapa skenario. Sebagai contoh, jika kita ingin mengakses informasi
pembuat untuk N
tulisan, menggunakan pendekatan lazy akan menyertakan eksekusi
N
gabungan queri. Kita harus beralih ke apa yang disebut pendekatan eager loading
dlam situasi seperti ini.
Pendekatan eager loading mengambil turunan AR terkait bersama dengan turunan utama AR. Ini dilaksanakan dengan menggunakan metode with() bersama dengan salah satu metode find atau findAll dalam AR. Sebagai contoh,
$posts=Post::model()->with('author')->findAll();
Kode di atas akan mengembalikan sebuah array turunan Post
. Tidak seperti pendekatan
lazy, properti author
dalam setiap turunan Post
sudah dipopulasi dengan
turunan User
terkait sebelum kita mengakses properti.
Daripada menjalankan queri gabungan untuk setiap tulisan, pendekatan eager loading
membawa semua tulisan bersama dengan penulisnya dalam satu queri gabungan!
Kita dapat menetapkan nama multipel hubungan dalam metode with() dan pendekatan eager loading akan membawa kembali semuanya dalam satu pekerjaan. Sebagai contoh, kode berikut akan membawa kembali tulisan bersama dengan penulis dan kategorinya:
$posts=Post::model()->with('author','categories')->findAll();
kia juga bisa melakukan eager loading berulang. Daripada mendaftar nama-nama hubungan, kita mengopernya dalam penyajian hirarkis nama hubungan ke metode with(), seperti berikut,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->findAll();
Contoh di atas akan membawa kembali semua tulisan bersama dengan pembuat dan kategorinya. Ini juga akan membawa kembali profil setiap pembuat serta tulisan.
Catatan: Pemakaian metode with() sudah diubah sejak versi 1.0.2. Silahkan baca dokumentasi API terkait dengan hati-hati.
Implementasi AR dalam Yii sangat efisien. Saat eager loading,
hirarki obyek terkait melibatkan hubungan N
HAS_MANY
atau MANY_MANY
,
ini akan mengambil N+1
queri SQL untuk mendapatkan hasil yang dibutuhkan.
Ini berarti diperlukan menjalankan 3 queri SQL dalam contoh terakhir karena
properti posts
dan categories
. Framework lain akan mengambil pendekatan
lebih radikal dengan hanya menggunakan satu queri SQL. Sekilas, pendekatan radikal
terlihat lebih efisien karena queri lebih sedikit yang diurai dan dijalankan
oleh DBMS. Kenyataanya tidak praktis karena dua alasan. Pertama, terdapat
banyak kolom data yang berulan dalam hasil yang membutuhkan waktu ekstra
untuk mengirimkan dan memrosesnya. Kedua, jumlah baris dalam hasil membengkak
secara eksponensial dengan jumlah tabel yang terlibat, menjadikannya tidak bisa
diatur karena hubungan lebih banyak yang terlibat
Sejak versi 1.0.2, juga dapat memaksa queri hubungan untuk dikerjakan dengan hanya satu queri SQL. Cukup tambahkan panggilan together() setelah with(). Sebagai contoh,
$posts=Post::model()->with(
'author.profile',
'author.posts',
'categories')->together()->findAll();
Queri di atas akan dikerjakan oleh satu query SQL. Tanpa memanggil together,
ini akan membutuhkan dua queri SQL: satu gabungan tabel Post
, User
dan Profile
,
serta gabungan lain tabel User
dan Post
.
Telah kita sebutkan bahwa opsi tambahan dapat ditetapkan dalam deklarasi hubungan. Opsi ini ditetapkan sebagai pasangan nama-nilai, dipakai untuk menkustomisasi queri relasional. Ringkasannya adalah sebagai berikut.
select
: daftar kolom yang dipilih untuk kelas AR terkait.
Standarnya adalah '*', berarti semua kolom. Nama-nama kolom harus disatukan
menggunakan aliasToken
jika muncul dalam sebuah ekspresi (misalnya
COUNT(??.name) AS nameCount
).
condition
: klausul WHERE
. Standarnya kosong. Catatan, referensi
kolom perlu disatukan mengunakan aliasToken
(misalnya ??.id=10
).
params
: parameter yang diikat ke pernyataan SQL yang dibuat.
Ini harus berupa array pasangan nama-nilai. Opsi ini sudah tersedia sejak
versi 1.0.3.
on
: klausul ON
. Kondisi yang ditetapkan di sini akan ditambahkan ke
kondisi penggabungan menggunakan operator AND
. Catatan, referensi
kolom perlu disatukan menggunakan aliasToken
(misalnya ??.id=10
).
Opsi ini tidak berlaku pada relasi MANY_MANY
. Opsi ini sudah tersedia sejak
versi 1.0.2.
order
: klausul ORDER BY
. Standarnya kosong. Catatan, referensi kolom
perlu disatukan menggunakan aliasToken
(misalnya ??.age
DESC
).
with
: daftar anak terkait obyek yang harus diambil bersama dengan
obyek ini. Harap berhati-hati, salah menggunakan opsi ini akan mengakibatkan
pengulangan tanpa akhir.
joinType
: jenis gabungan untuk hubungan ini. Standarnya LEFT
OUTER JOIN
.
aliasToken
: penampung prefiks kolom. Ini akan diganti dengan
alias tabel terkait untuk menyatukan referensi kolom.
Standarnya '??.'
.
alias
: alias untuk tabel terkait dengan hubungan ini.
Opsii ini sudah tersedia sejak versi 1.0.1. Standarnya null,
berarti alias tabel secara otomatis dibuat. Ini berbeda dengan
aliasToken
di mana aliasToken hanyalah penampung dan akan diganti
dengan alias tabel sebenarnya.
together
: apakah tabel yang terkait dengan hubungan ini harus
dipaksa untuk bergabung bersama dengan tabel primer. Opsi ini hanya berarti untuk relasi HAS_MANY dan MANY_MANY. Jika opsi ini tidak disetel atau false, setiap relasi HAS_MANY atau MANY_MANY akan memiliki pernyataan JOIN sendiri untuk meningkatkan performansi. Opsi ini sudah tersedia sejak versi 1.0.3.
group
: klausul GROUP BY
. Standarnya kosong. Catatan, referensi
kolom perlu disatukan menggunakan aliasToken
(misalnya ??.age
).
having
: klausul HAVING
. Standarnya kosong. Catatan, referensi
kolom untuk disatukan menggunakan aliasToken
(misalnya ??.age
).
Catatan: opsi sudah tersedia sejak versi 1.0.1.
Sebagai tambahan, opsi berikut tersedia untuk hubungan tertentu selama lazy loading:
limit
: batas baris yang dipilih. Opsi ini TIDAK berlaku pada
relasi BELONGS_TO
.
offset
: ofset baris yang dipilih. opsi ini TIDAK berlaku pada
relasi BELONGS_TO
.
Di bawah ini kita memodifikasi deklarasi hubungan posts
dalam User
dengan
menyertakan beberapa opsi di atas
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'),
);
}
}
Sekarang jika kita mengakses $author->posts
, kita akan memperoleh tulisan pembuat
yang diurut berdasarkan waktu pembuatannya. Kategori setiap turunan post juga
sudah diambil.
Info: Saat nama kolom muncul dalam dua atau lebih tabel yang sedang digabung bersama, ia perlu disatukan. Ini dikerjakan dengan memberi prefiks pada nama kolom dengan nama tabel. Sebagai contoh,
id
menjadiTeam.id
. Dalam queri relasional AR, kita tidak memiliki kebebasan karena pernyataan SQL secara otomatis dibuat oleh AR yang secara sistematis memberikan alias setiap tabelnya. Oleh karena itu, untuk menghindari konflik nama kolom, kita menggunakan penampung guna menunjukan kolom yang perlu disatukan. AR akan mengganti penampung dengan alias tabel yang sesuai dan menyatukan kolomnya dengan benar.
Mulai dari versi 1.0.2, kita dapat menggunakan opsi queri relasional dinamis baik dalam
with() maupun opsi with
. Opsi dinamis akan
menimpa opsi yang sudah ada seperti yang ditetapkan pada metode relations().
Sebagai contoh, dengan model User
di atas, jika kita ingin menggunakan pendekatan eager
loading untuk membawa kembali tulisan milik penulis dalam urutan membesar
(opsi order
dalam spesifikasi relasi adalah urutan mengecil), kita dapat
melakukannya seperti berikut:
User::model()->with(array(
'posts'=>array('order'=>'??.createTime ASC'),
'profile',
))->findAll();
Mulai dari versi 1.0.5, opsi queri dinamis juga dapat dipakai saat menggunakan pendekatan lazy loading untuk melakukan queri relasional. untuk mengerjakannya, kia harus memanggil metode yang namanya sama dengan nama relasi dan mengoper opsi queri dinamis sebagai parameter metode Sebagai contoh, kode berikut mengembalikan tulisan pengguna yang memiliki status
= 1:
$user=User::model()->findByPk(1);
$posts=$user->posts(array('condition'=>'status=1'));
Catatan: Queri statistik sudah didukung sejak versi 1.0.4.
Selain queri yang dijelaskan di atas, Yii juga mendukung apa yang disebut queri statistik (atau queri agregasional). Ini merujuk ke pengambilan informasi agregasional mengenai obyek terkait, seperti jumlah komentar untuk setiap tulisan, rata-rata peringkat setiap produk, dll. Queri statistik hanya bisa dilakukan untuk obyek terkait dalam HAS_MANY
(misalnya sebuah tulisan memiliki banyak komentar) atau MANY_MANY
(misalnya tulisan milik banyak kategori dan kategori memiliki banyak tulisan).
Melakukan queri statistik sangat mirip dengan melakukan queri relasional seperti dijelaskan sebelumnya. Pertama kita perlu mendeklarasikan queri statistik dalam metode relations() pada CActiveRecord seperti yang kita lakukan dengan queri relasional.
class Post extends CActiveRecord
{
public function relations()
{
return array(
'commentCount'=>array(self::STAT, 'Comment', 'postID'),
'categoryCount'=>array(self::STAT, 'Category', 'PostCategory(postID, categoryID)'),
);
}
}
Dalam contoh di atas, kita mendeklarasikan dua queri statistik: commentCount
menghitung jumlah komentar milik sebuah tulisan, dan categoryCount
menghitung jumlah kategori di mana tulisan tersebut berada. Catatan bahwa hubungan antara Post
dan Comment
adalah HAS_MANY
, sementara hubungan Post
dan Category
adalah MANY_MANY
(dengan menggabung tabel PostCategory
). Seperti yang bisa kita lihat, deklarasi sangat mirip ke relasi tersebut yang kami jelaskan dalam subseksi sebelumnya. Perbedaannya jenis relasinya adalah STAT
di sini.
Dengan deklarasi di atas, kita dapat mengambil sejumlah komentar untuk sebuah tulisan menggunakan ekspresi $post->commentCount
. Ketika kita mengakses properti ini untuk pertama kalinya, pernyataan SQL akan dijalankan secara implisit untuk mengambil hasil terkait. Seperti yang sudah kita ketahui, ini disebut pendekatan lazy loading. Kita juga dapat menggunakan pendekatan eager loading jika kita harus menentukan jumlah komentar untuk multipel tulisan:
$posts=Post::model()->with('commentCount', 'categoryCount')->findAll();
Pernyataan di atas akan menjalankan tiga SQL untuk membawa kembali semua tulisan bersama dengan jumlah komentar dan jumlah kategorinya. Menggunakan pendekatan lazy loading, kita akan berakhir dengan 2*N+1
queri SQL jika ada N
tulisan.
Secara standar, queri statistik akan menghitung ekspresi COUNT
(dan selanjutnya jumlah komentar dan jumlah kategori dalam contoh di atas). Kita dapat mengkustomisasinya dengan menetapkan opsi tambahan saat mendeklarasikannya dalam relations(). Opsi yang tersedia diringkas seperti berikut.
select
: ekspresi statistik. Standarnya COUNT(*)
, berarti jumlah obyek anak.
defaultValue
: nilai yang ditempatkan ke rekaman itu yang tidak menerima hasil queri statistik. Sebagai contoh, jika tulisan tidak memiliki komentar apapun, commentCount
akan menerima nilai ini. Nilai standar untuk opsi ini adalah 0.
condition
: klausul WHERE
. Standarnya kosong.
params
: parameter yang akan diikat ke pernyataan SQL yang dibuat.
Ini harus berupa array pasangan nama-nilai.
order
: klausul ORDER BY
. Standarnya kosong.
group
: klausul GROUP BY
. Standarnya kosong.
having
: klausul HAVING
. Standarnya kosong.
Catatan: Dukungan lingkup bernama sudah tersedia sejak versi 1.0.5.
Query relasional juga dapat dilakukan dengan kombinasi lingkup bernama. Ia datang dengan dua bentuk. Dalam bentuk pertama, lingkup bernama diterapkan ke model utama. Dalam bentuk kedua, lingkup bernama diterapkan ke model terkait.
Kode berikut memperlihatkan bagaimana untuk menerapkan lingkup bernama ke model utama.
$posts=Post::model()->published()->recently()->with('comments')->findAll();
Ini sangat mirip dengan queri non-relasional. Perbedaannya hanyalah bahwa kita memiliki panggilan with()
setelah rantai lingkup-bernama. Queri ini akan membawa kembali tulisan terbaru yang diterbitkan bersama dengan komentarnya.
Kode berikut memperlihatkan bagaimana untuk menerapkan lingkup bernama ke model terkait.
$posts=Post::model()->with('comments:recently:approved')->findAll();
Queri di atas akan membawa kembali semua tulisan bersama dengan komentarnya yang sudah disetujui. Catatan bahwa comments
merujuk ke nama relasi, sementara recently
dan approved
merujuk ke dua lingkup bernama yang dideklarasikan dalam kelas model Comment
. Nama relasi dan lingkup bernama harus dipisahkan dengan titik dua.
Lingkup bernama dapat ditetapkan dalamopsi with
pada aturan relasional yang dideklarasikan dalam CActiveRecord::relations(). Dalam contoh berikut, jika kita mengakses $user->posts
, ia akan membawa kembali semua komentar yang disetujui pada tulisan.
class User extends CActiveRecord
{
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'authorID',
'with'=>'comments:approved'),
);
}
}
Catatan: Lingkup bernama yang diterapkan ke model terkait harus ditetapkan dalam CActiveRecord::scopes. Sebagai hasilnya, ia tidak bisa diparameterisasi.
Signup or Login in order to comment.