27

I am using PDOStatement to query the database. Whenever I get a returned row, I want it to be fetched into an array, with the $row[0] as the key, and the subsequent elements in the row as the values.

I can, of course, write a combination of foreach loops and if conditionals to do the job, such as the below:

private static function GetMySQLResult($dbname, $sqlString) {
    
    $dbh = self::ConstructPDOObject($dbname);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    $result=array();
    foreach ($dbh->query($sqlString) as $row) {
        // the simplest case for 2 columns, 
        // should add more to handle more columns
        $result[$row[0]][]=$row[1];
    }
    
    return $result;   
}

but I am looking for an existing method; is there such a method already exist?

Why reopened the question.

What is asked here is clearly the combination of PDO::FETCH_GROUP|PDO::FETCH_ASSOC. PDO::FETCH_KEY_PAIR only works with a 2 column result. But here the example is a 2 column result only to simplify the code in the question, not for all situations.

Graviton
  • 81,782
  • 146
  • 424
  • 602
  • Your code actually groups rows on a non-unique key, is that also a requirement of a possible answer? – Ja͢ck Aug 25 '12 at 13:08
  • I asked to reopen the question, because after testing many solutions, what is asked here is clearly the combination of `PDO::FETCH_GROUP|PDO::FETCH_ASSOC`. PDO::FETCH_KEY_PAIR only works with a 2 column result. But here the example is a 2 column result only to simplify the code in the question, not for all situations. – Meloman Oct 27 '21 at 14:05

5 Answers5

36

Credits go to devdRew. Check the other question here.

Apparently, you can as stated in the answer. I checked as well and it works great.

$q = $db->query("SELECT `name` AS name, `value` AS value FROM `settings`;");
$r  = $q->fetchAll(PDO::FETCH_KEY_PAIR);

EDIT

This answer requires that you specify maximum 2 columns: 1 key and 1 value. If you need to retrieve more keys from the database, check the answer below and read @nullabilty's comment. For those who are lazy, here is his method:

$q->fetchAll(PDO::FETCH_UNIQUE);
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Savas Vedova
  • 5,622
  • 2
  • 28
  • 44
  • This is the only method that will use the first column as key, should you require additional columns, CONCAT or similar the additional fields as delimited string for 2nd column value and use `explode` to have an array before using the value. – nickl- Aug 24 '12 at 04:19
  • @nickl- using `CONCAT` for this purpose is a horrible idea; this answer should be used exclusively for key-value pairs, something for which it was designed to do. – Ja͢ck Aug 27 '12 at 01:56
  • Are you letting the bounty expire? – Ja͢ck Aug 27 '12 at 14:23
  • @Jack horrible might be a little harsh don't you think, especially in absence of motivation. Maybe it's the rebel in me but I don't like be told that I cannot do something. Maybe it's thinking outside the box that blurs the lines between this is what it was meant for and this is what I can do with it. Ultimately, ymmv, it is solving the problem in the best way you can with the resources at you disposal. The rest is bike shedding and makes little to no difference to the bottom line. – nickl- Aug 29 '12 at 05:59
  • @nickl- what would you use for the delimiter I wonder? `uniqid()`? besides, you'd have to rewrite existing queries to this format as well .. quite honestly, I don't get that at all. – Ja͢ck Aug 29 '12 at 06:37
  • @Jack I imagine the $key would be the unique id and you can delimit with anything you feel like, traditionally the comma is used hence csv. If you are unsure that the delimiter wont appear in the data it is customary to use a wrapper. I thought we are writing a new query but you may alter existing ones too. I am starting to think you are just bating for an argument so excuse me if I tap out here. Unless you have anything valid to add to the conversation, now you're not even trying, I will not partake in a quarrel. – nickl- Aug 31 '12 at 22:22
  • @nickl- allow me to canonicalize my argument then; 1) you can't use `table.*` anymore due to the concat() format, 2) you need to find a proper delimiter, 3) you lose the field names (and types) and 4) you need to load all rows in memory first ... i'm not trying to stop you from thinking outside the box, but just saying there's many good things inside of it :) – Ja͢ck Sep 01 '12 at 07:30
15
$stmt->fetchAll(PDO::FETCH_UNIQUE); 

albeit a bit of a hack but really the only way to do this since someone decided FETCH_KEY_PAIR should require 2 column result set's only.

Note: first column is used a key and is not returned in the result set in any other form.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
lsl
  • 4,371
  • 3
  • 39
  • 54
  • 17
    Thanks, I extended mine to `PDO::FETCH_GROUP|PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC` to get exactly what I need. – nullability Nov 19 '13 at 21:39
  • 1
    Note: first column is used a key and is not returned in the result set in any other form. You still can duplicate your first column using "SELECT firstcolumn AS id, firstcolumn, secondcolumn ..." if you want to get the column is results – Reign.85 Nov 18 '15 at 15:08
3

Afaik, there are no existing method that would do that in PHP but there are a couple of ways you could achieve what you want.

One of the first being what Xeoncross said but a bit modified :

$pdostmt = $pdo->query("SELECT ... FROM your_table");

$res = array();

foreach ($pdostmt->fetchAll(PDO::FETCH_ASSOC) as $row)
{
    $res[array_shift($row)] = $row;
}

Otherwise, you can create a class with a __set() method to catch variable assignment:

class at_res
{
  public $key;
  public $values = array();
  protected $flag = true;

  public function __set($var, $value)
  {
    if ($this->flag)
    {
      $this->key = $value;
      $this->flag = false;
    }
    else
    {
      $this->values[$var] = $value;
    }
  }

  public function extract()
  {
    return array($this->key => $this->values);
  }
}


$pdo = new PDO(...);

$pdostmt = $pdo->query("SELECT ... FROM your_table");

$res = $pdostmt->fetchObject('at_res');

var_dump($res->extract());

Hope it helps.

greg
  • 3,354
  • 1
  • 24
  • 35
2

Some tips, you need to pass the right fetch style to the PDOStatement->fetch() method so that you don't end up with double data (numeric and textual column names). Like $row[0] and $row['id'] which both contain the same value when you use PDO::FETCH_BOTH.

$result = $dbh->query($sqlString);
while ($row = $result->fetch(PDO::FETCH_NUM)) {
...

As for your question, you will have to fetch all the results and then create an array with the $row['id'] as the key and the result row as the value - just like you are doing. I built an entire ORM library around PDO and I could never find anything to do this automatically.

$result = $dbh->query($sqlString);

$results = array();
while ($row = $result->fetch(PDO::FETCH_NUM)) {
    $results[$row[0]] = $row;
}

return $results;
Xeoncross
  • 55,620
  • 80
  • 262
  • 364
1

Besides the two column table scenario, there's nothing at the PDO level to handle this, but you could write a reusable iterator for it, like this:

class FirstColumnKeyIterator implements Iterator
{
    private $stmt;
    private $key;
    private $data;
    private $mode;

    public function __construct(PDOStatement $stmt, $fetch_mode = PDO::FETCH_NUM)
    {
        $this->stmt = $stmt;
        $this->mode = $fetch_mode;
        $this->fetch();
    }

    private function fetch()
    {
        if (false !== ($this->data = $this->stmt->fetch($this->mode))) {
            $this->key = current(array_splice($this->data, 0, 1));
        }
    }

    public function rewind()
    {
        // nil operation
    }

    public function key()
    {
        return $this->key;
    }

    public function current()
    {
        return $this->data;
    }

    public function next()
    {
        $this->fetch();
    }

    public function valid()
    {
        return false !== $this->data;
    }
}

The constructor takes a PDOStatement and an optional fetch mode (numeric columns by default, but can be changed to PDO::FETCH_ASSOC for an associative array) and lets you iterate over the query results using a typical foreach.

Usage example:

$dbh = new PDO(/* etc */);

$stmt = $dbh->query('SELECT * FROM accounts');
foreach (new FirstColumnKeyIterator($stmt, PDO::FETCH_ASSOC) as $key => $value) {
        echo $key, ' = ', print_r($value, true), PHP_EOL;
}
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • 10 points for creativity =) but you will find this way more expensive then the original scenario. You can improve this by referencing the `new Iterator` first and always avoid `as $key => #value` if you can help it. You will find that a for loop on a referenced `array_keys` will yield better results as long as the count is also referenced. `for ($i = 0; $i < count($data); $i++)` turns the strongest construct into the worst loop ever and should get me my money back. – nickl- Aug 24 '12 at 04:36
  • @nickl iterators are always more expensive than a "plain" array because extra code gets executed at every loop which won't go away by referencing the instance; removing the `key => value` is not an option either, since there's no way to get the key otherwise :) – Ja͢ck Aug 24 '12 at 04:46
  • alternative ways to use an iterator and still obtain the key could be with do..while or while for that matter and I can think of several for loops ie. `for ($it = new ArrayIterator($data); $it->valid(); $it->next()) echo $it->key() .' => '. $it->current();` but I'm sure you thought about these too. =) – nickl- Aug 31 '12 at 22:11
  • @nickl the problem with ArrayIterator is that you have to load all rows first, so that could be a problem if you have many rows :) – Ja͢ck Aug 31 '12 at 23:45