Contents

PHP 產生動態sql技巧(IN)

由於 MySQL 的 IN 條件無法直接傳入陣列,在 PHP 中動態產生 SQL 的 IN 子句是個常見需求,但如果不注意 SQL Injection,可能帶來嚴重的安全風險。

問題背景

PDO 的 prepare 語句支援 ?:name 佔位符(placeholder),但這些佔位符代表「單一值」,無法直接綁定一個陣列到 IN (?) 中:

1
2
3
4
// ❌ 這樣不能正確運作
$ids = [1, 2, 3];
$stmt = $pdo->prepare("SELECT * FROM users WHERE id IN (?)");
$stmt->execute([$ids]);  // 只會傳入第一個值或報錯

解法一:字串拼接(注意 SQL Injection)

早期常見做法,用 implode 拼接成字串後插入 SQL:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
$array_uid = [
    ['uid' => 1],
    ['uid' => 2],
    ['uid' => 3],
];

$list = '';
if (is_array($array_uid)) {
    foreach ($array_uid as $user_val) {
        $list .= "'" . $user_val['uid'] . "',";
    }
    $list = substr($list, 0, -1);  // 移除最後一個逗號
    $filter = " AND u.uid NOT IN ($list) ";
}

$sql = "SELECT * FROM xxx WHERE 1=1 $filter";

安全警告:若 uid 來自使用者輸入,必須先做 intval()htmlspecialchars() 等過濾,否則有 SQL Injection 風險。

解法二:動態生成 Placeholder(推薦,安全)

根據陣列長度動態產生對應數量的 ?,再用 execute 傳入:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
$ids = [1, 2, 3, 4];

// 產生 "?,?,?,?"
$placeholders = implode(',', array_fill(0, count($ids), '?'));

$sql = "SELECT * FROM users WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($ids);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

這個方式完全透過 PDO 的 Prepared Statement 處理,資料不會直接插入 SQL 字串,不存在 SQL Injection 問題

解法三:具名 Placeholder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
$ids = [1, 2, 3];
$params = [];
$placeholders = [];

foreach ($ids as $i => $id) {
    $key = ':id' . $i;
    $placeholders[] = $key;
    $params[$key] = $id;
}

$sql = "SELECT * FROM users WHERE id IN (" . implode(',', $placeholders) . ")";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);

解法四:使用 ORM(Eloquent、Doctrine)

現代框架的 ORM 已內建處理 IN 條件,不需要手動生成 SQL:

1
2
3
4
5
// Laravel Eloquent
$users = User::whereIn('id', [1, 2, 3])->get();

// 對應到 NOT IN
$users = User::whereNotIn('id', [1, 2, 3])->get();
1
2
3
4
5
6
7
8
// Doctrine QueryBuilder
$users = $em->createQueryBuilder()
    ->select('u')
    ->from('User', 'u')
    ->where('u.id IN (:ids)')
    ->setParameter('ids', [1, 2, 3])
    ->getQuery()
    ->getResult();

解法比較

方式 安全性 複雜度 適用場景
字串拼接(無過濾) 不建議
字串拼接(有過濾) ⚠️ 僅限整數型 ID
動態 ? Placeholder 一般情境
ORM whereIn 最低 有使用 ORM 的專案