ผ่านอาร์เรย์ไปยังแบบสอบถามโดยใช้คำสั่งย่อย WHERE


314

รับ array ของรหัส$galleries = array(1,2,5)ฉันต้องการมีแบบสอบถาม SQL ที่ใช้ค่าของอาร์เรย์ในส่วนคำสั่ง WHERE เช่น:

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

ฉันจะสร้างสตริงการสืบค้นนี้เพื่อใช้กับ MySQL ได้อย่างไร



5
@trante อันนี้เก่าที่สุด (2009)
Fabián

มีวิธีแก้ปัญหาที่คล้ายกันสำหรับปัญหาเช่น SELECT * จากตาราง WHERE NAME LIKE ('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')" ตัวอย่างเช่น คุณจำเป็นต้องอ้างอิงตัวระบุแต่ละตัวแยกกันหรือทำเครื่องหมายคำพูดไว้ในวงเล็บ
Rob

22
ฉันเพิ่งเพิ่มคำเตือนที่$galleriesควรตรวจสอบอินพุตก่อนที่จะมีคำสั่งนี้! คำสั่งที่เตรียมไว้ไม่สามารถจัดการกับอาร์เรย์ AFAIK ได้ดังนั้นหากคุณคุ้นเคยกับตัวแปรที่ผูกไว้คุณสามารถทำให้ SQL injection เป็นไปได้ที่นี่
leemes

3
สำหรับมือใหม่กับ PHP อย่างตัวฉันเองบางคนสามารถอธิบายได้หรือชี้ให้ฉันไปยังแหล่งข้อมูลเพื่ออธิบายว่าทำไมจึงมีแนวโน้มที่จะฉีดยา จะเกิดอะไรขึ้นหากมีการสร้างรายการรหัสจากแบบสอบถามทันทีก่อนที่จะมีการเรียกใช้แบบสอบถามต่อไปนี้จะยังคงเป็นอันตราย
ministe2003

3
@ ministe2003 ลองคิดดูหากมีค่าต่อไปนี้:$galleries หากคุณไม่ได้ฆ่าเชื้อที่แบบสอบถามจะอ่านarray('1); SELECT password FROM users;/*') SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*)เปลี่ยนชื่อตารางและคอลัมน์เป็นชื่อที่คุณมีในฐานข้อมูลของคุณแล้วลองใช้แบบสอบถามลองดูผลลัพธ์ คุณจะพบรายการรหัสผ่านที่เป็นผลลัพธ์แทนที่จะเป็นรายการแกลเลอรี่ ขึ้นอยู่กับว่าข้อมูลส่งออกหรือสิ่งที่สคริปต์ทำกับอาร์เรย์ของข้อมูลที่ไม่คาดคิดที่อาจได้รับการส่งออกไปยังมุมมองสาธารณะ ... ouch
Chris Baker

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 ก่อนอื่นให้สร้างรายการ?สำหรับแต่ละรายการเพื่อสร้างแบบสอบถามแบบกำหนดพารามิเตอร์ ที่นี่ฉันคิดว่าอาร์เรย์ที่มีรหัสของคุณถูกเรียกว่า$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]:ฉันละเว้นการตรวจสอบข้อผิดพลาดบางอย่างแล้ว คุณต้องตรวจสอบข้อผิดพลาดตามปกติสำหรับแต่ละวิธีฐานข้อมูล (หรือตั้งค่าไดรเวอร์ DB ของคุณให้มีข้อยกเว้น)

[2]:ต้องการ PHP 5.6 หรือสูงกว่า อีกครั้งฉันได้ละเว้นการตรวจสอบข้อผิดพลาดบางอย่างสั้น ๆ


7
... $ ids ทำอะไร ฉันได้รับ "ข้อผิดพลาดทางไวยากรณ์ที่ไม่คาดคิด". "
Marcel

ฉันเห็นพวกเขาฉันกำลังใช้ 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)อีกมากมายเช่น มีเพียงต้องการอย่างน้อยหนึ่ง
Levi Morrison

1
ฉันสับสนว่า <<< เป็นอย่างไร แต่ฉันพบข้อมูลอ้างอิง: php.net/manual/en/…
Tsangares

1
นอกจากนี้ที่นี่ยังมีการอ้างอิงสำหรับ...: wiki.php.net/rfc/argument_unpacking
Tsangares

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 ตัวอย่างสิ่งที่ถูกสาปนั้นก็ใช้งานได้
ซามูเอล 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
Dharman

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

5

ปลอดภัยมากขึ้น

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

5

ห้องสมุดSafeMySQLของ พ.อ. Shrapnel สำหรับ PHP ให้ตัวยึดตำแหน่งชนิดคำใบ้ในคิวรีแบบ Parametrised และรวมถึงตัวยึดตำแหน่งสองอันที่สะดวกสำหรับการทำงานกับอาร์เรย์ ?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 โพสต์คำตอบที่ดีกว่านี้
Supratim Roy

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 เข้าร่วมค่าทั้งหมดเพื่อเลือกใน

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 (\')และจากนั้น implodes อาร์เรย์

บันทึก: $statusไม่มีเครื่องหมายอัญประกาศเดี่ยวในคำสั่ง SQL

อาจมีวิธีที่ดีกว่าในการเพิ่มราคา แต่ก็ใช้งานได้


หรือ$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo

1
นี่คือความเสี่ยงการฉีด
Mark Amery

การหลบหนีของสตริงอยู่ที่ไหน ตัวอย่างเช่น'ภายในสตริง? SQL Injection มีช่องโหว่ ใช้ 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 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 ที่โพสต์ไว้ข้างใต้เป็นเวลาหลายปี
Mark Amery
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.