2
0
Просмотр исходного кода

ZF-5295: DISTINCT with multiple columns should use a subquery so it can work on most databases.

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@16149 44c647ce-9c0f-0410-b52a-842ac1e357ba
norm2782 16 лет назад
Родитель
Сommit
f7d606cfa6

+ 29 - 18
library/Zend/Paginator/Adapter/DbSelect.php

@@ -180,9 +180,10 @@ class Zend_Paginator_Adapter_DbSelect implements Zend_Paginator_Adapter_Interfac
         $db = $rowCount->getAdapter();
 
         $countColumn = $db->quoteIdentifier($db->foldCase(self::ROW_COUNT_COLUMN));
+        $countPart = 'COUNT(1) AS ';
 
         if ($rowCount->getPart(Zend_Db_Select::UNION) != array()) {
-            $expression = new Zend_Db_Expr('COUNT(*) AS ' . $countColumn);
+            $expression = new Zend_Db_Expr($countPart . $countColumn);
 
             $rowCount = $db->select()->from($rowCount, $expression);
         } else {
@@ -190,44 +191,54 @@ class Zend_Paginator_Adapter_DbSelect implements Zend_Paginator_Adapter_Interfac
              * The DISTINCT and GROUP BY queries only work when selecting one column.
              * The question is whether any RDBMS supports DISTINCT for multiple columns, without workarounds.
              */
-            if (true === $rowCount->getPart(Zend_Db_Select::DISTINCT)) {
+            if ($rowCount->getPart(Zend_Db_Select::DISTINCT) === true) {
+                $groupPart = null;
+
                 $columnParts = $rowCount->getPart(Zend_Db_Select::COLUMNS);
 
-                $columns = array();
+                /**
+                 * If this is a DISTINCT query with multiple columns, then turn
+                 * the original query into a subquery of the COUNT query.
+                 */
+                if (count($columnParts) > 1) {
+                    $rowCount->__toString();
+                    $rowCount->reset(Zend_Db_Select::FROM);
+                    $rowCount->from($this->_select);
+                } else {
+                    $part = $columnParts[0];
 
-                foreach ($columnParts as $part) {
-                    if ($part[1] == Zend_Db_Select::SQL_WILDCARD || $part[1] instanceof Zend_Db_Expr) {
-                        $columns[] = $part[1];
-                    } else {
+                    if ($part[1] !== Zend_Db_Select::SQL_WILDCARD && !($part[1] instanceof Zend_Db_Expr)) {
                         $column = $db->quoteIdentifier($part[1], true);
 
                         if (!empty($part[0])) {
                             $column = $db->quoteIdentifier($part[0], true) . '.' . $column;
                         }
 
-                        $columns[] = $column;
+                        $groupPart = $column;
                     }
                 }
-
-                if (count($columns) == 1 && $columns[0] == Zend_Db_Select::SQL_WILDCARD) {
-                    $groupPart = null;
-                } else {
-                    $groupPart = implode(',', $columns);
-                }
             } else {
+                /**
+                 * TODO: If there is a GROUP BY clause with one column, do a
+                 * COUNT(DISTINCT column1). If there are more than one columns
+                 * take the subquery approach.
+                 */
                 $groupParts = $rowCount->getPart(Zend_Db_Select::GROUP);
 
-                foreach ($groupParts as &$part) {
+                foreach ($groupParts as $key => $part) {
                     if (!($part == Zend_Db_Select::SQL_WILDCARD || $part instanceof Zend_Db_Expr)) {
-                        $part = $db->quoteIdentifier($part, true);
+                        $groupPats[$key] = $db->quoteIdentifier($part, true);
                     }
                 }
 
                 $groupPart = implode(',', $groupParts);
             }
 
-            $countPart  = empty($groupPart) ? 'COUNT(*)' : 'COUNT(DISTINCT ' . $groupPart . ')';
-            $expression = new Zend_Db_Expr($countPart . ' AS ' . $countColumn);
+            if (!empty($groupPart)) {
+                $countPart = 'COUNT(DISTINCT ' . $groupPart . ') AS ';
+            }
+
+            $expression = new Zend_Db_Expr($countPart . $countColumn);
 
             $rowCount->__toString(); // Workaround for ZF-3719 and related
             $rowCount->reset(Zend_Db_Select::COLUMNS)

+ 34 - 2
tests/Zend/Paginator/Adapter/DbSelectTest.php

@@ -370,8 +370,40 @@ class Zend_Paginator_Adapter_DbSelectTest extends PHPUnit_Framework_TestCase
 
         $adapter = new Zend_Paginator_Adapter_DbSelect($union);
 
-        $expected = 'SELECT COUNT(*) AS "zend_paginator_row_count" FROM (SELECT "test".* FROM "test" WHERE (number <= 250) UNION SELECT "test".* FROM "test" WHERE (number > 250)) AS "t"';
+        $expected = 'SELECT COUNT(1) AS "zend_paginator_row_count" FROM (SELECT "test".* FROM "test" WHERE (number <= 250) UNION SELECT "test".* FROM "test" WHERE (number > 250)) AS "t"';
 
         $this->assertEquals($expected, $adapter->getCountSelect()->__toString());
     }
-}
+
+    /**
+     * @group ZF-5295
+     */
+    public function testMultipleDistinctColumns()
+    {
+        $select = $this->_db->select()->from('test', array('testgroup', 'number'))
+                                      ->distinct(true);
+
+        $adapter = new Zend_Paginator_Adapter_DbSelect($select);
+
+        $expected = 'SELECT COUNT(1) AS "zend_paginator_row_count" FROM (SELECT DISTINCT "test"."testgroup", "test"."number" FROM "test") AS "t"';
+
+        $this->assertEquals($expected, $adapter->getCountSelect()->__toString());
+        $this->assertEquals(500, $adapter->count());
+    }
+
+    /**
+     * @group ZF-5295
+     */
+    public function testSingleDistinctColumn()
+    {
+        $select = $this->_db->select()->from('test', 'testgroup')
+                                      ->distinct(true);
+
+        $adapter = new Zend_Paginator_Adapter_DbSelect($select);
+
+        $expected = 'SELECT COUNT(DISTINCT "test"."testgroup") AS "zend_paginator_row_count" FROM "test"';
+
+        $this->assertEquals($expected, $adapter->getCountSelect()->__toString());
+        $this->assertEquals(2, $adapter->count());
+    }
+}