Truyền một mảng cho truy vấn bằng mệnh đề WHERE


314

Đưa ra một mảng id $galleries = array(1,2,5)tôi muốn có một truy vấn SQL sử dụng các giá trị của mảng trong mệnh đề WHERE của nó như:

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

Làm cách nào để tạo chuỗi truy vấn này để sử dụng với MySQL?



5
@trante cái này là cái cũ nhất (2009).
Fabián

Có giải pháp tương tự nào cho vấn đề như CHỌN * TỪ BẢNG Ở ĐÂU TÊN THÍCH ('ABC%' hoặc 'ABD%' HOẶC ..)
Eugine Joseph

Câu trả lời:


332

HÃY THỬ! Câu trả lời này chứa một lỗ hổng SQL SQL nghiêm trọng . KHÔNG sử dụng các mẫu mã như được trình bày ở đây, mà không đảm bảo rằng bất kỳ đầu vào bên ngoài nào được khử trùng.

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

7
Các định danh vẫn là một danh sách được trích dẫn, vì vậy, nó xuất hiện dưới dạng "WHERE id IN ('1,2,3,4')", chẳng hạn. Bạn cần trích dẫn riêng từng mã định danh hoặc nếu không thì bỏ dấu ngoặc kép bên trong dấu ngoặc đơn.
Cướp

22
Tôi chỉ cần thêm cảnh báo $galleriescần được xác thực trước khi tuyên bố này! Các câu lệnh được chuẩn bị không thể xử lý các mảng AFAIK, vì vậy nếu bạn đã quen với các biến bị ràng buộc, bạn có thể dễ dàng thực hiện phép tiêm SQL ở đây.
leeme

3
đối với những người mới sử dụng PHP như tôi, ai đó có thể giải thích hoặc chỉ cho tôi một tài nguyên để giải thích, tại sao điều này dễ bị tiêm và làm thế nào để thực hiện chính xác để ngăn chặn điều đó? Điều gì xảy ra nếu danh sách ID được tạo từ một truy vấn ngay trước khi truy vấn tiếp theo này được chạy, điều đó có còn nguy hiểm không?
ministe2003

3
@ ministe2003 Hãy tưởng tượng nếu $galleriescó giá trị sau : array('1); SELECT password FROM users;/*'). Nếu bạn không vệ sinh điều đó, truy vấn sẽ đọc SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Thay đổi tên bảng và cột thành tên bạn có trong cơ sở dữ liệu của bạn và thử truy vấn đó, kiểm tra kết quả. Bạn sẽ tìm thấy một danh sách các mật khẩu như kết quả của bạn, chứ không phải là một danh sách các phòng trưng bày. Tùy thuộc vào cách dữ liệu được xuất ra, hoặc tập lệnh làm gì với một mảng dữ liệu không mong muốn, điều đó có thể đưa đầu ra vào chế độ xem công khai ... ouch.
Chris Baker

18
Đối với câu hỏi được hỏi, đó là một câu trả lời hoàn toàn hợp lệ và an toàn. Bất cứ ai phàn nàn nó không an toàn - làm thế nào về một thỏa thuận mà tôi thiết lập mã đặc biệt này với $galleriesnhư được cung cấp trong câu hỏi và bạn khai thác nó bằng cách sử dụng "lỗ hổng tiêm sql" đã nói ở trên. Nếu bạn không thể - bạn trả cho tôi 200 USD. Thế còn cái đó?
zerkms

307

Sử dụng 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);

Sử dụng 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();

Giải trình:

Sử dụng IN()toán tử SQL để kiểm tra xem một giá trị có tồn tại trong một danh sách nhất định không.

Nói chung nó trông như thế này:

expr IN (value,...)

Chúng ta có thể xây dựng một biểu thức để đặt bên trong ()từ mảng của chúng ta. Lưu ý rằng phải có ít nhất một giá trị bên trong dấu ngoặc đơn hoặc MySQL sẽ trả về lỗi; điều này tương đương với việc đảm bảo rằng mảng đầu vào của chúng ta có ít nhất một giá trị. Để giúp ngăn chặn các cuộc tấn công tiêm nhiễm SQL, trước tiên hãy tạo một ?cho mỗi mục đầu vào để tạo một truy vấn được tham số hóa. Ở đây tôi giả sử rằng mảng chứa id của bạn được gọi là $ids:

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

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

Đưa ra một mảng đầu vào gồm ba mục $selectsẽ như sau:

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

Một lần nữa lưu ý rằng có một ?mục cho mỗi mục trong mảng đầu vào. Sau đó, chúng tôi sẽ sử dụng PDO hoặc MySQLi để chuẩn bị và thực hiện truy vấn như đã lưu ý ở trên.

Sử dụng IN()toán tử với chuỗi

Thật dễ dàng để thay đổi giữa các chuỗi và số nguyên vì các tham số ràng buộc. Đối với PDO, không có thay đổi cần thiết; để MySQLi thay đổi str_repeat('i',thành str_repeat('s',nếu bạn cần kiểm tra chuỗi.

[1]: Tôi đã bỏ qua một số lỗi kiểm tra lỗi. Bạn cần kiểm tra các lỗi thông thường cho từng phương thức cơ sở dữ liệu (hoặc đặt trình điều khiển DB của bạn để ném ngoại lệ).

[2]: Yêu cầu PHP 5.6 trở lên. Một lần nữa tôi đã bỏ qua một số lỗi kiểm tra cho ngắn gọn.


7
... Id $ làm gì? Tôi nhận được "lỗi cú pháp, không mong muốn '.'".
Marcel

Tôi thấy họ, tôi đang sử dụng MySQLi và tôi có php 5.6
Marcel

1
Nếu bạn đang đề cập đến $statement->bind_param(str_repeat('i', count($ids)), ...$ids);thì việc ...mở rộng id từ một mảng thành nhiều tham số. Nếu bạn đang đề cập đến expr IN (value,...)thì điều đó chỉ có nghĩa là có thể có nhiều giá trị hơn, vd WHERE id IN (1, 3, 4). Chỉ cần có ít nhất một.
Levi Morrison

1
Tôi đã nhầm lẫn gì <<< là nhưng tôi tìm thấy một tài liệu tham khảo: php.net/manual/en/...
Tsangares

1
Ngoài ra, đây là tài liệu tham khảo cho ...: wiki.php.net/rfc/argument_unpacking
Tsangares

58

số nguyên

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

dây:

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

1
Tại sao '\' ?? Xin vui lòng cho tôi biết
zloctb

29

Giả sử bạn vệ sinh đúng cách đầu vào của bạn trước ...

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

Sau đó, chỉ cần điều chỉnh truy vấn của bạn:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Trích dẫn giá trị phù hợp tùy thuộc vào tập dữ liệu của bạn.


Tôi đã thử những gì bạn đang đề xuất nhưng nó chỉ lấy giá trị khóa đầu tiên. Tôi biết nó không có ý nghĩa gì, nhưng nếu tôi làm điều đó bằng ví dụ user542568, thì thứ đáng nguyền rủa này hoạt động.
Samuel Ramzan



7

Đối với MySQLi có chức năng thoát:

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

Đối với PDO với tuyên bố đã chuẩn bị:

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

Điều này là tốt, ngắn và tránh lỗ hổng chèn mã! +1
Stephan Richter

MySQLi cũng đã chuẩn bị báo cáo. Không thoát khỏi đầu vào của bạn, điều này có khả năng vẫn dễ bị tấn công SQL.
Dharman

6

Chúng ta nên quan tâm đến các lỗ hổng SQL SQL và một điều kiện trống . Tôi sẽ xử lý cả hai như dưới đây.

Đối với một mảng số nguyên chất, sử dụng tức là loại chuyển đổi thích hợp intvalhoặc floatvalhoặc doublevaltrên mỗi phần tử. Đối với các loại chuỗi mysqli_real_escape_string()cũng có thể được áp dụng cho các giá trị số nếu bạn muốn. MySQL cho phép số lượng cũng như các biến thể ngày tháng dưới dạng chuỗi .

Để thoát một cách thích hợp các giá trị trước khi chuyển đến truy vấn, hãy tạo một hàm tương tự như:

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

Một chức năng như vậy rất có thể đã có sẵn cho bạn trong ứng dụng của bạn hoặc có thể bạn đã tạo một chức năng.

Vệ sinh mảng chuỗi như:

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

Một mảng số có thể được khử trùng bằng intvalhoặc floatvalhay doublevalthay vì như phù hợp:

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

Sau đó, cuối cùng xây dựng điều kiện truy vấn

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

hoặc là

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

Vì mảng đôi khi cũng có thể trống, $galleries = array();vì vậy chúng ta nên lưu ý rằng IN ()không cho phép danh sách trống. Người ta cũng có thể sử dụng ORthay thế, nhưng vấn đề vẫn còn. Vì vậy, kiểm tra trên count($values), là để đảm bảo như vậy.

Và thêm nó vào truy vấn cuối cùng:

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

MIPO : Nếu bạn muốn hiển thị tất cả các bản ghi (không lọc) trong trường hợp mảng trống thay vì ẩn tất cả các hàng, chỉ cần thay 0 bằng 1 trong phần sai của ternary.


Để biến giải pháp của tôi thành một lớp lót (và xấu nhất) , chỉ trong trường hợp ai đó cần phải:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi

5

An toàn hơn.

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

5

Thư viện SafeMyQuery dành cho PHP của Đại tá Shrapnel cung cấp các trình giữ chỗ được gợi ý kiểu trong các truy vấn được tham số hóa và bao gồm một vài trình giữ chỗ thuận tiện để làm việc với các mảng. Trình ?agiữ chỗ mở rộng một mảng thành một danh sách các chuỗi thoát được phân tách bằng dấu phẩy *.

Ví dụ:

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

* Lưu ý rằng vì MySQL thực hiện cưỡng chế kiểu tự động, không có vấn đề gì khi SafeMySQL sẽ chuyển đổi các id ở trên thành chuỗi - bạn vẫn sẽ nhận được kết quả chính xác.


4

Chúng ta có thể sử dụng mệnh đề "WHERE id IN" này nếu chúng ta lọc đúng mảng đầu vào. Một cái gì đó như thế này:

$galleries = array();

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

Giống như ví dụ dưới đây:nhập mô tả hình ảnh ở đây

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

Tức là bạn nên sử dụng một cách an toàn $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrison đăng một giải pháp tốt hơn cho việc này.
Supratim Roy

4

Bạn có thể có bàn texts (T_ID (int), T_TEXT (text))và bàntest (id (int), var (varchar(255)))

Trong insert into test values (1, '1,2,3') ;phần sau đây sẽ xuất các hàng từ văn bản bảng trong đó 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

Bằng cách này, bạn có thể quản lý một mối quan hệ cơ sở dữ liệu n2m đơn giản mà không cần thêm bảng và chỉ sử dụng SQL mà không cần sử dụng PHP hoặc một số ngôn ngữ lập trình khác.


3

Thêm một ví dụ:

$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

Bên cạnh việc sử dụng truy vấn IN, bạn có hai tùy chọn để thực hiện vì trong truy vấn IN có nguy cơ bị lỗ hổng SQL tiêm. Bạn có thể sử dụng vòng lặp để có được dữ liệu chính xác mà bạn muốn hoặc bạn có thể sử dụng truy vấn với trường hợp 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

Cách an toàn mà không cần PDO:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsĐúc $idsbiến mảng
  • array_map Chuyển đổi tất cả các giá trị mảng thành số nguyên
  • array_unique Xóa các giá trị lặp lại
  • array_filter Xóa giá trị 0
  • implode Tham gia tất cả các giá trị để chọn IN

1

Bởi vì câu hỏi ban đầu liên quan đến một mảng các số và tôi đang sử dụng một chuỗi các chuỗi, tôi không thể làm cho các ví dụ đã cho hoạt động.

Tôi thấy rằng mỗi chuỗi cần được gói gọn trong các dấu ngoặc đơn để làm việc với IN()hàm.

Đây là giải pháp của tôi

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

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

Như bạn có thể thấy hàm đầu tiên bao bọc từng biến mảng single quotes (\')và sau đó mã hóa mảng.

LƯU Ý: $statuskhông có dấu ngoặc đơn trong câu lệnh SQL.

Có lẽ có một cách tốt hơn để thêm các trích dẫn nhưng điều này hoạt động.


Hoặc$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo

1
Đây là tiêm dễ bị tổn thương.
Mark Amery

Trường hợp thoát khỏi chuỗi? Ví dụ 'bên trong chuỗi? SQL Injection dễ bị tổn thương. Sử dụng PDO :: quote hoặc mysqli_real_escape_opes.
18C

1

Dưới đây là phương pháp tôi đã sử dụng, sử dụng PDO với trình giữ chỗ được đặt tên cho dữ liệu khác. Để khắc phục SQL SQL, tôi lọc mảng chỉ chấp nhận các giá trị là số nguyên và từ chối tất cả các giá trị khác.

$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

Các phương pháp cơ bản để ngăn chặn SQL tiêm là:

  • Sử dụng các câu lệnh được chuẩn bị và các truy vấn tham số
  • Thoát các ký tự đặc biệt trong biến không an toàn của bạn

Sử dụng các câu lệnh đã chuẩn bị và truy vấn truy vấn được tham số hóa được coi là cách thực hành tốt hơn, nhưng nếu bạn chọn phương thức ký tự thoát thì bạn có thể thử ví dụ của tôi dưới đây.

Bạn có thể tạo các truy vấn bằng cách sử dụng array_mapđể thêm một trích dẫn cho mỗi thành phần trong $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 . ");";

Var $ sql được tạo sẽ là:

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

Lưu ý: mysql_real_escape_opes , như được mô tả trong tài liệu của nó ở đây , không được dùng trong PHP 5.5.0 và nó đã bị xóa trong PHP 7.0.0. Thay vào đó, nên sử dụng phần mở rộng MySQLi hoặc PDO_MyQuery. Xem thêm MySQL: chọn hướng dẫn API và Câu hỏi thường gặp liên quan để biết thêm thông tin. Các lựa chọn thay thế cho chức năng này bao gồm:

  • mysqli_real_escape_opes ()

  • PDO :: quote ()


4
Điều này không chỉ không có gì mới so với các câu trả lời khác, nó còn dễ bị tiêm, mặc dù câu trả lời được chấp nhận đã có cảnh báo về việc tiêm SQL được đăng trong đó trong nhiều năm.
Mark Amery
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.