From 1d729693961dfa4cf4da45a05d703b392dbcb47f Mon Sep 17 00:00:00 2001 From: Fabio Bas Date: Sun, 12 Jan 2014 23:45:18 +0100 Subject: Quickstart Doc overhaul, pt. 2: active controls + minor fixes --- .../protected/pages/Database/id/ActiveRecord.page | 140 ++++++++++----------- 1 file changed, 70 insertions(+), 70 deletions(-) (limited to 'demos/quickstart/protected/pages/Database/id') diff --git a/demos/quickstart/protected/pages/Database/id/ActiveRecord.page b/demos/quickstart/protected/pages/Database/id/ActiveRecord.page index 454aa3f9..8e81678e 100755 --- a/demos/quickstart/protected/pages/Database/id/ActiveRecord.page +++ b/demos/quickstart/protected/pages/Database/id/ActiveRecord.page @@ -3,7 +3,7 @@

Rekaman Aktif adalah obyek yang melapisi baris dalam tabel atau view database, melindungi akses database dan menambahkan logika domain pada data tersebut. - Dasar dari Rekaman Aktif adalah kelas bisnis, sebagai contoh, kelas + Dasar dari Rekaman Aktif adalah kelas bisnis, sebagai contoh, kelas Products, yang hampir menyamai struktur rekaman dari tabel database dibawahnya. Setiap Rekaman Aktif akan bertanggung jawab atas penyimpanan dan pengambilan data ke dan dari database.

@@ -14,22 +14,22 @@

Kapan Menggunakannya

-

Rekaman Aktif adalah pilihan yang baik untuk logika domain yang tidak terlalu rumit, +

Rekaman Aktif adalah pilihan yang baik untuk logika domain yang tidak terlalu rumit, seperti membuat, membaca, memutakhirkan, dan menghapus. Derivasi dan validasi didasarkan pada satu rekaman yang bekerja denga baik dalam struktur ini. Rekaman Aktif mempunyai kuntungan utama dalam hal kesederhanaan. Mudah untuk membangun Rekaman Aktif, dan mudah untuk dimengerti.

Akan tetapi, seiring dengan perkembangan logika bisnis Anda dalm hal kompleksitas, Anda akan segera ingin menggunakan hubungan langsung obyek Anda, koleksi, turunan, dan seterusnya. Ini tidak mudah diterapkan ke dalam Rekaman Aktif, dan menambahkannya sedikit demi sedikit menjadi sangat kacau. Argumen lain terhadap Rekaman Aktif adalah kenyataan bahwa ia menyandingkan desin obyek ke desain database. Ini menjadikannya lebih sulit untuk merefraktorisasi karena proyek terus berjalan.

- -

Alternatifnya adalah menggunakan Pemeta Data yang yang memisahkan aturan dari obyek bisnis dan bagaimana obyek ini disimpan. + +

Alternatifnya adalah menggunakan Pemeta Data yang yang memisahkan aturan dari obyek bisnis dan bagaimana obyek ini disimpan. Prado menyediakan pilihan tambahan antara Rekaman Aktif dan - Pemeta Data SqlMap. - Pemeta Data SqlMap bisa dipakai untuk mengambil obyek Rekaman Aktif, hasilnya; obyek Rekaman Aktif ini bisa dipakai untuk memutakhirkan database. + Pemeta Data SqlMap. + Pemeta Data SqlMap bisa dipakai untuk mengambil obyek Rekaman Aktif, hasilnya; obyek Rekaman Aktif ini bisa dipakai untuk memutakhirkan database. "Hubungan" antara Rekaman Aktif dan SqlMap digambarkan dalam diagram berikut. Lebih rinci mengenai Pemeta Data SqlMap dapat ditemukan dalam Manual SqlMap. alt="Rekaman Aktif dan SqlMap DataMapper" id="fig:diagram.png" class="figure"/>

- +

Kelas Rekaman Aktif berfungsi untuk melakukan tugas-tugas berikut.

@@ -41,22 +41,22 @@

Implikasi Desain

-Implementasi Prado terhadap Rekaman Aktif tidak memelihara identitas referensial. Setiap obyek diperoleh menggunakan Rekaman Aktif pada data dalam database. Sebagai contoh, jika Anda meminta kustomer tertentu dan mendapatkan kembali obyek Customer, kali berikutnya Anda meminta kustomer itu, Anda akan kembali mendapatkan turunan lain dari obyek Customer. Ini berarti bahwa perbandingan tepat (misalnya menggunakan ===) akan mengembalikan false, sementara perbandingan bebas (misalnya menggunakan ==) akan mengembalikan true jika nilai obyek sama menurut perbandingan bebas. +Implementasi Prado terhadap Rekaman Aktif tidak memelihara identitas referensial. Setiap obyek diperoleh menggunakan Rekaman Aktif pada data dalam database. Sebagai contoh, jika Anda meminta kustomer tertentu dan mendapatkan kembali obyek Customer, kali berikutnya Anda meminta kustomer itu, Anda akan kembali mendapatkan turunan lain dari obyek Customer. Ini berarti bahwa perbandingan tepat (misalnya menggunakan ===) akan mengembalikan false, sementara perbandingan bebas (misalnya menggunakan ==) akan mengembalikan true jika nilai obyek sama menurut perbandingan bebas.

Implikasi desain ini terkait dengan pertanyaan berikut. -"Anda pikir kustomer sebagai obyek, di mana hanya satu, +"Anda pikir kustomer sebagai obyek, di mana hanya satu, atau Anda pikir obyek yang Anda operasikan sebagai duplikat dari database?" -Pemetaan O/R lain akan mengartikan bahwa hanya ada satu obyek Kustomer dengan custID 100, dan secara literal ia adalah kustomer. -Jika Anda mendapatkan kustomer dan mengubah field-nya, maka Anda sekarang telah mengubah kustomer itu. -"Itu berbatasan dengan: Anda telah mengubah duplikat kustomer ini, tapi bukan pada duplikat itu. +Pemetaan O/R lain akan mengartikan bahwa hanya ada satu obyek Kustomer dengan custID 100, dan secara literal ia adalah kustomer. +Jika Anda mendapatkan kustomer dan mengubah field-nya, maka Anda sekarang telah mengubah kustomer itu. +"Itu berbatasan dengan: Anda telah mengubah duplikat kustomer ini, tapi bukan pada duplikat itu. Dan jika dua orang memutakhirkan kustomer pada dua duplikat obyek, siapapun yang memutakhirkan pertama kali, atau mungkin yang terakhir yang menang." [A. Hejlsberg 2003]

Database yang Didukung

-Implementasi Rekaman Aktif memanfaatkan kelas Prado DAO untuk akses data. -Implementasi Rekaman Aktif saat ini mendukung database sebagai berikut. +Implementasi Rekaman Aktif memanfaatkan kelas Prado DAO untuk akses data. +Implementasi Rekaman Aktif saat ini mendukung database sebagai berikut.

  • MySQL 4.1 atau lebih tinggi
  • @@ -69,8 +69,8 @@ Implementasi Rekaman Aktif saat ini mendukung database sebagai berikut.

    Mendefinisikan Rekaman Aktif

    Mari kita anggap tabel - "users" berikut yang berisi dua kolom bernama "username" dan "email", - di mana "username" juga merupakan kunci primer. + "users" berikut yang berisi dua kolom bernama "username" dan "email", + di mana "username" juga merupakan kunci primer. CREATE TABLE users ( @@ -88,7 +88,7 @@ class UserRecord extends TActiveRecord public $username; //kolom bernama "username" dalam tabel "users" public $email; - + /** * @return TActiveRecord active record finder instance */ @@ -116,7 +116,7 @@ Anda dapat menetapkan nama-nama tabel yang memenuhi syarat. Contohnya untuk MySQ class UserRecord extends TActiveRecord { ... //definisi yang sudah ada seprti di atas - + private $_level; public function setLevel($value) { $this->_level=TPropertyValue::ensureInteger($value,0); @@ -126,18 +126,18 @@ class UserRecord extends TActiveRecord { } } -

    Lebih jelas mengenai TComponent dapat ditemukan dalam Dokumentasi komponen. +

    Lebih jelas mengenai TComponent dapat ditemukan dalam Dokumentasi komponen. Nantinya kita harus dapat menggunakan pengambil/penyetel guna membolehkan pengambilan malas atas obyek yang berhubungan.

    Info: TActiveRecord juga dapat bekerja dengan view database dengan menetapkan konstan TABLE terkait ke nama view. Akan tetapi, obyek yang dikembalikan dari view hanya-baca, memanggil metode -save() atau delete() akan memunculkan eksepsi. +save() atau delete() akan memunculkan eksepsi.

    - Metode statis finder() mengembalilkan turunan UserRecord + Metode statis finder() mengembalilkan turunan UserRecord yang dapat dipakai untuk mengambil rekaman dari database. Pengambilan rekaman menggunakan metode finder akan didiskusikan nanti. Metode statis TActiveRecord::finder() mengambil nama kelas Rekaman Aktif sebagai parameter.

    @@ -152,7 +152,7 @@ obyek yang dikembalikan dari view hanya-baca, memanggil metode $dsn = 'pgsql:host=localhost;dbname=test'; //Postgres SQL $conn = new TDbConnection($dsn, 'dbuser','dbpass'); TActiveRecordManager::getInstance()->setDbConnection($conn); - +

    Alternatifnya, Anda dapat membuat basis kelas dan mengganti metode getDbConnection() untuk mengembalikan koneksi database. Ini adalah cara sederhana untuk mengijinkan koneksi database multipel. Kode berikut mendemonstrasikan penetapan koneksi database dalam sebuah basis kelas (tidak perlu menyetel koneksi DB di manapun juga). @@ -178,13 +178,13 @@ class MyDb2Record extends TActiveRecord return $conn; } } - +

    Menggunakan application.xml di dalam Kerangka Kerja Prado

    - Koneksi database standar dapat juga dikonfigurasi menggunakan tag <module> dalam application.xml + Koneksi database standar dapat juga dikonfigurasi menggunakan tag <module> dalam application.xml atau config.xml seperti berikut. @@ -192,8 +192,8 @@ class MyDb2Record extends TActiveRecord - - + +

    Tip: Atribut EnableCache ketika disetel ke "true" akan melakukan cache meta data tabel, yakni nama kolom tabel, indeks dan batasan yang disimpan dalam cache dan dipakai ulang. Anda harus membersihkan atau mematikan cache jika Anda ingin melihat perubahan terhadap definisi tabel Anda. Modul cache juga harus didefinisikan agar cache berfungsi.
    @@ -207,13 +207,13 @@ class MyDb2Record extends TActiveRecord Username="dbuser" Password="dbpass" /> - - - + +

    @@ -299,10 +299,10 @@ $criteria->Offset = 20;
    Catatan: Untuk MSSQL dan saat Limit serta Offset berisi nilai integer positif. Query aktual yang dijalankan diubah oleh kelas - -berdasarkan pada +berdasarkan pada http://troels.arvin.dk/db/rdbms/ untuk mengemulasikan kondisi Limit dan Offset.
    @@ -332,11 +332,11 @@ $finder->find('Username = ? AND Password = ?', $name, $pass); $finder->findAllByAge($age); $finder->findAll('Age = ?', $age); - +
    Tip: Anda juga dapat menggunakan kombinasi AND dan OR sebagai kondisi dalam metode dinamis.
    - +

    findBySql() dan findAllBySql()

    Mencari rekaman menggunakan SQL penuh di mana findBySql() mengembalikan Rekaman Aktif dan findAllBySql()mengembalikan array obyek rekaman. Untuk setiap kolom yang dikembalikan, kelas Rekaman Aktif terkait harus mendefinisikan variabel atau properti untuk setiap nama kolom terkait. @@ -371,7 +371,7 @@ Obyek dimutakhirkan dengan kunci primer dari tabel itu yang berisi definisi yang Sebagai contoh, jika Anda menyisipkan sebuah rekaman baru ke dalam tabel MySQL yang kolomnya didefinisikan dengan "autoincrement", obyek Rekaman Aktif akan dimutakhirkan dengan nilai yang ditambahkan.

    -Untuk memutakhirkan rekaman dalam database, cukup ubah satu atau lebih properti obyek Rekaman Aktif yang sudah diambil dari database dan kemudian panggil metode save(). +Untuk memutakhirkan rekaman dalam database, cukup ubah satu atau lebih properti obyek Rekaman Aktif yang sudah diambil dari database dan kemudian panggil metode save(). $user = UserRecord::finder()->findByName('admin'); @@ -392,7 +392,7 @@ Kapan saja Anda memanggil metode save() pada obyek TActiveRecord, obyek

    Menghapus rekaman yang sudah ada

    Untuk menghapus rekaman yang sudah ada dan diambil, cukup panggil metode delete(). - Anda juga dapat menghapus rekaman dalam database dengan kunci primer tanpa mengambil rekaman apapun menggunakan metode deleteByPk() (dan metode yang sama deleteAllByPks()). + Anda juga dapat menghapus rekaman dalam database dengan kunci primer tanpa mengambil rekaman apapun menggunakan metode deleteByPk() (dan metode yang sama deleteAllByPks()). Sebagai contoh, untuk menghapus satu atau beberapa rekaman dengan menggunakan satu atau lebih kunci primer.

    @@ -457,8 +457,8 @@ TActiveRecord menawarkan dua event, OnCreateCommand dan OnExecuteCo

    -Event OnExecuteCommand dimunculkan ketika perintah dijalankan dan hasil dari database dikembalikan. Obyek parameter TDataGatewayResultEventParameter -dari properti Result berisi data yang dikembalikan dari database. +Event OnExecuteCommand dimunculkan ketika perintah dijalankan dan hasil dari database dikembalikan. Obyek parameter TDataGatewayResultEventParameter +dari properti Result berisi data yang dikembalikan dari database. Data yang dikembalikan dapat diubah dengan setelan properti Result.

    @@ -491,7 +491,7 @@ function logger($sender,$param) } TActiveRecord::finder('MyRecord')->OnExecuteCommand[] = 'logger'; $obj->OnExecuteCommand[] = array($logger, 'log'); //setiap PHP callback yg benar. -
    +

    Hubungan Rekaman Aktif

    @@ -507,7 +507,7 @@ Dalam bagian berikut kita akan menganggap hubungan tabel antara class="figure" /> -

    Tujuannya adalah untuk mendapatkan model obyek yang mewakili ke beberapa derajat hubungan entitas dalam gambar di atas. +

    Tujuannya adalah untuk mendapatkan model obyek yang mewakili ke beberapa derajat hubungan entitas dalam gambar di atas.

    class="figure" /> @@ -534,7 +534,7 @@ CREATE TABLE bar

    Pemetaan Kunci Asing

    -

    Hubungan entitas antara tabel Teams dan Players adalah apa yang dikenal sebagai hubungan 1-M. Yaitu, satu Tim dapat berisi 0 atau lebih Pemain. Dalam batasan hubungan obyek, kita katakan bahwa obyek TeamRecord memiliki banyak obyek PlayerRecord. +

    Hubungan entitas antara tabel Teams dan Players adalah apa yang dikenal sebagai hubungan 1-M. Yaitu, satu Tim dapat berisi 0 atau lebih Pemain. Dalam batasan hubungan obyek, kita katakan bahwa obyek TeamRecord memiliki banyak obyek PlayerRecord. (Perhatikan kebalikan dari arah hubungan antara tabel dan obyek.)

    @@ -548,7 +548,7 @@ class TeamRecord extends TActiveRecord const TABLE='Teams'; public $name; public $location; - + public $players=array(); // deklarasi ini tidak diperlukan lagi sejak v3.1.2 //mendefinisikan anggota $player yang memiliki hubungan banyak dengan PlayerRecord @@ -616,7 +616,7 @@ Metode with_xxx() (di mana xxx adalah nama properti hubungan,

    Catatan: Penting untuk dimengerti bahwa obyek terkait diambil menggunakan query tambahan. Query pertama mengambil obyek sumber, misalnya TeamRecord dalam contoh kode di atas. -Query kedua dipakai untuk mengambil obyek PlayerRecord terkait. +Query kedua dipakai untuk mengambil obyek PlayerRecord terkait. Penggunaan dua query mirip dengan query tunggal menggunakan Left-Outer join dengan eksepsi bahwa hasil null pada tabel kanan tidak dikembalikan. Konsekuensi pemakaian dua tau lebih query adalah kondisi agregat dan join tidak layak menggunakan Rekaman Aktif. Untuk query di luar lingkup Rekaman Aktif, Pemeta Data SqlMap diapat dupertimbangkan.
    @@ -634,9 +634,9 @@ Kode berikut mendefinisikan kelas PlayerRecord lengkap dengan 3 hubunga class PlayerRecord extends TActiveRecord { const TABLE='Players'; - public $player_id; - public $age; - public $team_name; + public $player_id; + public $age; + public $team_name; public $team; // deklarasi ini tidak diperlukan lagi sejak v3.1.2 public $skills=array(); // deklarasi ini tidak diperlukan lagi sejak v3.1.2 @@ -656,9 +656,9 @@ class PlayerRecord extends TActiveRecord }

    -Properti $RELATIONS dari PlayerRecord mendefinisikan properti $team milik TeamRecord. -Array $RELATIONS juga mendefinisikan dua hubungan lainnya yang nanti akan kita uji dalam seksi di bawah ini. -Dalam array(self::BELONGS_TO, 'TeamRecord'), elemen pertama mendefinisikan tipe hubungan, dalam hal ini self::BELONGS_TO dan +Properti $RELATIONS dari PlayerRecord mendefinisikan properti $team milik TeamRecord. +Array $RELATIONS juga mendefinisikan dua hubungan lainnya yang nanti akan kita uji dalam seksi di bawah ini. +Dalam array(self::BELONGS_TO, 'TeamRecord'), elemen pertama mendefinisikan tipe hubungan, dalam hal ini self::BELONGS_TO dan elemen kedua adalah string 'TeamRecord' yang terkait ke nama kelas dari kelas TeamRecord. Obyek pemain dengan obyek tim terkait dapat diambil serperti berikut.

    @@ -703,7 +703,7 @@ class ProfileRecord extends TActiveRecord

    Intinya, ada hubungan "belongs to" untuk obyek yang mengaitkan entitas yang memmpunyai kolom yakni kunci asing. Dalam keadaan tertentu, kita melihat bahwa tabel Profiles mempunyai batasan kunci asing pada kolom player_id yang terkait ke tabel Players kolom player_id. Selanjutnya, obyek ProfileRecord memiliki properti ($player) yang adalah milik obyek PlayerRecord. -Demikian juga, tabel Players mempunyai batasan kunci asing pada kolom team_name yang terkait ke tabel Teams kolom name. +Demikian juga, tabel Players mempunyai batasan kunci asing pada kolom team_name yang terkait ke tabel Teams kolom name. Kemudian, obyek PlayerRecord mempunyai properti ($team) yang adalah milik obyek TeamRecord.

    @@ -756,13 +756,13 @@ dua elemen tambahan ini mirip seperti parameter yang dikirimkan ke metode fi Obyek dapat dengan mudah menangani field multi nilai dengan menggunakan koleksi sebagai nilai field. Database relasional tidak memiliki fitur ini dan dibatasi hanya ke field nilai-tunggal. Ketika Anda memetakan asosiasi satu-ke-banyak, Anda bisa menangani ini menggunakan hubungan has many, intinya menggunakan kunci asing untuk nilai-tunggal akhir dari asosiasi. Tapi asosiasi banyak-ke-banyak tidak bisa melakukan ini karena tidak ada nilai-tunggal akhir ke kunci asing yang dipegangnya.

    -Jawabannya adalah resolusi klasik yang telah dipakai oleh orang selama dekade ini yakni: buat tabel ekstra (tabel asosiasi) untuk merekam asosiasi. +Jawabannya adalah resolusi klasik yang telah dipakai oleh orang selama dekade ini yakni: buat tabel ekstra (tabel asosiasi) untuk merekam asosiasi. Ide dasarnya adalah menggunakan tabel asosiasi untuk menyimpan asosiasi. Tabel ini memiliki ID kunci asing untuk dua tabel yang dikaitkan bersama, masing-masing memiliki pasangan dari obyek yang diasosiasikan.

    Tabel asosiasi tidak mempunyai kaitan obyek dalam-memori dan kunci primernya adalah gabungan dari dua kunci primer dari tabel yang diasosiasikan. -Dalam batasan yang sederhana, tuntuk mengambil data dari tabel asosiasi, Anda melakukan dua query (secara umum, ini juga bisa dicapai menggunakan satu query yang terdiri dari join). -Anggap pengambilan koleksi SkillRecord untuk daftar obyek PlayerRecord. +Dalam batasan yang sederhana, tuntuk mengambil data dari tabel asosiasi, Anda melakukan dua query (secara umum, ini juga bisa dicapai menggunakan satu query yang terdiri dari join). +Anggap pengambilan koleksi SkillRecord untuk daftar obyek PlayerRecord. Dalam hal ini, Anda melakukan query dalam dua tahap. Tahap pertama meng-query tabel Players untuk mencari seluruh baris dari pemain yang Anda inginkan. Tahap kedua mencari obyek SkillRecord ID pemain terkait untuk setiap barisnya dalam tabel asosiasi Player_Skills menggunakan sebuah inner join.

    @@ -794,7 +794,7 @@ class SkillRecord extends TActiveRecord

    Properti statis $RELATIONS dari SkillRecord mendefinisikan bahwa properti $players memiliki banyak PlayerRecords melalui tabel asosiasi 'Player_Skills'. Dalam array(self::MANY_TO_MANY, 'PlayerRecord', 'Player_Skills'), elemen pertama mendefinisikan tipe hubungan, dalam hal ini self::HAS_MANY, -elemen kedua adalah string 'PlayerRecord' yang terkait ke nama kelas dari kelas PlayerRecord, dan elemen ketiga adalah nama dari nama tabel asosiasi. +elemen kedua adalah string 'PlayerRecord' yang terkait ke nama kelas dari kelas PlayerRecord, dan elemen ketiga adalah nama dari nama tabel asosiasi.

    Catatan: @@ -815,22 +815,22 @@ Metode with_xxx() (di mana xxx adalah nama properti hubungan,

    Untuk tabel asosiasi yang mererefensi dirinya sendiri, yaitu titik asosiasi ke tabel yang sama. Sebagai contoh, anggap tabel items dengan item terkait M-N melalui tabel asosiasi related_items. Sintaks dalam contoh berikut adalah benar untuk database PostgreSQL. Untuk database lain, lihat dokumentasinya masing-masing untuk mendefinisikan batasan kunci asing. -CREATE TABLE items +CREATE TABLE items ( - "item_id" SERIAL, + "item_id" SERIAL, "name" VARCHAR(128) NOT NULL, PRIMARY KEY("item_id") ); -CREATE TABLE "related_items" +CREATE TABLE "related_items" ( - "item_id" INTEGER NOT NULL, - "related_item_id" INTEGER NOT NULL, - CONSTRAINT "related_items_pkey" PRIMARY KEY("item_id", "related_item_id"), + "item_id" INTEGER NOT NULL, + "related_item_id" INTEGER NOT NULL, + CONSTRAINT "related_items_pkey" PRIMARY KEY("item_id", "related_item_id"), CONSTRAINT "related_items_item_id_fkey" FOREIGN KEY ("item_id") REFERENCES "items"("item_id") ON DELETE CASCADE ON UPDATE NO ACTION - NOT DEFERRABLE, + NOT DEFERRABLE, CONSTRAINT "related_items_related_item_id_fkey" FOREIGN KEY ("related_item_id") REFERENCES "items"("item_id") ON DELETE CASCADE @@ -839,7 +839,7 @@ CREATE TABLE "related_items" ); -

    Nama tabel asosiasi dalam elemen ketiga dari array hubungan dapat berisi nama kolom tabel asing. Kolom yang didefinisikan dalam tabel asosiasi harus juga didefinisikan dalam kelas rekaman (contohnya properti $related_item_id terkait ke kolom related_item_id dalam tabel related_items). +

    Nama tabel asosiasi dalam elemen ketiga dari array hubungan dapat berisi nama kolom tabel asing. Kolom yang didefinisikan dalam tabel asosiasi harus juga didefinisikan dalam kelas rekaman (contohnya properti $related_item_id terkait ke kolom related_item_id dalam tabel related_items).

    class Item extends TActiveRecord @@ -849,18 +849,18 @@ class Item extends TActiveRecord public $details; //id item asing tambahan didefinisikan dalam tabel asosiasi - public $related_item_id; + public $related_item_id; public $related_items=array(); // deklarasi ini tidak diperlukan lagi sejak v3.1.2 public static $RELATIONS=array ( - 'related_items' => array(self::MANY_TO_MANY, + 'related_items' => array(self::MANY_TO_MANY, 'Item', 'related_items.related_item_id'), ); }
    Tip: -Kunci gabungan dalam tabel asing dapat ditetapkan sebagai nilai dipisahkan koma diantara kurung buka/tutup. Contohnya 'related_items.(id1,id2)'. +Kunci gabungan dalam tabel asing dapat ditetapkan sebagai nilai dipisahkan koma diantara kurung buka/tutup. Contohnya 'related_items.(id1,id2)'.