如何获取GROUP BY查询的总行数?

时间:2011-05-18 08:29:37

标签: php database sqlite pdo

来自PDO手册:

  

PDOStatement :: rowCount()返回   受上次影响的行数    DELETE,INSERT或UPDATE 语句   由相应的执行   PDOStatement对象。

     

如果执行的最后一条SQL语句   相关的PDOStatement是一个    SELECT 语句,某些数据库可能   返回返回的行数   那句话。但是,这个   行为并非所有人都能保证   数据库,不应该依赖   用于便携式应用。

我最近才发现这一点。我刚刚将db抽象层更改为不再使用SELECT COUNT(1) ...,因为只需查询实际行然后计算结果就会更有效率。现在PDO不支持!?

我没有将PDO用于MySQL和Pg​​SQL,但我是为SQLite做的。有没有办法(没有完全改变dbal背部)在PDO中计算这样的行?在MySQL中,这将是这样的:

$q = $db->query('SELECT a, b, c FROM tbl WHERE oele = 2 GROUP BY boele');
$rows = $q->num_rows;
// and now use $q to get actual data

使用MySQLi和PgSQL驱动程序,这是可能的。对于所有PDO,它不是!?

PS。我最初的解决方案是扩展SQLResult-> count方法(我自己的)以SELECT ... FROM替换SELECT COUNT(1) FROM并返回该数字(非常低效,但仅适用于SQLite PDO)。但这还不够好,因为在上面的示例中,查询是GROUP BY,这会改变COUNT(1)的含义/功能。

11 个答案:

答案 0 :(得分:44)

以下是适合您的解决方案

$sql="SELECT count(*) FROM [tablename] WHERE key == ? ";
$sth = $this->db->prepare($sql);
$sth->execute(array($key));
$rows = $sth->fetch(PDO::FETCH_NUM);
echo $rows[0];

答案 1 :(得分:24)

这有点内存效率低但是如果你还在使用数据,我经常使用它:

$rows = $q->fetchAll();
$num_rows = count($rows);

答案 2 :(得分:8)

我最终使用的方法非常简单:

$query = 'SELECT a, b, c FROM tbl WHERE oele = 2 GROUP BY boele';
$nrows = $db->query("SELECT COUNT(1) FROM ($query) x")->fetchColumn();

可能不是最有效的,但它似乎是万无一失的,因为它实际上算上了原始查询的结果。

答案 3 :(得分:4)

  

我没有将PDO用于MySQL和Pg​​SQL,但我是为SQLite做的。有没有办法(没有完全改变dbal背部)在PDO中计算这样的行?

根据this comment,SQLite问题是由3.x中的API更改引入的。

尽管如此,您可能希望在使用之前检查PDO实际上如何实现该功能。

我不熟悉它的内部结构,但我对PDO解析你的SQL这个想法持怀疑态度(因为SQL语法错误会出现在数据库的日志中),更不用说试图对它有所了解了使用最优策略计算行数。

假设它确实不存在,它在select语句中返回所有适用行的计数的现实策略包括从SQL语句中操作限制子句的字符串,以及以下任一项:

  1. 在其上运行select count()作为子查询(从而避免了您在PS中描述的问题);
  2. 打开游标,运行fetch all并计算行数;或
  3. 首先打开这样的光标,并类似地计算其余的行。
  4. 然而,更好的计算方法是执行完全优化的查询。通常情况下,这意味着重写您尝试分页的初始查询的有意义块 - 剥离不需要的字段并按操作排序等。

    最后,如果您的数据集足够大以计算任何类型的延迟,您可能还需要调查返回从statistics派生的估计值,和/或定期在Memcache中缓存结果。在某些时候,精确正确的计数不再有用......

答案 4 :(得分:2)

请注意,PDOStatementTraversable。给出一个查询:

$query = $dbh->query('
    SELECT
        *
    FROM
        test
');

可以迭代:

$it = new IteratorIterator($query);
echo '<p>', iterator_count($it), ' items</p>';

// Have to run the query again unfortunately
$query->execute();
foreach ($query as $row) {
    echo '<p>', $row['title'], '</p>';
}

或者你可以这样做:

$it = new IteratorIterator($query);
$it->rewind();

if ($it->valid()) {
    do {
        $row = $it->current();
        echo '<p>', $row['title'], '</p>';
        $it->next();
    } while ($it->valid());
} else {
    echo '<p>No results</p>';
}

答案 5 :(得分:2)

如果您愿意放弃一些抽象提示,那么您可以使用自定义包装器类,它只是将所有内容传递给PDO。比如说:(警告,代码未经测试)

class SQLitePDOWrapper
{
    private $pdo;

    public function __construct( $dns, $uname = null, $pwd = null, $opts = null )
    {
        $this->pdo = new PDO( $dns, $unam, $pwd, $opts ); 
    }
    public function __call( $nm, $args )
    {
        $ret = call_user_func_array( array( $this->pdo, $nm ), $args );
        if( $ret instanceof PDOStatement )
        {
            return new StatementWrapper( $this, $ret, $args[ 0 ] ); 
               // I'm pretty sure args[ 0 ] will always be your query, 
               // even when binding
        }

        return $ret;
    }

}

class StatementWrapper
{
    private $pdo; private $stat; private $query;

    public function __construct( PDO $pdo, PDOStatement $stat, $query )
    {
        $this->pdo  = $pdo;
        $this->stat = $stat;
        this->query = $query;
    }

    public function rowCount()
    {
        if( strtolower( substr( $this->query, 0, 6 ) ) == 'select' )
        {
            // replace the select columns with a simple 'count(*)
            $res = $this->pdo->query( 
                     'SELECT COUNT(*)' . 
                          substr( $this->query, 
                              strpos( strtolower( $this->query ), 'from' ) ) 
                   )->fetch( PDO::FETCH_NUM );
            return $res[ 0 ];
        }
        return $this->stat->rowCount();
    }

    public function __call( $nm, $args )
    {
        return call_user_func_array( array( $this->stat, $nm ), $args );
    }
}

答案 6 :(得分:1)

也许这会为你做到这一点?

$FoundRows = $DataObject->query('SELECT FOUND_ROWS() AS Count')->fetchColumn();

答案 7 :(得分:0)

您必须使用rowCount - 返回受上一个SQL语句影响的行数

$query = $dbh->prepare("SELECT * FROM table_name");
$query->execute();
$count =$query->rowCount();
echo $count;

答案 8 :(得分:0)

如何将查询结果放在一个数组中,您可以在其中进行计数($ array)并使用查询后产生的行?例如:

$sc='SELECT * FROM comments';
$res=array();
foreach($db->query($sc) as $row){
    $res[]=$row;
}

echo "num rows: ".count($res);
echo "Select output:";
foreach($res as $row){ echo $row['comment'];}

答案 9 :(得分:0)

这又是另一个问题,错误地提出了许多可怕的解决方案,所有这些都使解决一个不存在的问题变得非常复杂。

任何数据库交互的极其简单明了的规则是

始终选择您需要的唯一数据。

从这个角度来看,问题是错误的,接受的答案是对的。但其他提议的解决方案非常糟糕。

问题是&#34;如何计算错误的方式&#34;。一个人不应该直截了当地回答它,而是唯一正确的答案是#34;一个人永远不应该选择行来计算它们。相反,总是要求数据库为您计算行数。&#34;这个规则是如此明显,以至于看到如此多的尝试打破它是不可能的。

了解此规则后,我们会发现这是一个 SQL问题,甚至与PDO无关。并且,从SQL的角度来看,它是否正确地被问到,答案会立即出现 - DISTINCT

$num = $db->query('SELECT count(distinct boele) FROM tbl WHERE oele = 2')->fetchColumn();

是这个特定问题的正确答案。

从上述规则的角度来看,开幕式海报自己的解决方案也是可以接受的,但总的来说效率较低。

答案 10 :(得分:-1)

有两种方法可以计算行数。

$query = "SELECT count(*) as total from table1";
$prepare = $link->prepare($query);
$prepare->execute();
$row = $prepare->fetch(PDO::FETCH_ASSOC);
echo $row['total']; // This will return you a number of rows.

或者第二种方式是

$query = "SELECT field1, field2 from table1";
$prepare = $link->prepare($query);
$prepare->execute();
$row = $prepare->fetch(PDO::FETCH_NUM);
echo $rows[0]; // This will return you a number of rows as well.