Передача массива в запрос с использованием предложения WHERE

314

Учитывая массив идентификаторов, $galleries = array(1,2,5)я хочу иметь запрос SQL, который использует значения массива в своем предложении WHERE, например:

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

Как я могу сгенерировать эту строку запроса для использования с MySQL?

Braiam
источник
5
@trante этот самый старый (2009).
Фабиан
Есть ли подобное решение проблемы, например, SELECT * FROM TABLE, ГДЕ ИМЯ НРАВИТСЯ ('ABC%' или 'ABD%' ИЛИ ​​..)
Юджин Джозеф,

Ответы:

332

BEWARE! Этот ответ содержит серьезную уязвимость внедрения 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 долларов США. Как насчет этого?
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-инъекций, сначала ?создайте для каждого элемента ввода a для создания параметризованного запроса. Здесь я предполагаю, что массив, содержащий ваши идентификаторы, называется $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
Что делает ... $ идентификаторы? Я получаю «Синтаксическая ошибка, неожиданная».
Марсель
Я вижу их, я использую 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

Интс:

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

строки:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
user542568
источник
1
Почему '\' ?? Пожалуйста, скажите мне
zloctb
29

Предполагая, что вы правильно санируете свои данные заранее ...

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

Затем просто настройте ваш запрос:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Укажите значения соответственно в зависимости от вашего набора данных.

AvatarKava
источник
Я попробовал то, что вы предлагаете, но он просто получил значение первого ключа. Я знаю, что это не имеет смысла, но если я сделаю это на примере user542568, проклятая вещь сработает.
Самуэль Рамзан
12

Использование:

select id from galleries where id in (1, 2, 5);

Простой for eachцикл будет работать.

Путь Flavius ​​/ AvatarKava лучше, но убедитесь, что ни одно из значений массива не содержит запятых.

Мэтью Флэшен
источник
9

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

$ids = join(',', array_map('intval', $galleries));  
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";
Ван-Дуйе Ле
источник
7

Для MySQLi с функцией escape:

$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);
artoodetoo
источник
это хорошо, коротко и позволяет избежать уязвимости вставки кода! +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 полковника Шрапнеля предоставляет заполненные подсказки типа в своих параметризованных запросах и включает пару удобных заполнителей для работы с массивами. ?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 или какого-либо другого языка программирования.

SERJOU
источник
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
Lito
источник
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.

Существует, вероятно, более хороший способ добавить цитаты, но это работает.

RJaus
источник
Или$filter = "'" . implode("','",$status) . "'";
Алехандро Саламанка Мазуэло
1
Это уязвимо для инъекций.
Марк Амери
Где экранирование строк? Например, 'внутри строки? 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);
kojow7
источник
-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 и соответствующие часто задаваемые вопросы для получения дополнительной информации. Альтернативы этой функции включают в себя:

  • mysqli_real_escape_string ()

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

Дэвид
источник
4
Это не только не добавляет ничего нового по сравнению с другими ответами, но и уязвимо для инъекций, несмотря на то, что в принятом ответе уже много лет размещаются предупреждения о внедрении SQL.
Марк Амери