skiwi
skiwi

Reputation: 69269

PHP PDO and query with SELECT COUNT(*)

I have a very strange issue here - it is a small workaround to the fact that PDO cannot return the num_rows in combination with MySQL.

I can feed this query directly to the database via phpmyadmin:

SELECT COUNT(*) AS COUNT
FROM ((
         (SELECT 'Key' AS tradeOrigin,
                 CONCAT(skti.tier, ' ', skty.type) AS trade,
                 CONCAT('Amount: ', t.sourceKeyAmount) AS tradeInfo,
                 'Platinum' AS tradeToOrigin,
                 t.destinationPlatinum AS tradeTo,
                 '' AS tradeToInfo,
                 u.ingame AS seller,
                 DAYSPASSED(added) AS daysPassed,
                 DATEDIFF(NOW(), added) AS sortingSince
          FROM trades t
          JOIN users u ON t.sourceItem = 1
          AND t.destinationItem = 1
          AND t.userId = u.userId
          AND t.sourceModId = 18
          JOIN keytiers skti ON t.sourceKeyTierId = skti.keyTierId
          JOIN keytypes skty ON t.sourceKeyTypeId = skty.keyTypeId)
       UNION ALL
         (SELECT 'Mod' AS tradeOrigin,
                 sm.name AS trade,
                 CONCAT('Level: ', IF(t.sourceModLevel = 0, 'Unranked', t.sourceModLevel)) AS tradeInfo,
                 'Platinum' AS tradeToOrigin,
                 t.destinationPlatinum AS tradeTo,
                 '' AS tradeToInfo,
                 u.ingame AS seller,
                 DAYSPASSED(added) AS daysPassed,
                 DATEDIFF(NOW(), added) AS sortingSince
          FROM trades t
          JOIN users u ON t.sourceItem = 2
          AND t.destinationItem = 1
          AND t.userId = u.userId
          AND t.sourceModId = 18
          JOIN mods sm ON t.sourceModId = sm.modId)) AS derived)

And it will return one row with column count and value 1 as expected.

However it goes wrong when it needs to go through my framework.

The executing code:

if (!empty($sql)) {
    try {
        echo $sql."<br><pre>";
        print_r($dataArray);
        echo "</pre>";
        $numrows = $dbh->num_rows($sql, $dataArray);
    } catch (PDOException $ex) {
//        echo $ex;
        error($ex);
    }
    //...

Output:

  (SELECT 'Key' AS tradeOrigin,
          CONCAT(skti.tier, ' ', skty.type) AS trade,
          CONCAT('Amount: ', t.sourceKeyAmount) AS tradeInfo,
          'Platinum' AS tradeToOrigin,
          t.destinationPlatinum AS tradeTo,
          '' AS tradeToInfo,
          u.ingame AS seller,
          DAYSPASSED(added) AS daysPassed,
          DATEDIFF(NOW(), added) AS sortingSince
   FROM trades t
   JOIN users u ON t.sourceItem = 1
   AND t.destinationItem = 1
   AND t.userId = u.userId
   AND t.sourceModId = :modId
   JOIN keytiers skti ON t.sourceKeyTierId = skti.keyTierId
   JOIN keytypes skty ON t.sourceKeyTypeId = skty.keyTypeId)
UNION ALL
  (SELECT 'Mod' AS tradeOrigin,
          sm.name AS trade,
          CONCAT('Level: ', IF(t.sourceModLevel = 0, 'Unranked', t.sourceModLevel)) AS tradeInfo,
          'Platinum' AS tradeToOrigin,
          t.destinationPlatinum AS tradeTo,
          '' AS tradeToInfo,
          u.ingame AS seller,
          DAYSPASSED(added) AS daysPassed,
          DATEDIFF(NOW(), added) AS sortingSince
   FROM trades t
   JOIN users u ON t.sourceItem = 2
   AND t.destinationItem = 1
   AND t.userId = u.userId
   AND t.sourceModId = :modId
   JOIN mods sm ON t.sourceModId = sm.modId)

Array
(
    [:modId] => 18
)

This goes into (Note: $this->dbh is a PDO instance):

/**
 * Returns the number of rows that this query has.
 * 
 * @param type $query   The input query
 * @param type $values  The values
 * @return type Number of rows
 */
public function num_rows($query, $values) {
    $newquery = "SELECT COUNT(*) AS count FROM (({$query}) AS derived)";
    echo $newquery;
    $statement = $this->query($newquery, $values);
    $i = $statement->fetch();
    echo "<pre>";
    print_r($i);
    echo "</pre>";
    return $i->count;
}

It echoes:

SELECT COUNT(*) AS COUNT
FROM ((
         (SELECT 'Key' AS tradeOrigin,
                 CONCAT(skti.tier, ' ', skty.type) AS trade,
                 CONCAT('Amount: ', t.sourceKeyAmount) AS tradeInfo,
                 'Platinum' AS tradeToOrigin,
                 t.destinationPlatinum AS tradeTo,
                 '' AS tradeToInfo,
                 u.ingame AS seller,
                 DAYSPASSED(added) AS daysPassed,
                 DATEDIFF(NOW(), added) AS sortingSince
          FROM trades t
          JOIN users u ON t.sourceItem = 1
          AND t.destinationItem = 1
          AND t.userId = u.userId
          AND t.sourceModId = :modId
          JOIN keytiers skti ON t.sourceKeyTierId = skti.keyTierId
          JOIN keytypes skty ON t.sourceKeyTypeId = skty.keyTypeId)
       UNION ALL
         (SELECT 'Mod' AS tradeOrigin,
                 sm.name AS trade,
                 CONCAT('Level: ', IF(t.sourceModLevel = 0, 'Unranked', t.sourceModLevel)) AS tradeInfo,
                 'Platinum' AS tradeToOrigin,
                 t.destinationPlatinum AS tradeTo,
                 '' AS tradeToInfo,
                 u.ingame AS seller,
                 DAYSPASSED(added) AS daysPassed,
                 DATEDIFF(NOW(), added) AS sortingSince
          FROM trades t
          JOIN users u ON t.sourceItem = 2
          AND t.destinationItem = 1
          AND t.userId = u.userId
          AND t.sourceModId = :modId
          JOIN mods sm ON t.sourceModId = sm.modId)) AS derived)

stdClass Object
(
    [count] => 0
)

Which calls:

/**
 * Can be called to create a query. Use either unnamed or named placeholders for the prepared statements.
 * 
 * Example: $dbh->query("INSERT INTO table (data1, data2) VALUES(?, ?)", array($data1, $data2));
 * 
 * @param type $query   The input query, including unnamed or named placeholders
 * @param type $values  The input values. If it's not an array, then it will be an one-element array
 * @return type The statement constructed by this query
 */
public function query($query, $values = array()) {
    if (!is_array($values)) {
        $values = array($values);
    }
    $statement = $this->dbh->prepare($query);
    $statement->setFetchMode(PDO::FETCH_OBJ);
    if (is_assoc($values)) {
        foreach ($values as $key => $value) {
            $statement->bindValue($key, $value);
        }
    }
    else {
        $i = 1;
        foreach ($values as $value) {
            $statement->bindValue($i++, $value);
        }
    }
    $statement->execute();
    return $statement;
}

The query method has been proven to work in the past, and also strangely enough the num_rows does work on some other arbitrarily query that returns 6 as count correctly.

I am really stuck here and have no clue what is happening, please help me out.

UPDATE:

Appereantly a setting was introducing me to this issue: $this->dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);.

However I would still like to know why it is causing the issue, as I cannot just disable it, as I am using it for the LIMIT clause which else fails (plenty of SO posts over that matter). An example of a post is the following: How to apply bindValue method in LIMIT clause?

Upvotes: 2

Views: 1226

Answers (2)

halfer
halfer

Reputation: 20439

Following the discussion in the comments, it seems that you now have enough information to be getting on with: your PDO setting is preventing your binding from working.

You say that you are using that setting to get LIMIT working - I am guessing specifically binding to LIMIT. You cannot normally do this, since binding is just for parameter values (i.e. in WHERE clauses) whereas LIMIT clauses are not treated as parameters. Can you rewrite the bound LIMIT queries another way?

Lastly, as per the comments, ensure that any aliases you use are in lower case, and are not reserved words. You can probably use these case-insensitively, but code conventions are good to have anyway!

Upvotes: 1

SmootQ
SmootQ

Reputation: 2122

$statement->bindValue($key, $value);

Did you try using $statement->bindParam instead??

because results differ, there's no problem with the Sql itself, but the problem is either in the values, or the bind.

Upvotes: 1

Related Questions