Melewati array ke kueri menggunakan klausa WHERE


314

Diberikan array id $galleries = array(1,2,5)Saya ingin memiliki query SQL yang menggunakan nilai-nilai array di klausa WHERE-nya seperti:

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

Bagaimana saya bisa menghasilkan string kueri ini untuk digunakan dengan MySQL?



5
@trante yang ini adalah yang tertua (2009).
Fabián

Apakah ada solusi serupa untuk masalah seperti SELECT * FROM TABLE WHERE NAME LIKE ('ABC%' atau 'ABD%' ATAU ..)
Eugine Joseph

Jawaban:


332

AWAS! Jawaban ini mengandung kerentanan injeksi SQL parah . JANGAN gunakan contoh kode seperti yang disajikan di sini, tanpa memastikan bahwa input eksternal dibersihkan.

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

7
Pengidentifikasi masih berupa daftar yang dikutip, jadi muncul sebagai "WHERE id IN ('1,2,3,4')", misalnya. Anda perlu mengutip setiap pengenal secara terpisah, atau membuang kutipan di dalam tanda kurung.
Rob

22
Saya hanya menambahkan peringatan yang $galleriesharus divalidasi input sebelum pernyataan ini! Pernyataan yang disiapkan tidak dapat menangani array AFAIK, jadi jika Anda terbiasa dengan variabel terikat, Anda dapat dengan mudah membuat injeksi SQL menjadi mungkin di sini.
leemes

3
untuk pemula ke PHP seperti saya, dapatkah seseorang menjelaskan, atau mengarahkan saya ke sumber daya untuk menjelaskan, mengapa ini rentan terhadap injeksi dan bagaimana ini harus dilakukan dengan benar untuk mencegahnya? Bagaimana jika daftar ID dihasilkan dari kueri segera sebelum kueri berikutnya dijalankan, apakah itu masih berbahaya?
ministe2003

3
@ ministe2003 Bayangkan jika $galleriesmemiliki nilai berikut: array('1); SELECT password FROM users;/*'). Jika Anda tidak membersihkannya, kueri akan membaca SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Ubah nama tabel & kolom menjadi sesuatu yang Anda miliki di database Anda dan coba kueri itu, periksa hasilnya. Anda akan menemukan daftar kata sandi sebagai hasil, bukan daftar galeri. Bergantung pada bagaimana data di-output, atau apa yang dilakukan skrip dengan berbagai data yang tidak terduga, yang mungkin membuat output ke tampilan publik ... aduh.
Chris Baker

18
Untuk pertanyaan yang diajukan, itu adalah jawaban yang benar-benar valid dan aman. Siapa pun yang mengeluh itu tidak aman - bagaimana dengan kesepakatan yang saya setel kode khusus ini dengan yang $galleriesdisediakan dalam pertanyaan dan Anda mengeksploitasinya menggunakan "kerentanan injeksi sql" yang disebutkan di atas. Jika Anda tidak bisa - Anda membayar saya USD200. Bagaimana tentang itu?
zerkms

307

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

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

Penjelasan:

Gunakan SQL IN() operator untuk memeriksa apakah ada nilai dalam daftar yang diberikan.

Secara umum terlihat seperti ini:

expr IN (value,...)

Kita dapat membangun ekspresi untuk ditempatkan di dalam ()dari array kita. Perhatikan bahwa harus ada setidaknya satu nilai di dalam tanda kurung atau MySQL akan mengembalikan kesalahan; ini sama dengan memastikan bahwa array input kami memiliki setidaknya satu nilai. Untuk membantu mencegah serangan injeksi SQL, pertama-tama buat a ?untuk setiap item input untuk membuat kueri parameter. Di sini saya berasumsi bahwa array yang berisi id Anda disebut $ids:

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

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

Diberikan array input dari tiga item $selectakan terlihat seperti:

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

Sekali lagi perhatikan bahwa ada ?untuk setiap item dalam larik input. Kemudian kita akan menggunakan PDO atau MySQLi untuk menyiapkan dan mengeksekusi kueri seperti disebutkan di atas.

Menggunakan IN()operator dengan string

Mudah untuk mengubah antara string dan integer karena parameter yang terikat. Untuk PDO tidak diperlukan perubahan; untuk perubahan MySQLi str_repeat('i',menjadistr_repeat('s', jika Anda perlu memeriksa string.

[1]: Saya telah menghilangkan beberapa kesalahan saat memeriksa singkatnya. Anda perlu memeriksa kesalahan yang biasa untuk setiap metode database (atau mengatur driver DB Anda untuk membuang pengecualian).

[2]: Membutuhkan PHP 5.6 atau lebih tinggi. Sekali lagi saya telah menghilangkan beberapa kesalahan memeriksa singkatnya.


7
Apa yang dilakukan ... $ id? Saya mendapatkan "kesalahan sintaks, tak terduga '.'".
Marcel

Saya melihat mereka, saya menggunakan MySQLi dan saya memiliki php 5,6
Marcel

1
Jika Anda merujuk $statement->bind_param(str_repeat('i', count($ids)), ...$ids);maka ...sedang memperluas id dari array ke beberapa parameter. Jika Anda merujuk expr IN (value,...)maka itu hanya berarti bahwa bisa ada lebih banyak nilai misalnya WHERE id IN (1, 3, 4). Hanya perlu ada setidaknya satu.
Levi Morrison

1
Saya bingung apa <<< tapi saya menemukan referensi: php.net/manual/en/…
Tsangares

1
Juga, inilah rujukan untuk ...: wiki.php.net/rfc/argument_unpacking
Tsangares

58

int:

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

string:

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

1
Kenapa '\' ?? Tolong beritahu saya
zloctb

29

Dengan asumsi Anda membersihkan dengan benar input Anda sebelumnya ...

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

Kemudian sesuaikan kueri Anda:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Nilai penawaran tepat tergantung pada dataset Anda.


Saya mencoba apa yang Anda usulkan tetapi hanya mengambil nilai kunci pertama. Saya tahu itu tidak masuk akal, tetapi jika saya melakukannya menggunakan contoh user542568, hal terkutuk bekerja.
Samuel Ramzan


9

Sebagai jawaban Flavius ​​Stef , Anda dapat menggunakan intval()untuk memastikan semua idnilai int:

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

7

Untuk MySQLi dengan fungsi pelarian:

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

Untuk PDO dengan pernyataan yang disiapkan:

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

ini bagus, pendek dan menghindari kerentanan penyisipan kode! +1
Stephan Richter

MySQLi juga sudah menyiapkan pernyataan. Jangan luput dari input Anda, ini berpotensi masih rentan terhadap injeksi SQL.
Dharman

6

Kita harus merawat kerentanan injeksi SQL dan kondisi kosong . Saya akan menangani keduanya seperti di bawah ini.

Untuk array numerik murni, gunakan konversi jenis yang sesuai yaitu intvalatau floatvalatau di doublevalatas setiap elemen. Untuk tipe string mysqli_real_escape_string()yang juga dapat diterapkan ke nilai numerik jika diinginkan. MySQL memungkinkan angka dan varian tanggal sebagai string .

Untuk menghindari nilai-nilai sebelum meneruskan ke kueri, buat fungsi yang mirip dengan:

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

Fungsi seperti itu kemungkinan besar sudah tersedia untuk Anda di aplikasi Anda, atau mungkin Anda sudah membuatnya.

Sanitasi array string seperti:

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

Array angka dapat dibersihkan dengan menggunakan intvalatau floatvalatau doublevalsebaliknya sesuai:

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

Kemudian akhirnya membangun kondisi permintaan

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

atau

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

Karena kadang-kadang array juga bisa kosong, $galleries = array();karena itu kita harus mencatat bahwa IN ()tidak mengizinkan daftar kosong. Satu juga bisa menggunakan OR, tetapi masalahnya tetap. Jadi cek di atas count($values),, adalah untuk memastikan hal yang sama.

Dan tambahkan ke permintaan terakhir:

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

TIP : Jika Anda ingin menampilkan semua rekaman (tanpa pemfilteran) jika array kosong alih-alih menyembunyikan semua baris, cukup ganti 0 dengan 1 di bagian salah ternary.


Untuk membuat solusi saya satu-liner (dan jelek) , kalau-kalau ada yang perlu:$query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
Izhar Aazmi

5

Lebih aman

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

5

SafeMySQL library Kolonel Shrapnel untuk PHP menyediakan placeholder yang diisyaratkan tipe dalam kueri parametris, dan menyertakan beberapa placeholder yang nyaman untuk bekerja dengan array. Itu?a placeholder memperluas keluar array ke daftar dipisahkan koma string lolos *.

Sebagai contoh:

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

* Perhatikan bahwa karena MySQL melakukan paksaan tipe otomatis, tidak masalah bahwa SafeMySQL akan mengonversi id di atas menjadi string - Anda masih akan mendapatkan hasil yang benar.


4

Kita dapat menggunakan klausa "WHERE id IN" ini jika kita memfilter array input dengan benar. Sesuatu seperti ini:

$galleries = array();

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

Seperti contoh di bawah ini:masukkan deskripsi gambar di sini

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

Yaitu sekarang Anda harus menggunakan aman $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";


@ levi-morrison memposting solusi yang jauh lebih baik untuk ini.
Supratim Roy

4

Anda mungkin memiliki meja texts (T_ID (int), T_TEXT (text))dan mejatest (id (int), var (varchar(255)))

Dalam insert into test values (1, '1,2,3') ;output kehendak baris berikut dari teks-teks meja tempat 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

Dengan cara ini Anda dapat mengelola relasi basis data n2m sederhana tanpa tabel tambahan dan hanya menggunakan SQL tanpa perlu menggunakan PHP atau bahasa pemrograman lain.


3

Lebih banyak contoh:

$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

Selain menggunakan kueri IN, Anda memiliki dua opsi untuk melakukannya seperti dalam kueri IN ada risiko kerentanan injeksi SQL. Anda dapat menggunakan perulangan untuk mendapatkan data yang tepat yang Anda inginkan atau Anda dapat menggunakan kueri dengan huruf ATAU

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

Cara aman tanpa PDO:

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

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsKeluarkan $idsvariabel ke array
  • array_map Ubah semua nilai array menjadi bilangan bulat
  • array_unique Hapus nilai berulang
  • array_filter Hapus nilai nol
  • implode Gabungkan semua nilai ke seleksi IN

1

Karena pertanyaan awal terkait dengan array angka dan saya menggunakan array string saya tidak bisa membuat contoh yang diberikan berfungsi.

Saya menemukan bahwa setiap string perlu diringkas dalam tanda kutip tunggal untuk bekerja dengan IN()fungsi.

Ini solusinya

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

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

Seperti yang Anda lihat, fungsi pertama membungkus masing-masing variabel array single quotes (\')dan kemudian mengimplementasikan array.

CATATAN: $statustidak memiliki tanda kutip tunggal dalam pernyataan SQL.

Mungkin ada cara yang lebih baik untuk menambahkan tanda kutip tetapi ini berhasil.


Atau$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo

1
Ini rentan injeksi.
Mark Amery

Di mana melarikan diri dari string? Misalnya 'di dalam string? Injeksi SQL rentan. Gunakan PDO :: quote atau mysqli_real_escape_string.
18C

1

Di bawah ini adalah metode yang saya gunakan, menggunakan PDO dengan placeholder bernama untuk data lainnya. Untuk mengatasi injeksi SQL, saya memfilter array untuk menerima hanya nilai yang integer dan menolak yang lainnya.

$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

Metode dasar untuk mencegah injeksi SQL adalah:

  • Gunakan pernyataan yang disiapkan dan pertanyaan parameter
  • Melarikan karakter khusus dalam variabel tidak aman Anda

Menggunakan pernyataan yang disiapkan dan kueri parameterisasi dianggap praktik yang lebih baik, tetapi jika Anda memilih metode karakter melarikan diri maka Anda dapat mencoba contoh saya di bawah ini.

Anda dapat membuat kueri dengan menggunakan array_mapuntuk menambahkan satu kutipan ke setiap elemen di $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 yang dihasilkan akan:

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

Catatan: mysql_real_escape_string , seperti yang dijelaskan dalam dokumentasinya di sini , tidak digunakan lagi dalam PHP 5.5.0, dan dihapus dalam PHP 7.0.0. Sebaliknya, ekstensi MySQLi atau PDO_MySQL harus digunakan. Lihat juga MySQL: memilih panduan API dan FAQ terkait untuk informasi lebih lanjut. Alternatif untuk fungsi ini meliputi:

  • mysqli_real_escape_string ()

  • PDO :: quote ()


4
Ini tidak hanya menambahkan sesuatu yang baru dibandingkan dengan jawaban lain, tetapi juga rentan terhadap injeksi, meskipun jawaban yang diterima telah memiliki peringatan tentang injeksi SQL yang diposting di bawahnya selama bertahun-tahun.
Mark Amery
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.