web-dev-qa-db-ja.com

GROUP BYクエリの行の総数を取得する方法は?

PDOマニュアルから:

PDOStatement :: rowCount()は、対応するPDOStatementオブジェクトによって実行されたDELETE、INSERT、またはUPDATEステートメントによって影響を受ける行数を返します。

関連するPDOStatementによって最後に実行されたSQLステートメントが[〜#〜] select [〜#〜]ステートメントであった場合、一部のデータベースはそのステートメントによって返された行数を返すことがあります =。ただし、この動作はすべてのデータベースで保証されているわけではありませんであり、移植可能なアプリケーションでは使用しないでください。

私はそれをごく最近発見しました。実際の行を調べてから結果をカウントする方がはるかに効率的であるため、db抽象化レイヤーをSELECT COUNT(1) ...を使用しないように変更しました。そして今、PDOはそれをサポートしていません!?

MySQLとPgSQLにはPDOを使用しませんが、SQLiteには使用します。 PDOでこのような行をカウントする方法はありますか(dbalを完全に元に戻すことなく)? 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)の意味/機能を変更するため、これでは十分ではありません。

44
Rudie

最終的に使用した方法は非常に簡単です。

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

最も効率的ではないかもしれませんが、実際には元のクエリの結果をカウントするため、確実に実行できるようです。

8
Rudie

ここにあなたのための解決策があります

$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

それは少しメモリ効率が悪いですが、とにかくデータを使用している場合、私はこれを頻繁に使用します:

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

MySQLとPgSQLにはPDOを使用しませんが、SQLiteには使用します。 PDOでこのような行をカウントする方法はありますか(dbalを完全に元に戻すことなく)?

このコメント に従って、SQLiteの問題は3.xでのAPIの変更によって導入されました。

とはいえ、PDOを使用する前に、PDOが実際にどのように機能を実装しているかを調べたいかもしれません。

私はその内部に精通していませんが、行をカウントするためにPDOがSQLの構文エラーがDBのログに表示されるため、PDOがそれを少しでも理解しようとするのは言うまでもありません最適な戦略を使用します。

実際にはそうではないと仮定すると、selectステートメントで該当するすべての行のカウントを返すための現実的な戦略には、SQLステートメントからの制限句の文字列操作、および次のいずれかが含まれます。

  1. サブクエリとしてselect count()を実行します(したがって、PSで説明した問題を回避します)。
  2. カーソルを開き、fetch allを実行して行をカウントします。または
  3. そのようなカーソルを最初に開き、残りの行を同様にカウントします。

ただし、より適切なカウント方法は、完全に最適化されたクエリを実行することです。多くの場合、これはページ分割しようとしている最初のクエリの意味のあるチャンクを書き換えることを意味します-不要なフィールドと操作による順序などを削除します.

最後に、データセットが十分に大きく、あらゆる種類のラグをカウントする場合は、代わりに statistics から派生した推定値を返すこと、および/または結果をMemcacheに定期的にキャッシュすることも検討できます。ある時点で、正確に正しいカウントを持つことはもはや有用ではありません...

4

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

抽象化のヒントをあきらめたい場合は、すべてを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 );
    }
}
2
cwallenpoole

たぶんこれはあなたのためにトリックをするでしょうか?

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

クエリ結果を配列に入れて、count($ 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'];}
0
XaviQV

それはさらに別の質問であり、間違って置くと、たくさんのひどい解決策が生まれ、存在しない問題を解決するためにすべてが非常に複雑になります。

データベースの相互作用に関する非常にシンプルで明白なルールは次のとおりです。

必要なデータのみを常に選択してください。

この観点から、質問は間違っており、受け入れられた答えは正しいです。しかし、他の提案されたソリューションはひどいものです。

問題は、「カウントを間違った方法で取得する方法」です。簡単に答えるべきではありませんが、代わりに、唯一の適切な答えは「行を選択してカウントしないことです。代わりに、常にデータベースに行のカウントを依頼してください」です。このルールは非常に明白であるため、非常に多くの試みがそれを破ろうとすることはありえません。

このルールを学習すると、これはSQLの質問であり、PDO関連でさえないことがわかります。そして、SQLの観点から適切に尋ねられた場合、答えはすぐに表示されます-DISTINCT

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

この特定の質問に対する正しい答えです。

前述の規則の観点からは、オープニングポスター自体のソリューションも受け入れられますが、一般的には効率が低下します。

0

RowCountを使用する必要があります—最後のSQLステートメントによって影響を受けた行の数を返します

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