Felix Riesterer: Was will mir diese PDO-Meldung sagen? "SQLSTATE[HY000]: General error"

Liebe Mitlesende,

ich habe mir für ein Projekt eine Klasse gebaut, die PHPs PDO-Klasse nutzt. Nun möchte ich ein SQL-UPDATE an einer Tabelle durchführen, und erhalte dabei diese ominöse Fehlermeldung:

SQLSTATE[HY000]: General error

Was will mir wer (PHP, PDO oder MySQL?) damit sagen?

Mein PHP-Code, der die Anfrage stellt:

class MyDB {
...
  public  function __construct ($settings) {
    $t = $this;

    $t->errors = array();

    try {

      $t->pdo = new \PDO(
        sprintf(
          'mysql:dbname=%1$s;host=%2$s;charset=UTF8;',
          $settings['db-name'],
          $settings['db-host']
        ),
        $settings['db-user'],
        $settings['db-pw'],
        array(
          \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
          \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
        )
      );

    } catch (\PDOException $e) {

      $t->errors[] = $t->get_error($e);
    }
  }

  /**
   * wrapper for \PDO::prepare, \PDOStatement::execute and \PDOStatement::fetchAll
   *
   * This function passes the parameters through to the PDO object.
   *
   * @param string SQL
   * @param array parameters list
   * @param int column number for \PDO::FETCH_COLUMN fetch mode
   * @return array result sets
   */
  public  function get ($sql = '', $params = array(), $col = null) {
    $t = $this;

    $r = array();

    if (!empty($sql)) {

      $st = $t->pdo->prepare($sql);
      $st->execute($params); // getestet: ergibt in allen Fällen true

      try {

        $r = (
          !is_null($col) && is_numeric($col)
          ? $st->fetchAll(\PDO::FETCH_COLUMN, $col)
          : $st->fetchAll(\PDO::FETCH_ASSOC)
        );

      } catch (\PDOException $ex) {

        $t->errors[] = $t->get_error(
          $ex,
          array('sql' => $sql, 'params' => $params)
        );
      }
    }

    return $r;
  }
}

Da ich sowohl den SQL-Code mit Platzhaltern, als auch die Parameter logge, hier meine Debug-Ausgabe, was ich an Daten füttern wollte:

UPDATE `people` SET
 `call`=:call,
 `first_name`=:first_name,
 `family_name`=:family_name,
 `email`=:email,
 `sex`=:sex,
 `titles`=:titles
WHERE `first_name`=:first_name AND `family_name`=:family_name

Die Platzhalter wurden mit diesem Array befüllt:

[params] => Array
  (
    [:call] => Herr
    [:first_name] => Fritzchen
    [:family_name] => Müller
    [:email] => fritzchenmueller@example.org
    [:sex] => m
    [:titles] => Prof. Dr.
  )

Mit ist völlig unklar, wo hier ein Fehler liegen könnte. Anscheinend wird mein SQL-Code korrekt erstellt (sehe keinen Syntax-Fehler darin) und mit der korrekten Anzahl an passenden Ersetzungsparametern an MyDB::get übertragen.

Wer weiß Rat?

Liebe Grüße,

Felix Riesterer.

  1. Tach!

    Was will mir wer (PHP, PDO oder MySQL?) damit sagen?

    Vielleicht, dass es beim Update nichts zu fetchen gibt.

    Da muss doch auch eine Zeile angezeigt werden, für die der Fehler ausgelöst wird. Diese Information hast du unterschlagen.

    dedlfix.

    1. Lieber dedlfix,

      Vielleicht, dass es beim Update nichts zu fetchen gibt.

      soll das heißen, dass ich in meiner MyDB-Klasse den SQL-Code nach UPDATE sniffen sollte, um statt fetchAll nur fetch aufzurufen? Dass ich am besten gleich so lange fetche, bis ich auch bei SELECT alle betroffenen Zeilen ermittelt habe?

      Zu was würdest Du mir da raten?

      Ist schon einige Jahre her, dass ich was mit SQL gemacht habe. Damals hatte ich noch die alten mysql_*-Funktionen verwendet. Das Kapseln mit PDO ist mir noch sehr neu.

      Jedenfalls vielen herzlichen Dank für Deine sehr hilfreiche Antwort!

      Liebe Grüße,

      Felix Riesterer.

      1. Tach!

        Vielleicht, dass es beim Update nichts zu fetchen gibt.

        soll das heißen, dass ich in meiner MyDB-Klasse den SQL-Code nach UPDATE sniffen sollte, um statt fetchAll nur fetch aufzurufen? Dass ich am besten gleich so lange fetche, bis ich auch bei SELECT alle betroffenen Zeilen ermittelt habe?

        Zu was würdest Du mir da raten?

        Es gibt noch mehr Statements, die keine Ergebnismenge produzieren. Anderswo werden die Statement-Abschick-Funktionen unterschieden (sprich: es gibt zwei Funktionen), ob du ein Ergebnis haben möchtest oder nicht. Demzufolge gibt es ein befragbares Resultset zurück oder nur eine Statusinformation (affected Rows zum Beispiel).

        Ist schon einige Jahre her, dass ich was mit SQL gemacht habe. Damals hatte ich noch die alten mysql_*-Funktionen verwendet. Das Kapseln mit PDO ist mir noch sehr neu.

        Auch da kannst du nicht auf keine Ergebnismenge fetchen, weil in dem Fall das Ergebnis von mysql_query() nur ein Integer und keine Resource ist.

        dedlfix.

  2. Hallo Felix,

    ich habe mir für ein Projekt eine Klasse gebaut, die PHPs PDO-Klasse nutzt. Nun möchte ich ein SQL-UPDATE an einer Tabelle durchführen, und erhalte dabei diese ominöse Fehlermeldung:

    SQLSTATE[HY000]: General error
    

    Was will mir wer (PHP, PDO oder MySQL?) damit sagen?

    Du versuchst, wenn ich deinen Code richtig interpretiere (du hast ja nicht alles gepostet), ein fetchAll auf ein UPDATE-Statement. Das geht, wenn du keine RETURNING-Klausel hast, schief, denn es gibt kein Resultset.

    LG,
    CK

    1. Tach!

      Das geht, wenn du keine RETURNING-Klausel hast, schief, denn es gibt kein Resultset.

      MySQL kennt keine RETURNING-Klausel oder ein Äquivalent dazu.

      dedlfix.

      1. Hallo dedlfix,

        Das geht, wenn du keine RETURNING-Klausel hast, schief, denn es gibt kein Resultset.

        MySQL kennt keine RETURNING-Klausel oder ein Äquivalent dazu.

        Das ist mir bewusst, aber für andere DBMS gilt das nicht. Deshalb war der Einschub notwendig.

        LG,
        CK

    2. Lieber Christian,

      Du versuchst, wenn ich deinen Code richtig interpretiere (du hast ja nicht alles gepostet), ein fetchAll auf ein UPDATE-Statement. Das geht, wenn du keine RETURNING-Klausel hast, schief, denn es gibt kein Resultset.

      also stimmt meine Logik in meinem Code noch nicht ganz. Empfiehlst Du mir, dass ich in den SQL-Code hineinsniffe, um UPDATE zu erkennen und dann kein Resultset holen zu wollen?

      Beträfe das noch mehr, als nur UPDATE? Gibt es sowieso nur bei SELECT ein Resultset? Dann könnte ich ja darauf prüfen, ob am Anfang meines SQL-Code ein SELECT steht, um nur dann fetchAll aufzurufen.

      Was meinst Du dazu?

      Liebe Grüße,

      Felix Riesterer.

      1. Hallo Felix,

        also stimmt meine Logik in meinem Code noch nicht ganz. Empfiehlst Du mir, dass ich in den SQL-Code hineinsniffe, um UPDATE zu erkennen und dann kein Resultset holen zu wollen?

        Nein, ich empfehle zwei verschiedene Varianten zu verwenden, etwa exec und get.

        Beträfe das noch mehr, als nur UPDATE?

        Ja: REPLACE INTO, INSERT, DELETE, …

        Gibt es sowieso nur bei SELECT ein Resultset?

        Funktionen können auch ein Resultset haben. Aber im wesentlichen betrifft es SELECT, ja.

        Was meinst Du dazu?

        Ich würde davon abraten, das erscheint mir error prone. Etwa im Falle von INSERT … SELECT. Verwende einfach zwei Methoden, exec und get oder so ;-)

        LG,
        CK

        1. Lieber Christian,

          Verwende einfach zwei Methoden, exec und get oder so ;-)

          OK. Habe nun eine Methode send ergänzt:

          /**
           * function to send a query to the DB
           *
           * This function passes the parameters through to the PDO object.
           *
           * @param string SQL
           * @param array parameters list
           */
          public  function send ($sql = '', $params = array()) {
            $t = $this;
          
            if (!empty($sql)) {
          
              if (empty($params)) {
          
                try {
          
                  $t->pdo->query($sql);
          
                } catch (\PDOException $ex) {
          
          
                  $t->errors[] = $t->get_error(
                    $ex,
                    array('sql' => $sql, 'params' => $params)
                  );
          
                }
          
              } else {
          
          
                try {
          
                  $st = $t->pdo->prepare($sql);
                  $st->execute($params);
          
                } catch (\PDOException $ex) {
          
          
                  $t->errors[] = $t->get_error(
                    $ex,
                    array('sql' => $sql, 'params' => $params)
                  );
          
                }
              }
            }
          }
          

          Was meinst Du dazu? Geht das auch eleganter? Mir war es wichtig, die Parameter-Substitution zu erhalten. Dann kann ich mein eingangs erwähntes UPDATE nun mit MyDB::send ausführen lassen, inklusive der Ersetzungsparameter.

          Liebe Grüße,

          Felix Riesterer.

          1. Tach!

            if (!empty($sql)) {

            Nesting reduzieren! Logik umdrehen und return.

            if (empty($params)) {

            Extrawurst reduzieren! Probier mal, ob execute() ein leeres Array oder alternativ null schluckt (Voraussetzung, keine Platzhalter im SQL-Statement). Dann lass query() weg und nimm nur execute().

            dedlfix.

            1. Lieber dedlfix,

              habe Deine Vorschläge jetzt so umgesetzt:

              public  function send ($sql = '', $params = array()) {
                $t = $this;
              
                if (empty($sql)) {
                  return;
                }
              
                $st = null;
              
                try {
              
                  $st = $t->pdo->prepare($sql);
                  $st->execute($params);
              
                } catch (\PDOException $ex) {
              
                  $t->errors[] = $t->get_error(
                    $ex,
                    array('sql' => $sql, 'params' => $params)
                  );
              
                }
              
                if (is_object($st) && method_exists($st, 'rowCount')) {
                  return $st->rowCount();
                }
              }
              

              Es sollte auch nicht erwähnt bleiben, dass der Rückgabewert von rowCount nicht immer eine Zahl > 0 zurück gibt, vor allem dann, wenn beim UPDATE identische Werte zurückgeschrieben werden. Abhilfe schafft PDO::MYSQL_ATTR_FOUND_ROWS => true bei der Parameter-Übergabe im Konstruktor-Aufruf, den ich nun so geändert habe:

              public  function __construct ($settings) {
                $t = $this;
              
                $t->errors = array();
              
                try {
              
                  $t->pdo = new \PDO(
                    sprintf(
                      'mysql:dbname=%1$s;host=%2$s;charset=UTF8;',
                      $settings['db-name'],
                      $settings['db-host']
                    ),
                    $settings['db-user'],
                    $settings['db-pw'],
                    array(
                      \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                      \PDO::MYSQL_ATTR_FOUND_ROWS => true,
                      \PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
                    )
                  );
              
                } catch (\PDOException $e) {
              
                  $t->errors[] = $t->get_error($e);
                }
              }
              

              Nun klappt bisher alles so, wie ich mir das wünsche. Supi!

              Liebe Grüße,

              Felix Riesterer.

          2. Tja, lieber Felix,

            wie macht man das dann mit den "affected rows"? Ein PDOStatement kennt die Methode rowCount, die es nocht zu nutzen gilt:

            if (!empty($sql)) {
            
              $st = null;
            
              if (empty($params)) {
            
                try {
            
                  $st = $t->pdo->query($sql);
            
                } catch (\PDOException $ex) { ... }
            
              } else {
            
                try {
            
                  $st = $t->pdo->prepare($sql);
                  $st->execute($params);
            
                } catch (\PDOException $ex) { ... }
              }
            
              if (is_object($st) && method_exists($st, 'rowCount')) {
                return $st->rowCount();
              }
            }
            

            So sollte eine Information über "affected Rows" möglich sein. Wer Verbesserungsvorschläge hat, bitte immer her damit!

            Liebe Grüße,

            Felix Riesterer.