web-dev-qa-db-fra.com

Comment obtenir le nombre total de lignes d'une requête GROUP BY?

Du manuel PDO:

PDOStatement :: rowCount () renvoie le nombre de lignes affecté par la dernière instruction DELETE, INSERT ou UPDATE exécutée par l'objet PDOStatement correspondant.

Si la dernière instruction SQL exécutée par le PDOStatement associé était une instruction SÉLECTIONNEZ , certaines bases de données peuvent renvoyer le nombre de lignes renvoyées par cette instruction =. Cependant, ce comportement est non garanti pour toutes les bases de données et ne doit pas être utilisé pour les applications portables.

Je ne l'ai découvert que très récemment. Je venais de changer ma couche d'abstraction db pour ne plus utiliser SELECT COUNT(1) ..., car simplement interroger les lignes réelles puis compter le résultat serait beaucoup plus efficace. Et maintenant PDO ne supporte pas ça!?

Je n'utilise pas PDO pour MySQL et PgSQL, mais je l'utilise pour SQLite. Existe-t-il un moyen (sans changer complètement le balourd) de compter des lignes comme celle-ci dans PDO? Dans MySQL, ce serait quelque chose comme ceci:

$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

Avec les pilotes MySQLi et PgSQL, c'est possible. Avec tous les AOP, ce n'est pas!?

PS. Ma solution initiale a été d'étendre la méthode SQLResult-> count (la mienne) pour remplacer SELECT ... FROM Par SELECT COUNT(1) FROM et simplement renvoyer ce nombre (très inefficace, mais uniquement pour SQLite PDO). Ce n'est pas suffisant, car dans l'exemple de requête ci-dessus, il y a un GROUP BY, Ce qui changerait la signification/la fonction de la COUNT(1).

44
Rudie

La méthode que j'ai fini par utiliser est très simple:

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

Ce n'est peut-être pas le plus efficace, mais il semble être infaillible, car il compte en fait les résultats de la requête d'origine.

8
Rudie

Voici la solution pour vous

$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];
44
RoboTamer

C'est un peu inefficace en mémoire, mais si vous utilisez les données de toute façon, j'utilise souvent ceci:

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

Je n'utilise pas PDO pour MySQL et PgSQL, mais je l'utilise pour SQLite. Existe-t-il un moyen (sans changer complètement le balourd) de compter des lignes comme celle-ci dans PDO?

Conformément à ce commentaire , le problème SQLite a été introduit par un changement d'API dans 3.x.

Cela dit, vous souhaiterez peut-être vérifier comment PDO implémente réellement la fonctionnalité avant de l'utiliser.

Je ne connais pas ses composants internes, mais je me méfierais de l'idée que PDO analyse votre SQL (car une erreur de syntaxe SQL apparaîtrait dans les journaux de la base de données) et encore moins essaie d'en donner le moindre sens afin de compter les lignes en utilisant une stratégie optimale.

En supposant que ce ne soit pas le cas, les stratégies réalistes pour qu'il renvoie un nombre de toutes les lignes applicables dans une instruction select incluent la manipulation de chaîne de la clause limit hors de votre instruction SQL, et l'une des options suivantes:

  1. Exécuter un décompte sélectionné () en tant que sous-requête (évitant ainsi le problème que vous avez décrit dans votre PS);
  2. Ouvrir un curseur, exécuter tout récupérer et compter les lignes; ou
  3. Après avoir ouvert un tel curseur en premier lieu, et compter de la même manière les lignes restantes.

Cependant, une bien meilleure façon de compter serait d'exécuter la requête entièrement optimisée qui le fera. Le plus souvent, cela signifie réécrire des morceaux significatifs de la requête initiale que vous essayez de paginer - supprimer les champs inutiles et les classer par opérations, etc.

Enfin, si vos ensembles de données sont suffisamment volumineux pour prendre en compte tout type de décalage, vous pouvez également envisager de renvoyer l'estimation dérivée de statistiques à la place et/ou de mettre en cache périodiquement le résultat dans Memcache. À un moment donné, il n'est plus utile d'avoir des comptes exacts ...

4

Gardez à l'esprit qu'un PDOStatement est Traversable. Étant donné une requête:

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

Il peut être répété sur:

$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>';
}

Ou vous pouvez faire quelque chose comme ça:

$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>';
}
2
Nev Stokes

Si vous êtes prêt à renoncer à un soupçon d'abstraction, vous pouvez utiliser une classe wrapper personnalisée qui transmet tout simplement au PDO. Disons quelque chose comme ceci: (Attention, code non testé)

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 );
    }
}
2
cwallenpoole

Peut-être que cela fera l'affaire pour vous?

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

Qu'en est-il de placer les résultats de la requête dans un tableau, où vous pouvez faire un compte ($ array) et utiliser les lignes résultantes de la requête après? Exemple:

$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'];}
0
XaviQV

C'est encore une autre question qui, mal posée, engendre BEAUCOUP de solutions terribles, rendant toutes les choses terriblement compliquées pour résoudre un problème inexistant.

La règle extrêmement simple et évidente pour toute interaction de base de données est

Sélectionnez toujours les seules données dont vous avez besoin.

De ce point de vue, la question est fausse et la réponse acceptée est correcte. Mais d'autres solutions proposées sont tout simplement terribles.

La question est de savoir "comment obtenir le décompte dans le mauvais sens". Il ne faut jamais y répondre directement, mais à la place, la seule bonne réponse est "Il ne faut jamais sélectionner les lignes pour les compter. Au lieu de cela, demandez TOUJOURS à la base de données de compter les lignes pour vous." Cette règle est si évidente, qu'il est tout simplement improbable de voir autant de tentatives pour la briser.

Après avoir appris cette règle, nous verrions qu'il s'agit d'un question SQL, même pas lié à PDO. Et, s'il était demandé correctement, du point de vue SQL, la réponse apparaîtrait en un instant - DISTINCT.

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

est la bonne réponse à cette question particulière.

La propre solution de l'affiche d'ouverture est également acceptable du point de vue de la règle susmentionnée, mais serait moins efficace en termes généraux.

0
Your Common Sense

Vous devez utiliser rowCount - Renvoie le nombre de lignes affectées par la dernière instruction SQL

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