diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 6054b71115..9799339b29 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -82,6 +82,7 @@ Yii Framework 2 Change Log - Enh #2051: Do not save null data into database when using RBAC (qiangxue) - Enh #2101: Gii is now using model labels when generating search (thiagotalma) - Enh #2103: Renamed AccessDeniedHttpException to ForbiddenHttpException, added new commonly used HTTP exception classes (danschmidt5189) +- Enh #2124: Added support for UNION ALL queries (Ivan Pomortsev, iworker) - Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) - Enh: Support for file aliases in console command 'message' (omnilight) diff --git a/framework/db/Query.php b/framework/db/Query.php index 2039100ed7..9a42009495 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -635,11 +635,12 @@ class Query extends Component implements QueryInterface /** * Appends a SQL statement using UNION operator. * @param string|Query $sql the SQL statement to be appended using UNION + * @param bool $all TRUE if using UNION ALL and FALSE if using UNION * @return static the query object itself */ - public function union($sql) + public function union($sql, $all = false) { - $this->union[] = $sql; + $this->union[] = [ 'query' => $sql, 'all' => $all ]; return $this; } diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php index 7cadc1d6fc..4c51c28cc2 100644 --- a/framework/db/QueryBuilder.php +++ b/framework/db/QueryBuilder.php @@ -739,16 +739,23 @@ class QueryBuilder extends \yii\base\Object if (empty($unions)) { return ''; } + + $result = ''; + foreach ($unions as $i => $union) { - if ($union instanceof Query) { + $query = $union['query']; + if ($query instanceof Query) { // save the original parameters so that we can restore them later to prevent from modifying the query object - $originalParams = $union->params; - $union->addParams($params); - list ($unions[$i], $params) = $this->build($union); - $union->params = $originalParams; + $originalParams = $query->params; + $query->addParams($params); + list ($unions[$i]['query'], $params) = $this->build($query); + $query->params = $originalParams; } + + $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) '; } - return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)"; + + return trim($result); } /** diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 3e18bb6136..d9f9960920 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -215,4 +215,30 @@ class QueryBuilderTest extends DatabaseTestCase $this->assertEquals($expectedQueryParams, $queryParams); } */ + + /* + This test contains three select queries connected with UNION and UNION ALL constructions. + It could be useful to use "phpunit --group=db --filter testBuildUnion" command for run it. + + public function testBuildUnion() + { + $expectedQuerySql = "SELECT `id` FROM `TotalExample` `t1` WHERE (w > 0) AND (x < 2) UNION ( SELECT `id` FROM `TotalTotalExample` `t2` WHERE w > 5 ) UNION ALL ( SELECT `id` FROM `TotalTotalExample` `t3` WHERE w = 3 )"; + $query = new Query(); + $secondQuery = new Query(); + $secondQuery->select('id') + ->from('TotalTotalExample t2') + ->where('w > 5'); + $thirdQuery = new Query(); + $thirdQuery->select('id') + ->from('TotalTotalExample t3') + ->where('w = 3'); + $query->select('id') + ->from('TotalExample t1') + ->where(['and', 'w > 0', 'x < 2']) + ->union($secondQuery) + ->union($thirdQuery, TRUE); + list($actualQuerySql, $queryParams) = $this->getQueryBuilder()->build($query); + $this->assertEquals($expectedQuerySql, $actualQuerySql); + }*/ + }