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

PHP 無限分類遞歸查詢:獲取指定分類下所有「葉子節點」ID 實現商品精準搜尋

     編輯:PHP 相關知識

場景痛點

電商系統常見需求:用戶在前台選擇一個大分類(如「所有商品」或「國產商品」),後台需一次性查出該分類及其所有**子孫分類**下的商品。但資料表設計中,商品只關聯到**最底層無子分類的葉子節點**(即 `goods.typeid = category.id` 且該 `id` 在 `category` 表中沒有對應的 `pid`)。若直接用 `WHERE typeid = 1` 只會查到直接掛在 ID=1 下的商品(通常為零),必須遞歸拿到所有葉子節點 ID,再用 `WHERE typeid IN (...)` 查詢。

資料結構範例

idpidpathcategoryname
100所有商品
210-1國產商品
420-1-2國產水貨
520-1-2國產行貨
310-1進口商品
630-1-3進口水貨
73進口行貨
810-1測試類
910-1雜貨專區

期望結果:

  • 傳入 1 → 返回 4,5,6,7,8,9(所有葉子節點)
  • 傳入 2 → 返回 4,5
  • 傳入 4 → 返回 4(自身即葉子)

核心遞歸函數

<?php
/**
 * 遞歸獲取指定分類下所有「無子分類」的葉子節點 ID
 * @param int    $id           起始分類 ID
 * @param string $table        分類表名,預設 category
 * @param PDO    $pdo          資料庫連接(建議注入 PDO 實例)
 * @return string 以逗號分隔的 ID 字串,如 "4,5,6,7,8,9"
 */
function get_leaf_category_ids(int $id, string $table = 'category', PDO $pdo): string {
    // 1. 查詢直接子分類
    $stmt = $pdo->prepare("SELECT id FROM {$table} WHERE pid = ?");
    $stmt->execute([$id]);
    $children = $stmt->fetchAll(PDO::FETCH_COLUMN);

    // 2. 無子分類 → 當前 ID 即葉子節點
    if (empty($children)) {
        return (string)$id;
    }

    // 3. 有子分類 → 遞歸處理每個子節點,合併結果
    $leafIds = [];
    foreach ($children as $childId) {
        $leafIds[] = get_leaf_category_ids($childId, $table, $pdo);
    }
    return implode(',', $leafIds);
}

// 使用示例
$pdo = new PDO('mysql:host=localhost;dbname=shop;charset=utf8mb4', 'user', 'pass', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

echo get_leaf_category_ids(1, 'category', $pdo); // 輸出: 4,5,6,7,8,9
echo get_leaf_category_ids(2, 'category', $pdo); // 輸出: 4,5
echo get_leaf_category_ids(4, 'category', $pdo); // 輸出: 4

// 直接用於 SQL 查詢商品
$leafIds = get_leaf_category_ids(1, 'category', $pdo);
$sql = "SELECT * FROM goods WHERE typeid IN ({$leafIds})";
$goods = $pdo->query($sql)->fetchAll();
?>

關鍵點解析

  1. 終止條件:當某分類在 `pid` 欄位下查無資料時,判定為葉子節點,直接返回自身 ID。
  2. 遞歸合併:有子分類時,對每個子分類遞歸呼叫,將返回的字串用逗號拼接,最終形成 IN (...) 可直接使用的 ID 列表。
  3. 資源注入:函數接收 PDO 實例而非在函數內建立連接,符合依賴倒置原則,便於單元測試與連線複用。
  4. 防注入:使用預處理語句綁定 pid 參數。

效能優化建議(大數據量時)

  • 路徑法:利用 path 欄位(如 0-1-2),查詢 WHERE path LIKE '0-1-%' 可一次取出所有後代,再 PHP 端過濾葉子節點,減少遞歸查詢次數。
  • 閉包表:額外維護一張 category_closure 表記錄所有祖先-後代關係,查詢極快,寫入稍複雜。
  • 快取:分類結構變動較少,可將「分類 ID → 葉子 ID 列表」映射快取到 Redis,讀取時直接獲取。

擴展:獲取所有後代 ID(含非葉子)

function get_all_descendant_ids(int $id, string $table, PDO $pdo): array {
    $stmt = $pdo->prepare("SELECT id FROM {$table} WHERE pid = ?");
    $stmt->execute([$id]);
    $ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
    foreach ($ids as $childId) {
        $ids = array_merge($ids, get_all_descendant_ids($childId, $table, $pdo));
    }
    return $ids;
}

根據業務靈活選擇「僅葉子」或「所有後代」策略。

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