Browse Source

Implementing DISTINCT ON for SQLServer

Jose Lorenzo Rodriguez 11 years ago
parent
commit
1325d41f1d

+ 52 - 1
src/Database/Dialect/SqlserverDialectTrait.php

@@ -72,7 +72,7 @@ trait SqlserverDialectTrait
             return $this->_pagingSubquery($query, $limit, $offset);
         }
 
-        return $query;
+        return $this->_transformDistinct($query);
     }
 
     /**
@@ -132,6 +132,57 @@ trait SqlserverDialectTrait
     }
 
     /**
+     * Returns the passed query after rewriting the DISTINCT clause, so that drivers
+     * that do not support the "ON" part can provide the actual way it should be done
+     *
+     * @param Query $original The query to be transformed
+     * @return Query
+     */
+    protected function _transformDistinct($original)
+    {
+        if (!is_array($original->clause('distinct'))) {
+            return $original;
+        }
+
+        $query = clone $original;
+        $distinct = $query->clause('distinct');
+        $query->distinct(false);
+
+        $order = new OrderByExpression($distinct);
+        $query
+            ->select(function ($q) use ($distinct, $order) {
+                $over = $q->newExpr('ROW_NUMBER() OVER')
+                    ->add('(PARTITION BY')
+                    ->add($q->newExpr()->add($distinct)->type(','))
+                    ->add($order)
+                    ->add(')')
+                    ->type(' ');
+                return [
+                    '_cake_distinct_pivot_' => $over
+                ];
+            })
+            ->limit(null)
+            ->offset(null)
+            ->order([], true);
+
+        $outer = new Query($query->connection());
+        $outer->select('*')
+            ->from(['_cake_distinct_' => $query])
+            ->where(['_cake_distinct_pivot_' => 1]);
+
+        // Decorate the original query as that is what the
+        // end developer will be calling execute() on originally.
+        $original->decorateResults(function ($row) {
+            if (isset($row['_cake_distinct_pivot_'])) {
+                unset($row['_cake_distinct_pivot_']);
+            }
+            return $row;
+        });
+
+        return $outer;
+    }
+
+    /**
      * Returns a dictionary of expressions to be transformed when compiling a Query
      * to SQL. Array keys are method names to be called in this class
      *

+ 5 - 0
tests/TestCase/Database/QueryTest.php

@@ -1479,6 +1479,11 @@ class QueryTest extends TestCase
             ->from(['a' => 'articles'])
             ->execute();
         $this->assertCount(2, $result);
+        $expected = [
+            ['id' => 1, 'author_id' => 1],
+            ['id' => 2, 'author_id' => 3],
+        ];
+        $this->assertEquals($expected, $result->fetchAll('assoc'));
     }
 
     /**