# PDO: Wie kann man SQL String ausgeben und wie verwendet man "WHERE x IN (!, 2, 3)"



## tombe (27. September 2013)

Hallo,

ich habe viel zu lange gebraucht um mich endlich mit den Möglichkeiten von mySQL undPDO zu beschäfftigen und stehe jetzt gleich vor 2 Problemen die ich nicht gelöst bekomme.

1) Immer wenn eine Abfrage nicht klappt, habe ich mir bisher immer die SQL-Anweisung per echo ausgegeben und kontrolliert.


```
$sql = "SELECT * FROM tabelle WHERE feld1 = :parameter1 AND feld2 > :parameter2";
$rs->bindParam(":parameter1", $wert1);
$rs->bindParam(":parameter2", $wert2);
```

Wie schaffe ich das nun bei diesem Beispiel? Die Anweisung *$rs->debugDumpParams();* liefert zumindest mir nicht wirklich ein brauchbares Ergebnis.

2) Beim Versuch "*WHERE feld1 IN (1, 2, 3)*" zusammen mit bindParam zu verwenden konnte ich bisher auch noch keine funktionierende Abfrage erstellen.


```
$wert1 = "1, 2, 3";
$sql = "SELECT * FROM tabelle WHERE feld1 IN (:parameter1)";
$rs->bindParam(":parameter1", $wert1);
```

So wird immer nur der an erster Stelle stehende Wert (1) berücksichtigt, alle anderen nicht! Nur wenn ich die Variable $wert direkt in die SQL-Anweisung einbinde, funktoniert es. Wie muss also der Parameter bei einer solchen Abfrage eingebunden werden?

Vielen Dank für eure Tipps

Thomas


----------



## Godstyle (27. September 2013)

Hallo, ich persönlich lasse mir alle fehler immer ausgeben und zwar überliefere ich diese info schon bei der db->conn

$conn = new PDO('
$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

zu frage 2:

$rs->bindParam("arameter1", $wert1);  

du gibst oben aber nur $wert an und nicht $wert1


----------



## tombe (27. September 2013)

Danke für deine Antwort.

Die Fehlermeldungen sind ein Punkt der helfen kann, aber trotzdem würde es mich interessieren wie man die kompletierte SQL-Anweisung ausgeben kann.

Was die Variable $wert bzw. $wert1 angeht, das war nur ein Schreibfehler im Beispiel. Ist oben behoben.


----------



## Godstyle (27. September 2013)

ok, du musst meines wissens nach jeden Parameter einzeln binden in PDO, als bsp bei mir:


FEHLER:


```
$stmt->bind_param('ssi', $_SESSION['usid'], $add_album, $user_ip);
```

Richtig:


```
$stmt->bindParam(1, $_SESSION['usid']);
$stmt->bindParam(2, $add_album);
$stmt->bindParam(3, $user_ip, PDO::PARAM_INT);
```

was in deinem Fall gehen sollte ist:


```
$wert1 = array(1, 2, 3); 
$sql = "SELECT * FROM tabelle WHERE feld1 IN (:parameter1)";
$rs->bindParam(":parameter1", $wert1);
```

ungetestet


----------



## Yaslaw (27. September 2013)

Nutze FIND_IN_SET von MySQL

```
$wert = array(1, 2, 3); 
$sql = "SELECT * FROM tabelle WHERE FIND_IN_SET(feld1,:parameter1)";
$wertS =  implode($wert, ',');
$rs->bindParam(":parameter1",$wertS);
```


----------



## tombe (27. September 2013)

Wie in meinem ersten Beispiel zu sehen, binde ich jeden Wert einzeln in die Abfrage ein, das ist auch nicht das Problem.

Wenn man die "IN"-Werte als Array übergibt, löst es einen Fehler aus: "Notice: Array to string conversion in ...". Geht also auch nicht.


----------



## tombe (27. September 2013)

@Yaslaw: Danke du hast mal wieder eine Lösung die funktioniert! Hast du aber auch eine Erklärung warum es so wie ich es versucht habe nicht geht?

Und wenn du mir dann noch sagen kannst, wie man es schafft die SQL-Anweisung auszugeben bin ich fürs erste glücklich und zufrieden.


----------



## Yaslaw (27. September 2013)

Jepp, hab ich. Das SQL-Statement wird vorbereit und bereits an MySQL übermittelt (MySQL kennt sowas). Das SQL wird bereits da geprüft. Du kannst also nicht einfach Textbausteine zusammensetzen sondern 'nur' Werte eingefüllt.


----------



## Yaslaw (30. September 2013)

tombe hat gesagt.:


> Und wenn du mir dann noch sagen kannst, wie man es schafft die SQL-Anweisung auszugeben bin ich fürs erste glücklich und zufrieden.


Nicht direkt. Mann muss das schon selber basteln. Mit dem Gedanken spielte ich schon lange. Nun habe ich mir eine Erweiterung zum PDOStatement geschrieben, mit der ich das SQL ausgeben kann.


```
<?php
/**
* mpl           by ERB software
* @author       stefan.erb(at)erb-software.com
*/

/**
 * Erweiterung von PDOStatement um das SQL-Statement zu ermitteln
 * @example
 *               require_once 'connectPDO.php';
 *               require_once 'YPDOStmt.php';
 *               //PDO-Statementklasse durch die eigene ersetzen
 *               $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo)));
 */
class YPDOStmt extends PDOStatement{
    const         C_DEFAULT_ERROR_FORMAT = '%1$s<br/>parsed SQL:<br/><hr /><pre>%2$s</pre><hr />';
    /**
     * Error-Format(printf). Parameter 1 => PDO-Fehlermeldung, Parameter 2 => SQL-Statement
     * @var String
     */
    public        $error_format = self::C_DEFAULT_ERROR_FORMAT;
    /**
     * zuletzt ausgeführtes SQL
     * @var String
     */
    public        $executedSql;
    
    protected     $params = array();
    protected     $pdo;

    /**
     * @see PDOStatement::__construct()
     */
    protected function __construct($pdo) {
        $this->pdo = $pdo;
    }

    /**
     * bindParam überschreiben, damit der Parameter gespeichert wird.
     * @see PDOStatement::bindParam()
     */
    public function bindParam($parameter, &$variable, $data_type = null , $length = null, $driver_options = null){
        parent::bindParam($parameter, $variable, $data_type, $length, $driver_options);
        if(is_numeric($parameter)) $parameter = $parameter-1;
        $this->params[$parameter] = new YPDOParam($this->pdo, $parameter, $variable, $data_type, $length, $driver_options);
    }

    /**
     * bindValue überschreiben, damit der Parameter gespeichert wird.
     * @see PDOStatement::bindValue()
     */
    public function bindValue($parameter, $value, $data_type = null){
        parent::bindValue($parameter, $value, $data_type);
        if(is_numeric($parameter)) $parameter = $parameter-1;
        $this->params[$parameter] = new YPDOParam($this->pdo, $parameter, $value, $data_type);
    }

    /**
     * execute überschreiben, damit das SQL-Statement mit den Aktuellen Variableninhalten  gespeichert wird
     * Die PDOException wird um das SQL-Staement erweitert 
     * @see PDOStatement::execute()
     */
    public function execute($input_parameters = null){
        //Es wurden Parameter mitgegeben. Diese also noch erfassen
        if(is_array($input_parameters)){
            foreach($input_parameters as $key => $dummy){
                $this->params[$key] = new YPDOParam($this->pdo, $key , $input_parameters[$key]);
            }
        }
        //SQL genereiren und speichern
        $this->executedSql = $this->getSql();
        //parent->execute ausführen
        try{
            parent::execute($input_parameters);
        }catch (PDOException $e){
            //Den Fehler um das SQL-Statement erweitern
            $msg = sprintf($this->error_format, $e->getMessage(), $this->executedSql);
            throw new PDOException($msg, (int)$e->getCode(), $e);
        }
    }

    /**
     * SQL-Statement mit den aktuellen Variabelninhalten erstellen
     * @return String
     */
    public function getSql(){
        $sql = $this->queryString;
        if(count($this->params) > 0){
            reset($this->params);
            //Der Query-String hat ? als Platzhalter
            if(is_numeric(key($this->params))){
                $this->params = array();
                foreach($this->params as $key => &$param){
                    $sql = preg_replace('/\?/', $param->getValue(), $sql, 1);
                }
                //Der Query-String hat Variablen als Platzhalter
            }else{
                //Sicherstellen dass :peter_hans vor :peter ersetzt wird
                foreach($this->params as $key => $param){
                    $sql = preg_replace("/(?<!\w)({$key})(?!\w)/is", $param->getValue(), $sql);
                }

            }
        }
        return $sql;
    }

    /**
     * Gibt alle Parameters zurück
     * @return Array<YPDOParam>
     */
    public function getParams(){
        return $this->params;
    }

    /**
     * gibt einen einzelnen Parameter zurück
     * @param  Key oder Index
     * @return YPDOParam
     */
    public function getParam($iKey){
        return $this->params[$iKey];
    }
}

/**
 * Parameter-Klasse mit allen Informationen zu PDO-Bind Params
 * @author Stefan Erb
 */
class YPDOParam{
    protected $pdo;

    /**
     * @param PDO      PDO-DB-Connection
     * @see   PDOStatement::bindParam()
     */
    public function __construct(&$pdo, $parameter, &$variable, $data_type = null , $length = null, $driver_options = null){
        $this->pdo = &$pdo;
        $this->parameter        = $parameter;
        $this->variable         = &$variable;
        $this->data_type        = $data_type;
        $this->length           = $length;
        $this->driver_options   = $driver_options;
    }
    /**
     * @return <String>   Query-String mit PDO geparst. Nimmt die Aktuellen Werte der referenzierten Variable
     */
    public function getValue(){
        return $this->pdo->quote($this->variable, $this->data_type);
    }

}
?>
```

Und so wirds angewendet

```
<?php 

try{
    //Connection erstellen. $pdo ist das PDO-Objekt    
    require_once 'connectPDO.php';
    //YPDOStmt einbinden
    require_once 'YPDOStmt.php';
    
    // ! WICHTIG ! PDO-Statementklasse durch die YPDOStmt-Klasse ersetzen
    $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('YPDOStmt', array($pdo)));
    
    //SQL-Statement mit ? Parametern    
    $sql_indexed = <<<SQL
SELECT t.ID_tipp, t.username, t.ID_partien
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, ?) 
    AND t.punkte=?
SQL;

    //SQL-Statement mit benamsten Parametern
    $sql_named = <<<SQL
SELECT t.ID_tipp, t.username, t.ID_partien, :ids
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, :ids)
    AND t.punkte=:id
SQL;
    
    //Variablen vorbereiten
    $ids = array(319,320,321);
    $idsS = implode($ids, ',');
    $id = 1;

    //Ausgabe defineren
    define('C_TEST_OUTPUT', '<p>-- %s<br /><pre><code>%s</code></pre></p>');
    
    //ab jezt normal wie PDO anwenden
    
    //Bsp 1: Benannte Variablen, Valueübergabe im execute(). Ausgebenes SQL: letzter Lauf
    $title = '<b>Beispiel 1:</b> Benannte Variablen, Valueübergabe im execute(). Ausgebenes SQL: letzter Lauf';
    $stmt = $pdo->prepare($sql_named);
    $stmt->execute(array(':ids'=>$idsS, ':id'=>$id));
    printf(C_TEST_OUTPUT, $title,  $stmt->executedSql);
    
    
    //Bsp 2: Indexiert und mit einzelner Parameterbindung. Ausgebenes SQL: letzter Lauf
    $title = '<b>Beispiel 2:</b> Indexiert und mit einzelner Parameterbindung. Ausgebenes SQL: letzter Lauf';
    $stmt = $pdo->prepare($sql_indexed);    
    $stmt->bindValue(1, $idsS, PDO::PARAM_STR);    //Als Value
    $stmt->bindParam(2, $id, PDO::PARAM_INT);      //und als Bind Variable
    $stmt->execute();    
    printf(C_TEST_OUTPUT, $title,  $stmt->executedSql);
        
    //Bsp 3:und beide Variable mal ändern. Nur die Bind-Variable ($id) wird neu übernommen. Ausgebenes SQL: letzter Lauf
    $title = '<b>Beispiel 3:</b> Geänderte Variablen. Nur die BindVariable $id wird übernommen. Ausgebenes SQL: letzter Lauf';
    $idsS = 'A,B,C';
    $id = 5;
    $stmt->execute();
    printf(C_TEST_OUTPUT, $title,  $stmt->executedSql);
        
    //Bsp 4:Bind-Variable nochmal anpassen und nur das SQL ausgeben ohne auszuführen
    $title = '<b>Beispiel 4:</b> Bind-Variable nochmal anpassen und nur das SQL ausgeben ohne auszuführen';
    $id = 999;
    printf(C_TEST_OUTPUT, $title,  $stmt->getSql());
        
    //Bsp 5:Ausführen mit Fehler.
    echo '<p>-- <b>Beispiel 5:</b> Ausführen mit Fehler.</p>';
    $stmt->execute(array('abc'));
    
    
} catch(Exception $e){
    echo $e->getMessage();
    echo nl2br($e->getTraceAsString());
}    
?>
```
Ausgabe:

```
-- Beispiel 1: Benannte Variablen, Valueübergabe im execute(). Ausgebenes SQL: letzter Lauf

SELECT t.ID_tipp, t.username, t.ID_partien, '319,320,321'
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, '319,320,321')
    AND t.punkte='1'

-- Beispiel 2: Indexiert und mit einzelner Parameterbindung. Ausgebenes SQL: letzter Lauf

SELECT t.ID_tipp, t.username, t.ID_partien
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, '319,320,321') 
    AND t.punkte='1'

-- Beispiel 3: Geänderte Variablen. Nur die BindVariable $id wird übernommen. Ausgebenes SQL: letzter Lauf

SELECT t.ID_tipp, t.username, t.ID_partien
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, '319,320,321') 
    AND t.punkte='5'

-- Beispiel 4: Bind-Variable nochmal anpassen und nur das SQL ausgeben ohne auszuführen

SELECT t.ID_tipp, t.username, t.ID_partien
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, '319,320,321') 
    AND t.punkte='999'

-- Beispiel 5: Ausführen mit Fehler.
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
parsed SQL:

SELECT t.ID_tipp, t.username, t.ID_partien
FROM tipps_1213 t
WHERE FIND_IN_SET(t.ID_partien, 'abc') 
    AND t.punkte=?

#0 C:\xampp\htdocs\test\test4.php(67): YPDOStmt->execute(Array)
#1 {main}
```

Ich werde das demnächst in mein Wiki übernehmen....

Nachtrag:
Ist mit Anleitung im Wiki
http://wiki.yaslaw.info/dokuwiki/doku.php/php/libraries/ypdostmt


----------

