ฉันสามารถผูกอาร์เรย์กับเงื่อนไข IN () ได้หรือไม่


562

ฉันอยากรู้ว่าเป็นไปได้ไหมที่จะผูกค่ากับตัวยึดตำแหน่งโดยใช้ PDO กรณีการใช้งานที่นี่กำลังพยายามส่งผ่านอาร์เรย์ของค่าเพื่อใช้กับIN()เงื่อนไข

ฉันต้องการทำสิ่งนี้:

<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

และมี PDO ผูกและอ้างอิงค่าทั้งหมดในอาร์เรย์

ในขณะที่ฉันทำ:

<?php
$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
foreach($ids as &$val)
    $val=$db->quote($val); //iterate through array and quote
$in = implode(',',$ids); //create comma separated list
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$in.')'
);
$stmt->execute();
?>

สิ่งใดบ้างที่จะได้งาน แต่เพียงแค่สงสัยว่ามีวิธีแก้ปัญหาในตัวฉันหายไปหรือไม่?


3
คำแนะนำที่สมบูรณ์เกี่ยวกับการผูกอาเรย์เข้ากับเงื่อนไข IN ()รวมถึงกรณีเมื่อคุณมีตัวยึดตำแหน่งอื่นในแบบสอบถาม
สามัญสำนึกของคุณ

คำถามถูกปิดเป็นซ้ำกับคำถามนี้ ฉันกลับการตั้งค่าสถานะที่ซ้ำกันเนื่องจากคำถามนี้เก่ากว่า 4 ปีมีจำนวนการดู 4 ครั้งจำนวนคำตอบ 3 ครั้งและ 12 เท่าของคะแนน ชัดเจนว่าเป็นเป้าหมายที่เหนือกว่า
miken32

ใครก็ตามที่ดูเรื่องนี้ในปี 2020 คุณสามารถลองgithub.com/morris/dopเพื่อดูได้
morris4

คำตอบ:


262

ฉันคิดว่า Soulmerge นั้นถูกต้อง คุณจะต้องสร้างข้อความค้นหา

<?php
$ids     = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids), '?'));

$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(' . $inQuery . ')'
);

// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

การแก้ไข:แดนคุณพูดถูก แก้ไขรหัส (ไม่ได้ทดสอบว่า)

แก้ไข:ทั้ง chris (ความคิดเห็น) และ somebodyisisrourouble แนะนำว่า foreach-loop ...

(...)
// bindvalue is 1-indexed, so $k+1
foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();

... อาจซ้ำซ้อนดังนั้นforeachวนและ$stmt->executeอาจถูกแทนที่ด้วยเพียง ...

<?php 
  (...)
  $stmt->execute($ids);
?>

(อีกครั้งฉันไม่ได้ทดสอบ)


7
นั่นเป็นทางออกที่น่าสนใจและในขณะที่ฉันชอบที่จะวนซ้ำรหัสและเรียก PDO :: quote () ฉันคิดว่าดัชนีของ '?' ตัวแทนจะไปสับสนถ้าตัวยึดอื่น ๆ เกิดขึ้นที่อื่นในแบบสอบถามก่อนใช่ไหม
Andru

5
ใช่นั่นจะเป็นปัญหา แต่ในกรณีนี้คุณสามารถสร้างพารามิเตอร์ที่มีชื่อแทน?
stefs

9
คำถามเก่า แต่น่าสังเกตว่าฉันเชื่อว่าเป็น$foreachและbindValue()ไม่จำเป็น - เพียงดำเนินการกับอาร์เรย์ เช่น:$stmt->execute($ids);
Chris

2
การสร้างตัวยึดตำแหน่งควรทำเช่นนี้str_repeat('?,', count($array) - 1). '?';
Xeoncross

8
เพียงคำแนะนำสำหรับผู้ที่ไม่ทราบคุณไม่สามารถผสมพารามิเตอร์ที่มีชื่อและที่ไม่มีชื่อ ดังนั้นหากคุณใช้พารามิเตอร์ที่มีชื่อในการค้นหาของคุณให้เปลี่ยนเป็น? และเพิ่มดัชนี bindValue ของคุณเพื่อให้ตรงกับตำแหน่งของ IN ด้วยทุกที่ที่สัมพันธ์กับของคุณ params
justinl

169

เพื่อสิ่งที่รวดเร็ว:

//$db = new PDO(...);
//$ids = array(...);

$qMarks = str_repeat('?,', count($ids) - 1) . '?';
$sth = $db->prepare("SELECT * FROM myTable WHERE id IN ($qMarks)");
$sth->execute($ids);

7
ยอดเยี่ยมฉันไม่คิดว่าจะใช้อาร์กิวเมนต์ input_parameters ในวิธีนี้ สำหรับผู้ที่เคียวรีที่มีพารามิเตอร์มากกว่ารายการ IN คุณสามารถใช้ array_unshift และ array_push เพื่อเพิ่มอาร์กิวเมนต์ที่จำเป็นลงในส่วนหน้าและส่วนท้ายของอาร์เรย์ นอกจากนี้ฉันชอบ $input_list = substr(str_repeat(',?', count($ids)), 1);
orca

7
str_repeat('?,', count($ids) - 1) . '?'นอกจากนี้คุณยังสามารถลอง การเรียกใช้ฟังก์ชันน้อยลงหนึ่งครั้ง
orca

5
@erfling นี่เป็นคำสั่งที่เตรียมไว้การฉีดจะมาจากที่ไหน ฉันยินดีเป็นอย่างยิ่งที่จะทำการแก้ไขใด ๆ หากคุณสามารถสำรองข้อมูลนั้นได้ด้วยหลักฐานจริงบางประการ
DonVaughn

5
@erfling ใช่ถูกต้องและผูก params เป็นสิ่งที่เรากำลังทำในตัวอย่างนี้โดยการส่งexecuteอาร์เรย์ของรหัส
DonVaughn

5
โอ้จริง ๆ อย่างใดพลาดความจริงที่ว่าคุณผ่านอาร์เรย์ สิ่งนี้ดูเหมือนจะปลอดภัยและเป็นคำตอบที่ดี ขอโทษด้วย.
erfling

46

จำเป็นต้องใช้INคำสั่งหรือไม่ ลองใช้FIND_IN_SETop

ตัวอย่างเช่นมีแบบสอบถามใน PDO เช่นนั้น

SELECT * FROM table WHERE FIND_IN_SET(id, :array)

จากนั้นคุณจะต้องผูกอาเรย์ของค่าที่มีเครื่องหมายคอมมาเหมือนกับอันนี้

$ids_string = implode(',', $array_of_smth); // WITHOUT WHITESPACES BEFORE AND AFTER THE COMMA
$stmt->bindParam('array', $ids_string);

และมันก็ทำ

UPD: ในขณะที่บางคนชี้ให้เห็นในความคิดเห็นคำตอบนี้มีบางประเด็นที่ควรจะอธิบายอย่างชัดเจน

  1. FIND_IN_SETไม่ได้ใช้ดัชนีในตารางและมันก็ยังไม่ได้ดำเนินการยัง - ดูบันทึกนี้ในการติดตามข้อมูล MySQL ข้อผิดพลาด ขอบคุณ @BillKarwin สำหรับการแจ้งเตือน
  2. คุณไม่สามารถใช้สตริงที่มีเครื่องหมายจุลภาคอยู่ภายในเป็นค่าของอาร์เรย์สำหรับการค้นหา มันเป็นไปไม่ได้ที่จะแยกสตริงดังกล่าวในทางที่ถูกต้องหลังจากที่implodeคุณใช้เครื่องหมายจุลภาคเป็นตัวคั่น ขอบคุณ @VaL สำหรับบันทึกย่อ

หากคุณไม่ต้องพึ่งพาดัชนีและไม่ใช้สตริงที่มีเครื่องหมายจุลภาคสำหรับการค้นหาโซลูชันของฉันจะง่ายขึ้นง่ายขึ้นและเร็วกว่าโซลูชันที่ระบุไว้ข้างต้น


25
IN () สามารถใช้ดัชนีและนับเป็นการสแกนแบบช่วง FIND_IN_SET () ไม่สามารถใช้ดัชนี
Bill Karwin

1
นั่นคือประเด็น ฉันไม่รู้สิ่งนี้ แต่วิธีการใด ๆ ไม่มีข้อกำหนดใด ๆ สำหรับการปฏิบัติงานในคำถาม สำหรับตารางที่ไม่ใหญ่มากจะดีกว่าและสะอาดกว่าคลาสที่แยกต่างหากเพื่อสร้างคิวรีที่มีตัวยึดจำนวนแตกต่างกัน
ทิม Tonkonogov

11
ใช่ แต่ใครบ้างที่มีโต๊ะที่ไม่ใหญ่โตในวันนี้ ;-)
Bill Karwin

3
ปัญหาอีกประการหนึ่งของวิธีนี้คือจะเกิดอะไรขึ้นถ้ามีสตริงที่มีเครื่องหมายจุลภาคอยู่ภายใน ตัวอย่าง... FIND_IN_SET(description,'simple,search')จะใช้งานได้ แต่FIND_IN_SET(description,'first value,text, with coma inside')จะล้มเหลว ดังนั้นฟังก์ชั่นจะค้นหา "first value", "text", "with coma inside" แทนสิ่งที่ต้องการ"first value", "text, with coma inside"
VaL

33

เนื่องจากฉันทำเคียวรีแบบไดนามิกจำนวนมากนี่เป็นฟังก์ชั่นตัวช่วยที่ง่ายมากที่ฉันทำ

public static function bindParamArray($prefix, $values, &$bindArray)
{
    $str = "";
    foreach($values as $index => $value){
        $str .= ":".$prefix.$index.",";
        $bindArray[$prefix.$index] = $value;
    }
    return rtrim($str,",");     
}

ใช้มันแบบนี้:

$bindString = helper::bindParamArray("id", $_GET['ids'], $bindArray);
$userConditions .= " AND users.id IN($bindString)";

ส่งคืนสตริง:id1,:id2,:id3และอัพเดตการ$bindArrayเชื่อมโยงที่คุณต้องการเมื่อถึงเวลาเรียกใช้คิวรีของคุณ ง่าย!


6
นี่เป็นวิธีที่ดีกว่าเนื่องจากจะไม่ทำลายการผูกพารามิเตอร์กฎ ปลอดภัยกว่าการใช้อินไลน์ SQL ตามที่เสนอโดยคนอื่น ๆ ที่นี่
Dimitar Darazhanski

1
awesomeness สง่า สมบูรณ์
Ricalsin

17

ทางออกจาก EvilRygy ไม่ได้ผลสำหรับฉัน ใน Postgres คุณสามารถแก้ไขปัญหาอื่นได้:


$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id = ANY (string_to_array(:an_array, ','))'
);
$stmt->bindParam(':an_array', implode(',', $ids));
$stmt->execute();

สิ่งนี้ใช้ไม่ได้: ERROR: operator does not exist: integer = text. อย่างน้อยคุณต้องเพิ่มการคัดเลือกนักแสดงอย่างชัดเจน
collimarco

17

วิธีที่สะอาดมากสำหรับ postgres ใช้ postgres-array ("{}"):

$ids = array(1,4,7,9,45);
$param = "{".implode(', ',$ids)."}";
$cmd = $db->prepare("SELECT * FROM table WHERE id = ANY (?)");
$result = $cmd->execute(array($param));

มันป้องกันการฉีด sql?
Fábio Zangirolami

1
@ FábioZangirolamiเป็น PDO ดังนั้นใช่
ESCOBAR

ใช่ PDO! หลังจาก 4 ปี คำตอบของคุณดีสำหรับฉันเรียบง่ายและมีประสิทธิภาพมาก ขอบคุณ!!!
Fábio Zangirolami

14

นี่คือทางออกของฉัน:

$total_items = count($array_of_items);
$question_marks = array_fill(0, $total_items, '?');
$sql = 'SELECT * FROM foo WHERE bar IN (' . implode(',', $question_marks ). ')';

$stmt = $dbh->prepare($sql);
$stmt->execute(array_values($array_of_items));

หมายเหตุการใช้ array_values วิธีนี้สามารถแก้ไขปัญหาการสั่งซื้อคีย์

ฉันกำลังผสานอาร์เรย์ของรหัสแล้วลบรายการที่ซ้ำกัน ฉันมีสิ่งที่ชอบ:

$ids = array(0 => 23, 1 => 47, 3 => 17);

และนั่นคือความล้มเหลว


นี่เป็นทางออกที่ยอดเยี่ยม มันไม่เหมือนกับการสร้างสตริงแบบสอบถามซึ่งใช้การเชื่อมโยงอย่างถูกต้อง ฉันขอแนะนำอย่างนี้
Lindylead

หมายเหตุ: การใช้โซลูชันของคุณคุณสามารถเพิ่มรายการลงในด้านหน้าหรือด้านหลังของอาร์เรย์เพื่อให้คุณสามารถรวมการผูกอื่น ๆ ได้ $original_array เพิ่มรายการก่อนอาร์เรย์เดิม: array_unshift($original_array, new_unrelated_item); เพิ่มรายการหลังอาร์เรย์เดิม: array_push($original_array, new_unrelated_item); เมื่อค่าถูกผูกไว้รายการใหม่ที่ไม่เกี่ยวข้องจะถูกวางในตำแหน่งที่ถูกต้อง สิ่งนี้ช่วยให้คุณสามารถผสมรายการ Array และ Non-array `
Lindylead

12

ฉันขยาย PDO เพื่อทำสิ่งที่คล้ายกับสิ่งที่สเตฟแนะนำและมันง่ายสำหรับฉันในระยะยาว:

class Array_Capable_PDO extends PDO {
    /**
     * Both prepare a statement and bind array values to it
     * @param string $statement mysql query with colon-prefixed tokens
     * @param array $arrays associatve array with string tokens as keys and integer-indexed data arrays as values 
     * @param array $driver_options see php documention
     * @return PDOStatement with given array values already bound 
     */
    public function prepare_with_arrays($statement, array $arrays, $driver_options = array()) {

        $replace_strings = array();
        $x = 0;
        foreach($arrays as $token => $data) {
            // just for testing...
            //// tokens should be legit
            //assert('is_string($token)');
            //assert('$token !== ""');
            //// a given token shouldn't appear more than once in the query
            //assert('substr_count($statement, $token) === 1');
            //// there should be an array of values for each token
            //assert('is_array($data)');
            //// empty data arrays aren't okay, they're a SQL syntax error
            //assert('count($data) > 0');

            // replace array tokens with a list of value tokens
            $replace_string_pieces = array();
            foreach($data as $y => $value) {
                //// the data arrays have to be integer-indexed
                //assert('is_int($y)');
                $replace_string_pieces[] = ":{$x}_{$y}";
            }
            $replace_strings[] = '('.implode(', ', $replace_string_pieces).')';
            $x++;
        }
        $statement = str_replace(array_keys($arrays), $replace_strings, $statement);
        $prepared_statement = $this->prepare($statement, $driver_options);

        // bind values to the value tokens
        $x = 0;
        foreach($arrays as $token => $data) {
            foreach($data as $y => $value) {
                $prepared_statement->bindValue(":{$x}_{$y}", $value);
            }
            $x++;
        }

        return $prepared_statement;
    }
}

คุณสามารถใช้สิ่งนี้:

$db_link = new Array_Capable_PDO($dsn, $username, $password);

$query = '
    SELECT     *
    FROM       test
    WHERE      field1 IN :array1
     OR        field2 IN :array2
     OR        field3 = :value
';

$pdo_query = $db_link->prepare_with_arrays(
    $query,
    array(
        ':array1' => array(1,2,3),
        ':array2' => array(7,8,9)
    )
);

$pdo_query->bindValue(':value', '10');

$pdo_query->execute();

1
ฉันได้กล่าวถึงส่วนแรกของความคิดเห็นของ Mark แล้ว แต่ในขณะที่เขาชี้ให้เห็นมันยังไม่ปลอดภัยหากโทเค็นทำนอง:arrayนั้นอยู่ในสตริงในข้อความค้นหา
Chris

4
หมายเหตุถึงผู้อ่านในอนาคตทั้งหมด: ไม่ควรใช้วิธีนี้ การยืนยันไม่ได้มีไว้สำหรับรหัสการผลิต
สามัญสำนึกของคุณ

1
YCS: ขอบคุณสำหรับข้อเสนอแนะสนใจในความคิดเห็นของคุณเกี่ยวกับวิธีการนอกเหนือจากความเหมาะสมของการยืนยัน
Chris

1
แนวคิดนี้ค่อนข้างเหมือนเดิม แต่ไม่มีการยืนยันและวิธีที่ตรงไปตรงมาและชัดเจนมากขึ้น - ไม่ใช่ข้อยกเว้นเพียงกรณีเดียว แต่เป็นวิธีทั่วไปในการสร้างแบบสอบถามทั้งหมด ตัวยึดตำแหน่งทุกตัวจะถูกทำเครื่องหมายด้วยประเภทของมัน มันทำให้การคาดเดา (เช่นif (is_array($data))เดียว) ไม่จำเป็น แต่ทำให้การประมวลผลข้อมูลแม่นยำยิ่งขึ้น
สามัญสำนึกของคุณ

1
ให้กับคนใด ๆ อ่านความคิดเห็นนี้ปัญหาดังกล่าวโดย @Your สามัญสำนึกได้รับการแก้ไขในการแก้ไข 4
user2428118

11

มองไปที่ PDO: ค่าคงที่ที่กำหนดไว้ล่วงหน้าไม่มี PDO :: PARAM_ARRAY ที่คุณต้องการตามที่ระบุไว้ในPDOStatement-> bindParam

บูล PDOStatement :: bindParam (พารามิเตอร์ผสม $, ผสม & ตัวแปร $ [, int $ data_type [, int $ length [, ผสม $ driver_options]]])

ดังนั้นฉันไม่คิดว่าจะทำได้


1
ฉันไม่รู้ว่ามันใช้การได้หรือเปล่า ฉันเดาว่าสตริงที่ถูก imploded จะถูกยกมา
Soulmerge

2
คุณถูกต้องราคาถูกหนีเพื่อที่จะไม่ทำงาน ฉันลบรหัสนั้นแล้ว

11

เมื่อคุณมีพารามิเตอร์อื่น ๆ คุณอาจทำสิ่งนี้:

$ids = array(1,2,3,7,8,9);
$db = new PDO(...);
$query = 'SELECT *
            FROM table
           WHERE X = :x
             AND id IN(';
$comma = '';
for($i=0; $i<count($ids); $i++){
  $query .= $comma.':p'.$i;       // :p0, :p1, ...
  $comma = ',';
}
$query .= ')';

$stmt = $db->prepare($query);
$stmt->bindValue(':x', 123);  // some value
for($i=0; $i<count($ids); $i++){
  $stmt->bindValue(':p'.$i, $ids[$i]);
}
$stmt->execute();

ขอบคุณสำหรับคำตอบที่ดี นี่เป็นเพียงสิ่งเดียวเท่านั้นที่สามารถใช้ได้จริงสำหรับฉัน อย่างไรก็ตามฉันเห็นข้อผิดพลาด 1 ข้อ ตัวแปร $ rs ควรเป็น $ stmt
Piet

11

สำหรับฉันแล้ว sexier solution คือการสร้างอาเรย์แบบเชื่อมโยงและใช้มัน

// A dirty array sent by user
$dirtyArray = ['Cecile', 'Gilles', 'Andre', 'Claude'];

// we construct an associative array like this
// [ ':name_0' => 'Cecile', ... , ':name_3' => 'Claude' ]
$params = array_combine(
    array_map(
        // construct param name according to array index
        function ($v) {return ":name_{$v}";},
        // get values of users
        array_keys($dirtyArray)
    ),
    $dirtyArray
);

// construct the query like `.. WHERE name IN ( :name_1, .. , :name_3 )`
$query = "SELECT * FROM user WHERE name IN( " . implode(",", array_keys($params)) . " )";
// here we go
$stmt  = $db->prepare($query);
$stmt->execute($params);

ยากที่จะแน่ใจได้โดยไม่ต้องลองในสถานการณ์จริง แต่ดูเหมือนดี + 1
Anant Singh --- ยังมีชีวิตอยู่ถึง

9

ฉันยังทราบว่าเธรดนี้เก่า แต่ฉันมีปัญหาที่ไม่เหมือนใครซึ่งในขณะที่การแปลงไดรเวอร์ mysql ที่เลิกใช้เร็ว ๆ นี้ไปเป็นไดรเวอร์ PDO ฉันต้องสร้างฟังก์ชันที่สามารถสร้างแบบไดนามิกทั้งพารามิเตอร์ปกติและ INs จากเดิม อาร์เรย์ param ดังนั้นฉันจึงสร้างสิ่งนี้อย่างรวดเร็ว:

/**
 * mysql::pdo_query('SELECT * FROM TBL_WHOOP WHERE type_of_whoop IN :param AND siz_of_whoop = :size', array(':param' => array(1,2,3), ':size' => 3))
 *
 * @param $query
 * @param $params
 */
function pdo_query($query, $params = array()){

    if(!$query)
        trigger_error('Could not query nothing');

    // Lets get our IN fields first
    $in_fields = array();
    foreach($params as $field => $value){
        if(is_array($value)){
            for($i=0,$size=sizeof($value);$i<$size;$i++)
                $in_array[] = $field.$i;

            $query = str_replace($field, "(".implode(',', $in_array).")", $query); // Lets replace the position in the query string with the full version
            $in_fields[$field] = $value; // Lets add this field to an array for use later
            unset($params[$field]); // Lets unset so we don't bind the param later down the line
        }
    }

    $query_obj = $this->pdo_link->prepare($query);
    $query_obj->setFetchMode(PDO::FETCH_ASSOC);

    // Now lets bind normal params.
    foreach($params as $field => $value) $query_obj->bindValue($field, $value);

    // Now lets bind the IN params
    foreach($in_fields as $field => $value){
        for($i=0,$size=sizeof($value);$i<$size;$i++)
            $query_obj->bindValue($field.$i, $value[$i]); // Both the named param index and this index are based off the array index which has not changed...hopefully
    }

    $query_obj->execute();

    if($query_obj->rowCount() <= 0)
        return null;

    return $query_obj;
}

มันยังคงไม่ได้ทดสอบ แต่ตรรกะนั้นน่าจะอยู่ที่นั่น

หวังว่ามันจะช่วยคนที่อยู่ในตำแหน่งเดียวกัน

แก้ไข: หลังจากการทดสอบบางอย่างที่ฉันค้นพบ:

  • PDO ไม่ชอบ '.' ในชื่อของพวกเขา (ซึ่งโง่ถ้าคุณถามฉัน)
  • bindParam เป็นฟังก์ชั่นที่ไม่ถูกต้อง bindValue เป็นฟังก์ชั่นที่เหมาะสม

แก้ไขโค้ดเป็นเวอร์ชันใช้งานได้


8

การแก้ไขเล็กน้อยเกี่ยวกับรหัสของ Schnalle

<?php
$ids     = array(1, 2, 3, 7, 8, 9);
$inQuery = implode(',', array_fill(0, count($ids)-1, '?'));

$db   = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN(' . $inQuery . ')'
);

foreach ($ids as $k => $id)
    $stmt->bindValue(($k+1), $id);

$stmt->execute();
?>

//implode(',', array_fill(0, count($ids)-1), '?')); 
//'?' this should be inside the array_fill
//$stmt->bindValue(($k+1), $in); 
// instead of $in, it should be $id

1
ฉันต้องลบ -1 หลังจากการนับ ($ id) เพื่อให้ทำงานได้สำหรับฉันหรือจะมีตัวยึดตำแหน่งหนึ่งหายไปเสมอ
Marcel Burkhard

7

คุณใช้ฐานข้อมูลใด ใน PostgreSQL ฉันชอบใช้ ANY (array) ดังนั้นเพื่อนำตัวอย่างของคุณกลับมาใช้ใหม่:

<?php
$ids=array(1,2,3,7,8,9);
$db = new PDO(...);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id = ANY (:an_array)'
);
$stmt->bindParam('an_array',$ids);
$stmt->execute();
?>

น่าเสียดายที่นี่ไม่สามารถพกพาได้

ในฐานข้อมูลอื่นคุณจะต้องสร้างเวทย์มนตร์ของคุณเองเมื่อคนอื่นพูดถึง คุณจะต้องใส่ตรรกะนั้นลงในคลาส / ฟังก์ชั่นเพื่อให้มันนำมาใช้ซ้ำได้ตลอดหลักสูตรของคุณ ดูความคิดเห็นในmysql_queryหน้าบน PHP.NET สำหรับความคิดเพิ่มเติมเกี่ยวกับเรื่องและตัวอย่างของสถานการณ์นี้


4

หลังจากผ่านปัญหาเดียวกันฉันไปหาทางออกที่ง่ายกว่า (แม้ว่าจะยังไม่หรูหราเท่าที่PDO::PARAM_ARRAYควร):

รับอาร์เรย์$ids = array(2, 4, 32):

$newparams = array();
foreach ($ids as $n => $val){ $newparams[] = ":id_$n"; }

try {
    $stmt = $conn->prepare("DELETE FROM $table WHERE ($table.id IN (" . implode(", ",$newparams). "))");
    foreach ($ids as $n => $val){
        $stmt->bindParam(":id_$n", intval($val), PDO::PARAM_INT);
    }
    $stmt->execute();

... และต่อไป

ดังนั้นหากคุณใช้อาร์เรย์ค่าผสมคุณจะต้องมีรหัสเพิ่มเติมเพื่อทดสอบค่าของคุณก่อนกำหนดประเภทพารามิเตอร์:

// inside second foreach..

$valuevar = (is_float($val) ? floatval($val) : is_int($val) ? intval($val) :  is_string($val) ? strval($val) : $val );
$stmt->bindParam(":id_$n", $valuevar, (is_int($val) ? PDO::PARAM_INT :  is_string($val) ? PDO::PARAM_STR : NULL ));

แต่ฉันยังไม่ได้ทดสอบอันนี้


4

ฉันรู้ว่าไม่มีความเป็นไปได้ที่จะผูกอาเรย์เข้ากับคำสั่ง PDO

แต่มีอยู่ 2 โซลูชันทั่วไป:

  1. ใช้ตัวยึดตำแหน่ง (?,?,?,?) หรือตัวยึดตำแหน่งที่ระบุชื่อ (: id1,: id2,: id3)

    $ whereIn = implode (',', array_fill (0, นับ ($ ids), '?'));

  2. เสนอราคาอาร์เรย์ก่อนหน้า

    $ whereIn = array_map (อาร์เรย์ ($ db, 'quote'), $ ids);

ตัวเลือกทั้งสองเป็นสิ่งที่ดีและปลอดภัย ฉันชอบอันที่สองเพราะมันสั้นกว่าและฉันสามารถพารามิเตอร์ var_dump ได้ถ้าต้องการ การใช้ตัวยึดตำแหน่งคุณต้องผูกค่าและในที่สุดรหัส SQL ของคุณจะเหมือนกัน

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

และสิ่งสุดท้ายและสำคัญสำหรับฉันคือการหลีกเลี่ยงข้อผิดพลาด "จำนวนตัวแปรที่ถูกผูกไว้ไม่ตรงกับจำนวนโทเค็น"

หลักคำสอนเป็นตัวอย่างที่ดีของการใช้ตัวยึดตำแหน่งเนื่องจากมีการควบคุมภายในเกี่ยวกับพารามิเตอร์ขาเข้า


4

หากคอลัมน์สามารถมีจำนวนเต็มเท่านั้นคุณอาจทำได้โดยไม่มีตัวยึดตำแหน่งและใส่รหัสในแบบสอบถามโดยตรง คุณเพียงแค่ต้องโยนค่าทั้งหมดของอาร์เรย์เป็นจำนวนเต็ม แบบนี้:

$listOfIds = implode(',',array_map('intval', $ids));
$stmt = $db->prepare(
    "SELECT *
     FROM table
     WHERE id IN($listOfIds)"
);
$stmt->execute();

สิ่งนี้ไม่ควรเสี่ยงต่อการฉีด SQL


3

นี่คือทางออกของฉัน ฉันได้ขยายชั้น PDO ด้วย:

class Db extends PDO
{

    /**
     * SELECT ... WHERE fieldName IN (:paramName) workaround
     *
     * @param array  $array
     * @param string $prefix
     *
     * @return string
     */
    public function CreateArrayBindParamNames(array $array, $prefix = 'id_')
    {
        $newparams = [];
        foreach ($array as $n => $val)
        {
            $newparams[] = ":".$prefix.$n;
        }
        return implode(", ", $newparams);
    }

    /**
     * Bind every array element to the proper named parameter
     *
     * @param PDOStatement $stmt
     * @param array        $array
     * @param string       $prefix
     */
    public function BindArrayParam(PDOStatement &$stmt, array $array, $prefix = 'id_')
    {
        foreach($array as $n => $val)
        {
            $val = intval($val);
            $stmt -> bindParam(":".$prefix.$n, $val, PDO::PARAM_INT);
        }
    }
}

นี่คือตัวอย่างการใช้งานสำหรับโค้ดด้านบน:

$idList = [1, 2, 3, 4];
$stmt = $this -> db -> prepare("
  SELECT
    `Name`
  FROM
    `User`
  WHERE
    (`ID` IN (".$this -> db -> CreateArrayBindParamNames($idList)."))");
$this -> db -> BindArrayParam($stmt, $idList);
$stmt -> execute();
foreach($stmt as $row)
{
    echo $row['Name'];
}

แจ้งให้เราทราบสิ่งที่คุณคิด


ลืมที่จะพูดถึงว่านี้จะขึ้นอยู่กับคำตอบของผู้ใช้ 2198977 ด้านล่าง
Lippai Zoltan

ฉันไม่แน่ใจว่าอะไรคือ getOne () ดูเหมือนจะไม่ได้เป็นส่วนหนึ่งของ PDO ฉันเห็นมันในลูกแพร์เท่านั้น มันทำอะไรกันแน่?
Lippai Zoltan

@YourCommonSense คุณสามารถโพสต์ฟังก์ชั่นที่ผู้ใช้กำหนดเองเป็นคำตอบได้หรือไม่?
ต้อง

ฉันขอแนะนำให้ส่งประเภทข้อมูลไปยังBindArrayParamอาเรย์แบบเชื่อมโยงเนื่องจากดูเหมือนว่าคุณจะ จำกัด จำนวนนี้เป็นจำนวนเต็ม
Ian Brindley

2

ไม่สามารถใช้อาร์เรย์ใน PDO ได้

คุณต้องสร้างสตริงด้วยพารามิเตอร์ (หรือใช้?) สำหรับแต่ละค่าตัวอย่างเช่น:

:an_array_0, :an_array_1, :an_array_2, :an_array_3, :an_array_4, :an_array_5

นี่คือตัวอย่าง:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = join(
    ', ',
    array_map(
        function($index) {
            return ":an_array_$index";
        },
        array_keys($ids)
    )
);
$db = new PDO(
    'mysql:dbname=mydb;host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM table
     WHERE id IN('.$sqlAnArray.')'
);
foreach ($ids as $index => $id) {
    $stmt->bindValue("an_array_$index", $id);
}

หากคุณต้องการใช้ต่อbindParamไปคุณสามารถทำได้ดังนี้:

foreach ($ids as $index => $id) {
    $stmt->bindParam("an_array_$index", $ids[$id]);
}

หากคุณต้องการใช้?ตัวยึดตำแหน่งคุณสามารถทำได้เช่นนี้:

<?php
$ids = array(1,2,3,7,8,9);
$sqlAnArray = '?' . str_repeat(', ?', count($ids)-1);
$db = new PDO(
    'mysql:dbname=dbname;host=localhost',
    'user',
    'passwd'
);
$stmt = $db->prepare(
    'SELECT *
     FROM phone_number_lookup
     WHERE country_code IN('.$sqlAnArray.')'
);
$stmt->execute($ids);

หากคุณไม่ทราบว่า$idsว่างเปล่าคุณควรทดสอบและจัดการกับกรณีดังกล่าว (คืนอาเรย์ที่ว่างเปล่าหรือส่งคืน Null Object หรือส่งข้อยกเว้น ... )


0

ฉันใช้เวลาอีกเล็กน้อยเพื่อให้ได้คำตอบที่ใกล้เคียงกับคำถามดั้งเดิมของการใช้ตัวยึดตำแหน่งเพื่อผูก params

คำตอบนี้จะต้องทำสองลูปผ่านอาร์เรย์เพื่อใช้ในแบบสอบถาม แต่มันจะแก้ปัญหาของการมีตัวยึดคอลัมน์อื่น ๆ สำหรับแบบสอบถามที่เลือกมากกว่า

//builds placeholders to insert in IN()
foreach($array as $key=>$value) {
    $in_query = $in_query . ' :val_' . $key . ', ';
}

//gets rid of trailing comma and space
$in_query = substr($in_query, 0, -2);

$stmt = $db->prepare(
    "SELECT *
     WHERE id IN($in_query)";

//pind params for your placeholders.
foreach ($array as $key=>$value) {
    $stmt->bindParam(":val_" . $key, $array[$key])
}

$stmt->execute();

0

คุณตั้งค่าจำนวนแรกของ "?" ในแบบสอบถามแล้วโดย "สำหรับ" ส่งพารามิเตอร์เช่นนี้

require 'dbConnect.php';
$db=new dbConnect();
$array=[];
array_push($array,'value1');
array_push($array,'value2');
$query="SELECT * FROM sites WHERE kind IN (";

foreach ($array as $field){
    $query.="?,";
}
$query=substr($query,0,strlen($query)-1);
$query.=")";
$tbl=$db->connection->prepare($query);
for($i=1;$i<=count($array);$i++)
    $tbl->bindParam($i,$array[$i-1],PDO::PARAM_STR);
$tbl->execute();
$row=$tbl->fetchAll(PDO::FETCH_OBJ);
var_dump($row);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.