這章節要介紹關聯式資料庫,如果對於資料庫還不熟悉的朋友們,可以參考網路上的資料,可能會說明得很清楚。基礎非常重要,就像是要學會使用Laravel的框架,首先要把PHP的運用要熟悉,才能知道框架為什麼要這樣寫這樣用。

工商一下:資料庫管理系統概論, 2/e - 高淑珍、吳建興

這本可以把你的基礎打好,又有練習題可以練習。

什麼是關聯式資料庫?

我是一個個體,我讀過XX大學,所有大學是一個資料表,學生是一個資料表,但我讀過這所大學,我跟他有一個無法切斷的關聯,這就是關聯式資料庫。


在做關聯的時候,我們要先認識主鍵、外來鍵、索引。

主鍵 (PRIMARY KEY)

定義:

  1. 唯一值
  2. 不能重複
  3. 不得空值

說明:用來明確識別在資料表當中的,每一筆資料是不同的資料。

數量:一個資料表只能有一個

範例:員工編號、身分證字號等

外來鍵 (FOREIGN KEY)

定義:

  1. 值可重複
  2. 可空值

說明:用來與其他資料表做連結。

數量:一個資料表可以多個

唯一索引 (UNIQUE INDEX)

定義:

  1. 值不得重複
  2. 可有一個空值

說明:加快資料表查詢及排序速度。

數量:一個資料表可以多個唯一索引

建立關聯的資料表

我們這章要用到以下資料表,共5張表,若沒建立會員資料表及會員類別資料表,請回顧以前的章節唷!

Model名稱使用英文單數,以大駝峰式命名(Upper Camel Case),首字要大寫

建立會員詳細資訊資料表

※ Model 不管有沒有用到,我們還是會一併建立,以防不時之需。

1
$ php artisan make:model MemberInfo --migration

開啟剛剛建立的 migrations 檔案。
專案 > database > migrations > 2020_07_25_150146_create_member_infos_table.php

將所需要欄位都寫進去, m_id 這個欄位會對應 members 資料表的 id

  • members 資料表 > id 【主鍵】
  • member_infos 資料表 > m_id 【外來鍵】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMemberInfosTable extends Migration
{
public function up()
{
Schema::create('member_infos', function (Blueprint $table) {
// 設定儲存引擎類型。 預設:InnoDB
$table->engine ='MyISAM';
// 編號
$table->Increments('id');
// 對應會員表的編號
$table->integer('m_id');
// 性別
$table->string('m_sex',1)->nullable();
// 中華民國身分證
$table->string('m_roc_id',10)->nullable();
// 地址
$table->string('m_address',100)->nullable();
// 建立時間、更新時間
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('member_infos');
}
}

建立興趣資料表

1
$ php artisan make:model Interest --migration

開啟剛剛建立的 migrations 檔案。
專案 > database > migrations > 2020_07_26_072000_create_interests_table.php

  • member_infos 資料表 > m_interest 【外來鍵】
  • interests 資料表 > id 【主鍵】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateInterestsTable extends Migration
{
public function up()
{
Schema::create('interests', function (Blueprint $table) {
// 設定儲存引擎類型。 預設:InnoDB
$table->engine = 'MyISAM';
// 編號
$table->Increments('id');
// 興趣
$table->string('name',20)->nullable();
// 興趣種類
$table->string('type',20)->nullable();
// 建立時間、更新時間
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('interests');
}
}

建立會員的興趣資料表

1
$ php artisan make:model MemberInterest --migration

開啟剛剛建立的 migrations 檔案。
專案 > database > migrations > 2020_07_28_083727_create_member_interests_table.php

  • member_interests.m_id = members.id
  • member_interests.interest_id = interests.id
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateMemberInterestsTable extends Migration
{
public function up()
{
Schema::create('member_interests', function (Blueprint $table) {
// 設定儲存引擎類型。 預設:InnoDB
$table->engine = 'MyISAM';
// 編號
$table->Increments('id');
// 會員編號
$table->integer('m_id');
// 興趣編號
$table->integer('interest_id');
// 建立時間、更新時間
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('member_interests');
}
}
  • members 會員資料表
  • member_infos 會員詳細資訊資料表
  • member_categories 會員類別資料表
  • interests 興趣資料表
  • member_interests 會員的興趣資料表

建立好的資料表,記得自己填上資料。

Eloquent 關係

一對一

最簡單的就是一對一,一筆會員帳號資料(members) 對上 一筆會員詳細資料(members_infos)。
如下圖,members.id -> memeber_infos.m_id

定義一對一的方法是 Eloquent Model 所提供的 $this->hasOne()

1
return $this->hasOne('路徑\Model名稱(B資料表)', 'B資料表外來鍵欄位','A資料表對應欄位');

第三個參數,如果為id 可以做省略,但比較建議都用上面的方法。

1
return $this->hasOne('路徑\Model名稱(B資料表)', 'B資料表外來鍵欄位');

【實際操作】

  • A資料表:members
  • B資料表:member_infos

開啟會員資料表(A)對應的 Eloquent Model

  • m_id 為B資料表的外來鍵
  • id 為A資料表主鍵

專案 > app > Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Member extends Model
{
public function info()
{
// 一對一關聯
return $this->hasOne(MemberInfo::class,'m_id','id');
}
}

在上面 Eloquent Model 一對一關聯完以後,要做資料庫的操作,開啟 MemberController
※ 不會建立 Controller 的朋友們,請自行到相關的章節閱讀。

1、建立一個 function 命名自行決定,方便了為了之後寫在一起,所以我用 one_to_one 命名。

2、引入 memberEloquent Model -> use App\Member;

3、查詢會員的第三筆資料

4、$member 定義的就是我們要的第3筆資料,分別要取出會員姓名、會員電話。

5、由於我在 MemberEloquent Model 裡有自訂一個方法 info,而這info的方法就是我們設定一對一關聯 member_infos的資料表。使用 ->info 要從關聯的資料表取出我們要的欄位,分別是會員性別、會員身分證字號、會員地址。

專案 > app > Http > Controllers > MemberController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Http\Controllers;

use App\Member;
use Illuminate\Http\Request;

class MemberController extends Controller
{
public function one_to_one()
{
// 查詢會員資料表的第3筆資料
$member = Member::find(3);

echo '會員姓名:' . $member->m_name .'<br>';
echo '會員電話:' . $member->m_phone .'<br>';
echo '會員性別:' . $member->info->m_sex .'<br>';
echo '會員身分證字號:' . $member->info->m_roc_id .'<br>';
echo '會員地址:' . $member->info->m_address .'<br>';


echo '關聯資料表查詢結果:'.$member->info;
}
}

最後記得設定對應的URL。
專案 > routes > web.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/member/one_to_one', 'MemberController@one_to_one');

執行專案

1
$ php artisan serve

http://127.0.0.1:8000/member/one_to_one

查詢結果:

查詢結果列出的「關聯資料表查詢結果」,在定義的 info 做一對一的關聯,回傳的結果會是 object 的型態。


一對一(逆)

一對一(逆),其實只是相反的對應,只是變成會員詳細資料(members_infos) 對上一筆會員帳號資料(members)。

定義一對一(逆)的方法$this->belongsTo(),第二個參數跟第三個參數跟一對一是一樣的欄位。

1
return $this->belongsTo('路徑\Model名稱(B資料表)','A資料表對應欄位','B資料表對應欄位');

第三個參數,如果為id 可以做省略,但比較建議都用上面的方法。

1
return $this->belongsTo('路徑\Model名稱(B資料表)','A資料表對應欄位');

【實際操作】

  • A資料表:member_infos
  • B資料表:members

開啟會員詳細資料(A)對應的 Eloquent Model

  • m_id 為A資料表的外來鍵
  • id 為B資料表主鍵

專案 > app > MemberInfo.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class MemberInfo extends Model
{
public function member()
{
// 一對一(逆)
return $this->belongsTo(Member::class,'m_id','id');
}

}

接下來做資料庫的操作,其實方法跟一對一的一樣,只是反過來而已。
我們一樣在 MemberController 做資料庫操作。

建立方式都跟一對一一樣,不再贅述。

記得在上方引入 use App\MemberInfo;

專案 > app > Http > Controllers > MemberController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Http\Controllers;

use App\MemberInfo;
use Illuminate\Http\Request;

class MemberController extends Controller
{
public function one_to_one_Inverse()
{
// 查詢 member_infos 資料表的第8筆
$info = MemberInfo::find(8);

echo '會員姓名:' . $info->member->m_name .'<br>';
echo '會員電話:' . $info->member->m_phone .'<br>';
echo '會員性別:' . $info->m_sex .'<br>';
echo '會員身分證字號:' . $info->m_roc_id .'<br>';
echo '會員地址:' . $info->m_address .'<br>';
}
}

設置對應的URL。

專案 > routes > web.php

1
Route::get('/member/one_to_one_Inverse', 'MemberController@one_to_one_Inverse');

http://127.0.0.1:8000/member/one_to_one_Inverse

查詢結果:

一對一一對一(逆) 都查詢同一筆資料,結果都會是一樣的。


一對多

關聯式資料表,最常見的就是一對多,例如「超商」為一個類別,但是他底下可以列出很多「便利商店」。
看下圖,我們有兩個資料表,可是我們「一般會員」就有 3個會員符合。

如何去定義一對多的關聯?

$this->hasMany();

1
return $this->hasMany('路徑\Model名稱(B資料表)', 'B資料表外來鍵欄位','A資料表對應欄位');

第三個參數,如果為id 可以做省略。

1
return $this->hasMany('路徑\Model名稱(B資料表)', 'B資料表外來鍵欄位');

【實際操作】

  • A資料表:member_categories
  • B資料表:members

開啟會員類別(A)對應的 Eloquent Model

  • id 為A資料表主鍵
  • category 為B資料表的外來鍵

專案 > app > MemberCategory.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class MemberCategory extends Model
{
// 一對多
public function member()
{
return $this->hasMany(Member::class,'category','id');
}
}

建立一個會員類別的 Controller ,做資料庫的操作。
※ 不會建立 Controller 的朋友們,請自行到相關的章節閱讀。
1、記得引入 use App\MemberCategory; ,下面做法基本上與一對一都差不多,只是差在回傳值的部分。

2、建立一個方法(function) one_to_many

3、印出會員等級

4、印出會員名單,由於 $MemberCategory->member 回傳的是陣列,我們要將陣列做一個處理。

join() 是將陣列的值組成一個字串,中間可加入其他元素,也可以使用 implode(),一樣的功能。

專案 > app > Http > Controllers > MemberCategoryController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace App\Http\Controllers;

use App\MemberCategory;
use Illuminate\Http\Request;

class MemberCategoryController extends Controller
{

public function one_to_many()
{
// 查詢會員類別的第2筆
$MemberCategory = MemberCategory::find(2);

// 印出會員等級
echo '會員等級:' . $MemberCategory->name .'<br>';

// ********************
// 定義變數為陣列
$member_group = array();

// 將所有會員名單存進陣列
foreach($MemberCategory->member as $row){
$member_group[] = $row->m_name;
}

// 印出會員名單
echo '會員名單:' . join('、', $member_group) .'<br>';
// ********************


// 印出篩選出來的結果
echo '會員:' . $MemberCategory->member .'<br>';


}
}

設置對應的URL。

與一對一是設置不同的 Controller ,記得別搞錯了!
專案 > routes > web.php

1
Route::get('/member/one_to_many', 'MemberCategoryController@one_to_many');

執行專案

1
$ php artisan serve

http://127.0.0.1:8000/member/one_to_many

查詢結果:

上面的查詢結果,在會員的部分,是列出會員的詳細資料,但他們都是一筆一筆資料,在做呼叫方法的時候,回傳的方式是以陣列(array)呈現。


多對一 or 一對多(逆)

跟一對一一樣,我們可以做逆向的關聯,只是這樣會變成多對一的結果。

就像是小明的會員類別是金牌會員,小黑也是金牌會員,小明跟小黑就是代表多的那方,金牌會員就是一的那方。

定義多對一的方法,跟一對一(逆)一樣。

1
return $this->belongsTo('路徑\Model名稱(B資料表)','A資料表對應欄位','B資料表對應欄位');

第三個參數,如果為id 可以做省略,但比較建議都用上面的方法。

1
return $this->belongsTo('路徑\Model名稱(B資料表)','A資料表對應欄位');

【實際操作】

  • A資料表:member_categories
  • B資料表:members

開啟會員類別(A)對應的 Eloquent Model

  • id 為A資料表的主鍵
  • category 為B資料表外來鍵

專案 > app > Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Member extends Model
{
// 多對一 OR 一對多(逆)
public function category_turn()
{
// 回傳object的結果
return $this->belongsTo(MemberCategory::class,'category','id');
}
}

接下的做法都跟一對一(逆)關聯一樣。

開啟 MemberCategoryController.php 做資料庫操作。

一定要記得引入 use App\Member;

專案 > app > Http > Controllers > MemberCategoryController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace App\Http\Controllers;

use App\Member;
use Illuminate\Http\Request;

class MemberCategoryController extends Controller
{
public function one_to_many_Inverse()
{
// 查詢會員的第3筆
$member = Member::find(3);

echo '會員姓名:' . $member->m_name .'<br>';

// 使用Member Model 裡的 category_turn 關聯方法,取出關聯的等級名稱
echo '會員等級:' . $member->category_turn->name .'<br>';

// 印出回傳結果,型態為object
echo '會員等級資料:' . $member->category_turn .'<br>';
}
}

設置對應的URL。

專案 > routes > web.php

1
Route::get('/member/one_to_many_Inverse', 'MemberController@one_to_many_Inverse');

http://127.0.0.1:8000/member/one_to_many_Inverse

查詢結果:

一對一(逆)、一對多(逆),在關聯所回傳的型態基本上都是以object回傳,跟一對多是不太一樣的。


多對多

所有關聯最複雜的,應該就是多對多了。一位會員可能有很多興趣,所以一筆 memeber 對到一堆interests

如何定義多對多的關聯?
$this->belongsToMany()

1 = 路徑\Model名稱(C資料表)
2 = 中間資料表(B資料表)
3 = A資料表(本地)對應B資料表的欄位
4 = C資料表對應B資料表的欄位

1
return $this->belongsToMany('1', '2', '3', '4');

【實際操作】
中間資料表(B),主要是做 A 與 C 資料表中間的橋樑,主要放置多選項的興趣。

  • A資料表:members
  • B資料表:member_interests
  • C資料表:interests

開啟會員資料表(A)對應的 Eloquent Model

  • m_id 為B資料表對應 A資料表的 id
  • interest_id 為B資料表對應 C資料表的 id

專案 > app > Member.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Member extends Model
{
// 多對多
public function interested()
{
return $this->belongsToMany(Interest::class,'member_interests','m_id','interest_id');
}
}

建立一個 會員 的 Controller ,做資料庫的操作。
※ 不會建立 Controller 的朋友們,請自行到相關的章節閱讀。

1、記得引入 use App\Member; ,下面做法基本跟之前做的關聯都差不多。

join() 是將陣列的值組成一個字串,中間可加入其他元素,也可以使用 implode(),一樣的功能。

專案 > app > Http > Controllers > MemberController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Http\Controllers;

use App\Member;
use Illuminate\Http\Request;

class MemberController extends Controller
{
public function many_to_many()
{
$members = Member::find(3);

echo '會員姓名:' . $members->m_name .'<br>';

// ********************
$interest_name = array();

foreach($members->interested as $interest){
$interest_name[] = $interest->name;
}

echo '會員興趣:' . join('、', $interest_name) .'<br>';
// ********************
}
}

設置對應的URL。

專案 > routes > web.php

1
Route::get('/member/many_to_many', 'MemberController@many_to_many');

執行專案

1
$ php artisan serve

http://127.0.0.1:8000/member/many_to_many

查詢結果:

那至於多對多有沒有相反結果?當然有,就是跟上面一樣而已,只是角色不一樣而已。

結論

在資料庫關聯其實不難,只是複雜了點,但是如果把資料庫熟悉一定程度,其實這些都算是小菜一疊,道路不難走,重點是你用什麼方式去走。

參考文獻

Laravel - EloquentORM - Relationships - 不學會死

学院君

標籤: w3HexSchool PHP Laravel