WHERE句を使用してクエリに配列を渡す


314

idの配列を考えると、次の$galleries = array(1,2,5)ようにWHERE句で配列の値を使用するSQLクエリが必要です。

SELECT *
FROM galleries
WHERE id = /* values of array $galleries... eg. (1 || 2 || 5) */

MySQLで使用するこのクエリ文字列を生成するにはどうすればよいですか?



5
@tranteこれは最も古い(2009)です。
ファビアン

SELECT * FROM TABLE WHERE NAME LIKE( 'ABC%' or 'ABD%' OR ..)
Eugine Joseph

回答:


332

注意してください!この回答には、深刻なSQLインジェクションの脆弱性が含まれています。外部入力が無害化されていることを確認せずに、ここに示すコードサンプルを使用しないでください。

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";

7
識別子は引用符で囲まれたリストであるため、たとえば「WHERE id IN( '1,2,3,4')」のようになります。各識別子を個別に引用するか、括弧内の引用を破棄する必要があります。
ロブ

22
$galleriesこのステートメントの前に入力検証が必要な警告を追加するだけです!準備済みステートメントは配列AFAIKを処理できないため、変数のバインドに慣れている場合は、ここでSQLインジェクションを簡単に実行できます。
leemes

3
私のようなPHPの初心者の場合、誰かが説明したり、説明するためのリソースを私に指摘したりできます。なぜこれが注入されやすいのか、そしてこれを防ぐためにこれを正しく行う方法は?IDのリストが次のクエリが実行される直前にクエリから生成された場合、それでも危険ですか?
ministe2003

3
@ ministe2003 $galleries次の値がある場合を想像してください:array('1); SELECT password FROM users;/*')。それをサニタイズしないと、クエリはを読み取りますSELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*)。テーブルと列の名前をデータベースにあるものに変更し、そのクエリを試し、結果を確認します。結果として、ギャラリーのリストではなく、パスワードのリストが表示されます。データがどのように出力されるか、またはスクリプトが予期しないデータの配列をどのように処理するかに応じて、パブリックビューに出力される可能性があります。
Chris Baker

18
尋ねられた質問にとって、それは完全に有効で安全な答えです。安全ではないと不平を言う人は誰でも-私がこの特定のコードを$galleries質問で提供されたとおりに設定し、前述の「SQLインジェクションの脆弱性」を使用してそれを悪用する取り引きについてはどうでしょうか。あなたができない場合-あなたは私にUSD200を支払います。どのようにそのことについて?
zerkms 2015

307

PDOの使用:[1]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $pdo->prepare($select);
$statement->execute($ids);

MySQLiの使用 [2]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $mysqli->prepare($select);
$statement->bind_param(str_repeat('i', count($ids)), ...$ids);
$statement->execute();
$result = $statement->get_result();

説明:

SQL IN()演算子を使用して、指定されたリストに値が存在するかどうかを確認します。

一般的には次のようになります。

expr IN (value,...)

()from配列の中に配置する式を作成できます。括弧内に少なくとも1つの値がなければなりません。そうでない場合、MySQLはエラーを返します。これは、入力配列に少なくとも1つの値があることを確認することと同じです。SQLインジェクション攻撃を防ぐには、まず?各入力項目のを生成して、パラメーター化されたクエリを作成します。ここでは、IDを含む配列が呼び出されていると仮定します$ids

$in = join(',', array_fill(0, count($ids), '?'));

$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;

3つの項目の入力配列があるとすると、次の$selectようになります。

SELECT *
FROM galleries
WHERE id IN (?, ?, ?)

繰り返し?ますが、入力配列には項目ごとにがあることに注意してください。次に、PDOまたはMySQLiを使用して、上記のようにクエリを準備および実行します。

IN()文字列での演算子の使用

パラメータがバインドされているため、文字列と整数を簡単に変更できます。PDOの場合、変更は必要ありません。MySQLiでは、文字列をチェックする必要str_repeat('i',があるstr_repeat('s',場合はに変更します。

[1]:簡潔にするためにいくつかのエラーチェックを省略しました。各データベースメソッドの通常のエラーを確認する(または、例外をスローするようにDBドライバーを設定する)必要があります。

[2]: PHP 5.6以降が必要です。繰り返しますが、簡潔にするためにいくつかのエラーチェックを省略しています。


7
... $ idsは何をしますか?「構文エラー、予期しない「。」」が表示されます。
マルセル

私はそれらを見て、私はMySQLiを使用していて、php 5.6を持っています
Marcel

1
参照している$statement->bind_param(str_repeat('i', count($ids)), ...$ids);場合、...は配列のIDを複数のパラメータに拡張しています。あなたが参照している場合、expr IN (value,...)それは単により多くの値がある可能性があることを意味しますWHERE id IN (1, 3, 4)。少なくとも1つあれば十分です。
Levi Morrison

1
私は何であったか<<<混乱が、私は参照がわかった。php.net/manual/en/...
Tsangares

1
また、ここに参照があり...ます:wiki.php.net/rfc/argument_unpacking
Tsangares

58

int:

$query = "SELECT * FROM `$table` WHERE `$column` IN(".implode(',',$array).")";

文字列:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";

1
なぜ '\' ??教えてください
zloctb

29

入力を事前に適切にサニタイズすると仮定します...

$matches = implode(',', $galleries);

次に、クエリを調整します。

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

データセットに応じて値を適切に引用します。


私はあなたが提案しているものを試しましたが、それは最初のキー値をフェッチしただけです。私はそれが意味をなさないことを知っていますが、user542568の例を使用してそれを行うと、のろわれたことが機能します。
Samuel Ramzan



7

以下のためのMySQLiを脱出機能付き:

$ids = array_map(function($a) use($mysqli) { 
    return is_string($a) ? "'".$mysqli->real_escape_string($a)."'" : $a;
  }, $ids);
$ids = join(',', $ids);  
$result = $mysqli->query("SELECT * FROM galleries WHERE id IN ($ids)");

準備済みステートメントを含むPDOの場合:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);

これは素晴らしく、短く、コード挿入の脆弱性を回避します!+1
ステファン・リヒター

MySQLiもステートメントを準備しました。入力をエスケープしないでください。これはまだSQLインジェクションに対して脆弱です。
ダーマン

6

SQLインジェクションの脆弱性と空の状態に対処する必要があります。どちらも以下のように扱います。

純粋な数値配列のために、適切な型変換すなわち使用intvalまたはfloatvalまたはdoubleval各要素の上に。mysqli_real_escape_string()必要に応じて数値にも適用できる文字列型の場合。MySQLでは、文字列として数値と日付のバリアントを使用できます

クエリに渡す前に値を適切にエスケープするには、次のような関数を作成します。

function escape($string)
{
    // Assuming $db is a link identifier returned by mysqli_connect() or mysqli_init()
    return mysqli_real_escape_string($db, $string);
}

そのような関数は、おそらくあなたのアプリケーションですでに利用可能であるか、多分あなたはすでにそれを作成したでしょう。

次のように文字列配列をサニタイズします。

$values = array_map('escape', $gallaries);

数値配列はintvalfloatval以下を使用して、またはdoubleval代わりに適切にサニタイズできます。

$values = array_map('intval', $gallaries);

次に、最後にクエリ条件を作成します

$where  = count($values) ? "`id` = '" . implode("' OR `id` = '", $values) . "'" : 0;

または

$where  = count($values) ? "`id` IN ('" . implode("', '", $values) . "')" : 0;

配列が空になることもある$galleries = array();のでIN ()、空のリストは許可されないことに注意してください。OR代わりに使用することもできますが、問題は残っています。したがって、上記のチェックはcount($values)、同じことを確認するためのものです。

そして、それを最後のクエリに追加します。

$query  = 'SELECT * FROM `galleries` WHERE ' . $where;

ヒント:すべての行を非表示にするのではなく、空の配列の場合にすべてのレコード(フィルタリングなし)を表示する場合は、3項のfalse部分の01に置き換えます


私のソリューションをワンライナー(そして醜いもの)にするために、誰かが必要な場合に備えて:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi

5

より安全。

$galleries = array(1,2,5);
array_walk($galleries , 'intval');
$ids = implode(',', $galleries);
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";

5

Col. ShrapnelのPHP用SafeMySQLライブラリは、パラメーター化されたクエリに型ヒントのプレースホルダーを提供し、配列を操作するための便利なプレースホルダーをいくつか備えています。?aプレースホルダは*エスケープした文字列をカンマで区切ったリストに、アレイを展開します。

例えば:

$someArray = [1, 2, 5];
$galleries = $db->getAll("SELECT * FROM galleries WHERE id IN (?a)", $someArray);

* MySQLは自動型強制を実行するため、SafeMySQLが上記のIDを文字列に変換するかどうかは関係ありません。正しい結果が得られます。


4

入力配列を適切にフィルタリングすれば、この「WHERE id IN」句を使用できます。このようなもの:

$galleries = array();

foreach ($_REQUEST['gallery_id'] as $key => $val) {
    $galleries[$key] = filter_var($val, FILTER_SANITIZE_NUMBER_INT);
}

以下の例のように:ここに画像の説明を入力してください

$galleryIds = implode(',', $galleries);

つまり今、あなたは安全に使うべきです $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrisonはこれに対するより良い解決策を投稿しました。
Supratim Roy 2015

4

あなたはテーブルtexts (T_ID (int), T_TEXT (text))とテーブルを持っているかもしれませんtest (id (int), var (varchar(255)))

ではinsert into test values (1, '1,2,3') ;どこのテーブルのテキストから、次の意志の出力行T_ID IN (1,2,3)

SELECT * FROM `texts` WHERE (SELECT FIND_IN_SET( T_ID, ( SELECT var FROM test WHERE id =1 ) ) AS tm) >0

この方法では、追加のテーブルなしで単純なn2mデータベース関係を管理でき、PHPや他のプログラミング言語を使用する必要なくSQLのみを使用できます。


3

より多くの例:

$galleryIds = [1, '2', 'Vitruvian Man'];
$ids = array_filter($galleryIds, function($n){return (is_numeric($n));});
$ids = implode(', ', $ids);

$sql = "SELECT * FROM galleries WHERE id IN ({$ids})";
// output: 'SELECT * FROM galleries WHERE id IN (1, 2)'

$statement = $pdo->prepare($sql);
$statement->execute();

2

INクエリを使用する以外に、2つのオプションがあります。INクエリでは、SQLインジェクションの脆弱性のリスクがあるためです。ループを使用して必要な正確なデータを取得するか、ORケースでクエリを使用できます

1. SELECT *
      FROM galleries WHERE id=1 or id=2 or id=5;


2. $ids = array(1, 2, 5);
   foreach ($ids as $id) {
      $data[] = SELECT *
                    FROM galleries WHERE id= $id;
   }

2

PDOなしの安全な方法:

$ids = array_filter(array_unique(array_map('intval', (array)$ids)));

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$ids$ids変数を配列にキャストします
  • array_map すべての配列値を整数に変換します
  • array_unique 繰り返される値を削除する
  • array_filter ゼロ値を削​​除
  • implode すべての値をIN選択に結合します

1

元の質問は数値の配列に関するものであり、文字列の配列を使用しているため、指定された例を機能させることができませんでした。

IN()関数を使用するには、各文字列を単一引用符で囲む必要があることがわかりました。

これが私の解決策です

foreach($status as $status_a) {
        $status_sql[] = '\''.$status_a.'\'';
    }
    $status = implode(',',$status_sql);

$sql = mysql_query("SELECT * FROM table WHERE id IN ($status)");

ご覧のとおり、最初の関数は各配列変数をラップしてsingle quotes (\')から、配列を内包します。

注:$statusSQLステートメントに単一引用符はありません。

引用符を追加するより良い方法があるでしょうが、これはうまくいきます。


または$filter = "'" . implode("','",$status) . "'";
アレハンドロサラマンカマズエロ2015

1
これはインジェクションの脆弱性です。
Mark Amery

文字列のエスケープはどこですか?たとえば'、文字列の中では?SQLインジェクションの脆弱性。PDO :: quoteまたはmysqli_real_escape_stringを使用します。
18C、

1

以下は、私が使用した方法で、PDOを他のデータの名前付きプレースホルダーと共に使用しています。SQLインジェクションを克服するために、整数である値のみを受け入れ、その他すべてを拒否するように配列をフィルタリングしています。

$owner_id = 123;
$galleries = array(1,2,5,'abc');

$good_galleries = array_filter($chapter_arr, 'is_numeric');

$sql = "SELECT * FROM galleries WHERE owner=:OWNER_ID AND id IN ($good_galleries)";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
    "OWNER_ID" => $owner_id,
));

$data = $stmt->fetchAll(PDO::FETCH_ASSOC);

-3

SQLインジェクションを防止する基本的な方法は次のとおりです。

  • 準備されたステートメントとパラメーター化されたクエリを使用する
  • 安全でない変数の特殊文字をエスケープする

準備されたステートメントとパラメーター化されたクエリクエリを使用することをお勧めしますが、エスケープ文字方式を選択した場合は、以下の例を試すことができます。

を使用array_mapしてクエリを生成するには、の各要素に単一引用符を追加します$galleries

$galleries = array(1,2,5);

$galleries_str = implode(', ',
                     array_map(function(&$item){
                                   return "'" .mysql_real_escape_string($item) . "'";
                               }, $galleries));

$sql = "SELECT * FROM gallery WHERE id IN (" . $galleries_str . ");";

生成される$ sql変数は次のようになります。

SELECT * FROM gallery WHERE id IN ('1', '2', '5');

注:mysql_real_escape_stringは、そのドキュメントで説明されいるように、PHP 5.5.0で廃止され、PHP 7.0.0で削除されました。代わりに、MySQLiまたはPDO_MySQL拡張機能を使用する必要があります。詳細については、MySQL:APIガイドの選択および関連するFAQも参照してください。この関数の代替には、次のものがあります。

  • mysqli_real_escape_string()

  • PDO :: quote()


4
受け入れられた回答にSQLインジェクションに関する警告が何年も投稿されていたにもかかわらず、これは他の回答と比較して新しいものを何も追加しないだけでなく、インジェクションの脆弱性があります。
Mark Amery
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.