首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

Yii2 ActiveRecord 關聯查詢完整指南:hasOne、hasOne、viaTable、中間表、動態條件與效能優化

     編輯:PHP 綜合知識

Yii2 ActiveRecord 透過 hasOne()、hasMany() 定義關聯,支援一對一、一對多、多對多 (中間表)、動態條件查詢。本文系統整理關聯宣告、存取方式、SQL 執行機制、中間表 viaTable()/via()、條件參數化、快取與常見效能陷阱。

基本關聯宣告

namespace common\models;
use yii\db\ActiveRecord;

class Customer extends ActiveRecord {
    // 一對多:客戶擁有多筆訂單
    public function getOrders() {
        return $this->hasMany(Order::className(), ['customer_id' => 'id']);
    }
    
    // 一對多 + 條件:大額訂單
    public function getBigOrders($threshold = 100) {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where(['>', 'subtotal', $threshold])
            ->orderBy('id');
    }
}

class Order extends ActiveRecord {
    // 一對一:訂單屬於一位客戶
    public function getCustomer() {
        return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
    }
}

參數說明

  • hasMany($class, $link):$link = ['關聯表欄位' => '當前表欄位']
  • hasOne($class, $link):同上,但 LIMIT 1
  • 回傳 yii\db\ActiveQuery 物件,可鏈式呼叫 where(), orderBy(), with() 等。

關聯資料存取:屬性語法 vs 方法語法

$customer = Customer::findOne(1);

// 屬性語法:觸發關聯查詢,結果快取於物件
$orders = $customer->orders;        // 執行 SQL,回傳 Order[] 陣列
$orders2 = $customer->orders;       // 直接回傳快取,不再查詢

// 方法語法:取得 ActiveQuery 物件,可進一步修飾
$query = $customer->getOrders();    // 無 SQL,回傳 ActiveQuery
$bigOrders = $customer->getBigOrders(200)->all(); // 重新查詢,帶參數

// 強制重新載入
unset($customer->orders);
$orders = $customer->orders;        // 再次查詢

SQL 執行時機

-- $customer = Customer::findOne(1);
SELECT * FROM customer WHERE id=1;

-- $orders = $customer->orders;
SELECT * FROM order WHERE customer_id=1;

中間表關聯 (多對多)

場景:訂單 - 商品 (中間表 order_item)

class Order extends ActiveRecord {
    // 方式一:viaTable() 直接指定中間表
    public function getItems() {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->viaTable('order_item', ['order_id' => 'id']);
    }
    
    // 方式二:via() 透過已定義關聯
    public function getOrderItems() {
        return $this->hasMany(OrderItem::className(), ['order_id' => 'id']);
    }
    public function getItemsVia() {
        return $this->hasMany(Item::className(), ['id' => 'item_id'])
            ->via('orderItems'); // 關聯方法名
    }
}

SQL 執行 (viaTable)

SELECT * FROM item 
WHERE id IN (SELECT item_id FROM order_item WHERE order_id=1);

動態條件與參數化

class Customer extends ActiveRecord {
    // 方法參數傳遞條件
    public function getOrdersByStatus($status) {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->andWhere(['status' => $status]);
    }
    
    // 關聯範圍
    public function getRecentOrders($days = 30) {
        return $this->hasMany(Order::className(), ['customer_id' => 'id'])
            ->where(['>=', 'created_at', date('Y-m-d', strtotime("-$days days"))]);
    }
}

// 使用
$orders = $customer->getOrdersByStatus('paid')->all();
$recent = $customer->getRecentOrders(7)->all();

急切載入 :with() 解決 N+1 問題

// ❌ N+1 查詢:1 筆客戶 + N 筆訂單 = N+1 次 SQL
$customers = Customer::find()->all();
foreach ($customers as $c) { echo count($c->orders); }

// ✅ 急切載入:2 次 SQL (IN 子查詢或 JOIN)
$customers = Customer::find()->with('orders')->all();
foreach ($customers as $c) { echo count($c->orders); }

// 多層急切載入
$orders = Order::find()->with(['customer', 'items.category'])->all();

// 急切載入帶條件
$customers = Customer::find()->with([
    'orders' => function($q) { $q->where(['status' => 'paid'])->orderBy('id DESC'); }
])->all();

反向關聯與關聯建立/刪除

$order = new Order();
$order->customer_id = $customer->id;
$order->save();

// 或使用 link() 自動處理外鍵
$order = new Order(['amount' => 100]);
$customer->link('orders', $order); // 自動填 customer_id 並儲存

// 解除關聯 (不刪除 Order,只清外鍵)
$customer->unlink('orders', $order, true); // true = 刪除關聯記錄 (中間表才用)

// 多對多 link
$order->link('items', $item); // 自動寫入 order_item

效能優化檢查清單

問題症狀解決方案
N+1 查詢迴圈中存取關聯屬性使用 with() 急切載入
重複查詢同一請求多次存取同關聯屬性語法自動快取;跨請求用 Redis 快取結果
過度 JOINjoinWith() 導致巨大結果集改用 with() 分開查詢,或只 SELECT 必要欄位
缺少索引關聯欄位 (FK) 無索引遷移檔加 $this->createIndex('idx_customer_id', 'order', 'customer_id')
大量資料載入all() 記憶體爆炸用 each() 批次處理、cursor() 游標、分頁

總結

Yii2 ActiveRecord 關聯查詢核心在於:正確宣告 hasOne/hasMany、善用 with() 急切載入、理解屬性語法快取機制、中間表用 viaTable/via、動態條件用方法語法。配合索引優化、必要時降級用 DAO/SQL,即能兼顧開發效率與執行效能。

相關專題:《Yii 框架入門及常用技巧總結》、《PHP 優秀開發框架總結》。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved