Передача масиву запиту за допомогою пункту WHERE


314

З огляду на масив id, $galleries = array(1,2,5)я хочу мати SQL-запит, який використовує значення масиву в його пункті WHERE, наприклад:

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

Як я можу генерувати цей рядок запитів для використання з MySQL?



5
@trante цей найдавніший (2009).
Фабіян

Чи є подібне рішення для такої проблеми, як SELECT * ОТ ТАБЛИЦІ, ЩО ПІДМИЛИ ІМ'Я ("ABC%" або "ABD%" АБО)
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, як я, хтось може пояснити, або вказати мені на ресурс, щоб пояснити, чому це схильне до ін'єкцій і як це потрібно зробити правильно, щоб цього не допустити? Що робити, якщо список ідентифікаторів формується із запиту безпосередньо перед запуском наступного запиту, це все ще небезпечно?
ministe2003

3
@ Ministe2003 Уявіть собі , якщо $galleriesмав таке значення: array('1); SELECT password FROM users;/*'). Якщо цього не санітувати, запит буде прочитано SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Змініть назви таблиці та стовпців на те, що є у вашій базі даних та спробуйте цей запит, ознайомтеся з результатами. Як результат ви знайдете список паролів, а не список галерей. Залежно від того, як виводяться дані або що скрипт робить з масивом несподіваних даних, які можуть отримати вихід у загальнодоступний перегляд ... ой.
Кріс Бейкер

18
На поставлене запитання це цілком коректна і безпечна відповідь. Хто не скаржиться, що це не безпечно - як щодо угоди, що я встановив цей конкретний код із тим $galleries, що передбачено у запитанні, і ви використовуєте його, використовуючи вищезгадану "вразливість sql для ін'єкцій". Якщо ви не можете - ви платите мені 200 USD. Як щодо цього?
zerkms

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();

Пояснення:

Використовуйте IN()оператор SQL, щоб перевірити, чи існує значення в заданому списку.

Загалом це виглядає приблизно так:

expr IN (value,...)

Ми можемо побудувати вираз для розміщення всередині ()нашого масиву. Зауважте, що в дужках має бути принаймні одне значення, або MySQL поверне помилку; це прирівнюється до того, щоб переконатися, що наш вхідний масив має принаймні одне значення. Щоб запобігти атакам ін'єкцій SQL, спочатку згенеруйте ?для кожного елемента введення, щоб створити параметризований запит. Тут я припускаю, що масив, що містить ваші ідентифікатори, називається $ids:

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

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

Даний вхідний масив з трьох елементів $selectбуде виглядати так:

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

Ще раз зауважте, що ?для кожного елемента у вхідному масиві є. Тоді ми будемо використовувати PDO або MySQLi для підготовки та виконання запиту, як зазначено вище.

Використання IN()оператора з рядками

Легко змінювати між рядками та цілими числами через зв'язані параметри. Для PDO змін не потрібно; для MySQLi зміни str_repeat('i',в , str_repeat('s',якщо вам потрібно перевірити рядки.

[1]: Я пропустив деяку перевірку помилок на стислість. Потрібно перевірити наявність звичайних помилок для кожного методу бази даних (або встановити драйвер БД для викидання виключень).

[2]: Потрібна PHP 5.6 або вище. Знову я пропустив деяку перевірку помилок на стислість.


7
Що робить ... $ ids? Я отримую "помилку синтаксису, несподіване". "".
Марсель

Я бачу їх, я використовую MySQLi і маю php 5.6
Marcel,

1
Якщо ви маєте на увазі, $statement->bind_param(str_repeat('i', count($ids)), ...$ids);то ...розширює ідентифікатор з масиву на кілька параметрів. Якщо ви посилаєтесь на це, expr IN (value,...)це просто означає, що може бути більше значень, наприклад WHERE id IN (1, 3, 4). Там просто має бути хоча б один.
Леві Моррісон

1
Мене збентежило, що таке <<<, але я знайшов посилання: php.net/manual/en/…
Цангарес

1
Також, ось посилання на ...: wiki.php.net/rfc/argument_unpacking
Цангарес

58

ints:

$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, проклята річ працює.
Самуель Рамзан


9

Як відповідь Флавія Стефа , ви можете використовувати, intval()щоб переконатись, що всі idзначення int:

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

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);

Числовий масив може бути продезінфікувати , використовуючи intvalабо floatvalабо 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;

Порада : Якщо ви хочете показати всі записи (без фільтрування) у випадку порожнього масиву замість того, щоб ховати всі рядки, просто замініть 0 на 1 у помилковій частині терміналу.


Щоб зробити моє рішення $query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
однолінійним

5

Безпечніше.

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

5

Бібліотека SafeMySQL полковника Шрапнеля для PHP надає підказки із заповненням типів у своїх параметризованих запитах та включає в себе пару зручних заповнювачів для роботи з масивами. The?aЗаповнювач розширюється з масиву в список розділених комами втікачів рядків *.

Наприклад:

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

* Зауважте, що оскільки MySQL виконує примус автоматичного типу, не має значення, що SafeMySQL перетворить ідентифікатори вище в рядки - ви все одно отримаєте правильний результат.


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 опублікував набагато краще рішення цього питання.
Супратім Рой

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 без додаткової таблиці та використанням лише SQL без необхідності використання PHP або іншої мови програмування.


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, у вас є два варіанти зробити це, оскільки в запиті IN існує ризик вразливості ін'єкцій SQL. Ви можете скористатися циклами, щоб отримати точні дані, які ви хочете, або ви можете використовувати запит у випадку АБО

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 (\')а потім імплодує масив.

ПРИМІТКА: $statusне має одиничних лапок у операторі SQL.

Мабуть, є приємніший спосіб додавання лапок, але це працює.


Або$filter = "'" . implode("','",$status) . "'";
Алехандро Саламанка Мазуело

1
Це вразливе до ін’єкцій.
Марк Амерді

Де рятується струн? Наприклад, 'всередині рядка? Вразлива ін'єкція SQL Використовуйте PDO :: quota або 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 var буде:

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 та відповідні відповіді для отримання додаткової інформації. Альтернативи цій функції включають:

  • mysqli_real_escape_string ()

  • PDO :: цитата ()


4
Це не тільки не додає нічого нового в порівнянні з іншими відповідями, він вразливий до ін’єкцій, незважаючи на те, що прийнята відповідь протягом багатьох років розміщувала попередження про ін'єкцію SQL.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.