3 namespace Drupal\KernelTests\Core\Database;
6 * Tests the Select query builder.
10 class SelectSubqueryTest extends DatabaseTestBase {
13 * Tests that we can use a subquery in a FROM clause.
15 public function testFromSubquerySelect() {
16 // Create a subquery, which is just a normal query object.
17 $subquery = db_select('test_task', 'tt');
18 $subquery->addField('tt', 'pid', 'pid');
19 $subquery->addField('tt', 'task', 'task');
20 $subquery->condition('priority', 1);
22 for ($i = 0; $i < 2; $i++) {
23 // Create another query that joins against the virtual table resulting
25 $select = db_select($subquery, 'tt2');
26 $select->join('test', 't', 't.id=tt2.pid');
27 $select->addField('t', 'name');
29 // Use a different number of conditions here to confuse the subquery
30 // placeholder counter, testing https://www.drupal.org/node/1112854.
31 $select->condition('name', 'John');
33 $select->condition('task', 'code');
35 // The resulting query should be equivalent to:
37 // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
38 // INNER JOIN test t ON t.id=tt.pid
39 // WHERE tt.task = 'code'
40 $people = $select->execute()->fetchCol();
42 $this->assertCount(1, $people, 'Returned the correct number of rows.');
47 * Tests that we can use a subquery in a FROM clause with a LIMIT.
49 public function testFromSubquerySelectWithLimit() {
50 // Create a subquery, which is just a normal query object.
51 $subquery = db_select('test_task', 'tt');
52 $subquery->addField('tt', 'pid', 'pid');
53 $subquery->addField('tt', 'task', 'task');
54 $subquery->orderBy('priority', 'DESC');
55 $subquery->range(0, 1);
57 // Create another query that joins against the virtual table resulting
59 $select = db_select($subquery, 'tt2');
60 $select->join('test', 't', 't.id=tt2.pid');
61 $select->addField('t', 'name');
63 // The resulting query should be equivalent to:
65 // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
66 // INNER JOIN test t ON t.id=tt.pid
67 $people = $select->execute()->fetchCol();
69 $this->assertCount(1, $people, 'Returned the correct number of rows.');
73 * Tests that we can use a subquery with an IN operator in a WHERE clause.
75 public function testConditionSubquerySelect() {
76 // Create a subquery, which is just a normal query object.
77 $subquery = db_select('test_task', 'tt');
78 $subquery->addField('tt', 'pid', 'pid');
79 $subquery->condition('tt.priority', 1);
81 // Create another query that joins against the virtual table resulting
83 $select = db_select('test_task', 'tt2');
84 $select->addField('tt2', 'task');
85 $select->condition('tt2.pid', $subquery, 'IN');
87 // The resulting query should be equivalent to:
90 // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
91 $people = $select->execute()->fetchCol();
92 $this->assertCount(5, $people, 'Returned the correct number of rows.');
96 * Test that we can use a subquery with a relational operator in a WHERE clause.
98 public function testConditionSubquerySelect2() {
99 // Create a subquery, which is just a normal query object.
100 $subquery = db_select('test', 't2');
101 $subquery->addExpression('AVG(t2.age)');
103 // Create another query that adds a clause using the subquery.
104 $select = db_select('test', 't');
105 $select->addField('t', 'name');
106 $select->condition('t.age', $subquery, '<');
108 // The resulting query should be equivalent to:
111 // WHERE t.age < (SELECT AVG(t2.age) FROM test t2)
112 $people = $select->execute()->fetchCol();
113 $this->assertEquals(['John', 'Paul'], $people, 'Returned Paul and John.', 0.0, 10, TRUE);
117 * Test that we can use 2 subqueries with a relational operator in a WHERE clause.
119 public function testConditionSubquerySelect3() {
120 // Create subquery 1, which is just a normal query object.
121 $subquery1 = db_select('test_task', 'tt');
122 $subquery1->addExpression('AVG(tt.priority)');
123 $subquery1->where('tt.pid = t.id');
125 // Create subquery 2, which is just a normal query object.
126 $subquery2 = db_select('test_task', 'tt2');
127 $subquery2->addExpression('AVG(tt2.priority)');
129 // Create another query that adds a clause using the subqueries.
130 $select = db_select('test', 't');
131 $select->addField('t', 'name');
132 $select->condition($subquery1, $subquery2, '>');
134 // The resulting query should be equivalent to:
137 // WHERE (SELECT AVG(tt.priority) FROM test_task tt WHERE tt.pid = t.id) > (SELECT AVG(tt2.priority) FROM test_task tt2)
138 $people = $select->execute()->fetchCol();
139 $this->assertEquals(['John'], $people, 'Returned John.', 0.0, 10, TRUE);
143 * Test that we can use multiple subqueries.
145 * This test uses a subquery at the left hand side and multiple subqueries at
146 * the right hand side. The test query may not be that logical but that's due
147 * to the limited amount of data and tables. 'Valid' use cases do exist :)
149 public function testConditionSubquerySelect4() {
150 // Create subquery 1, which is just a normal query object.
151 $subquery1 = db_select('test_task', 'tt');
152 $subquery1->addExpression('AVG(tt.priority)');
153 $subquery1->where('tt.pid = t.id');
155 // Create subquery 2, which is just a normal query object.
156 $subquery2 = db_select('test_task', 'tt2');
157 $subquery2->addExpression('MIN(tt2.priority)');
158 $subquery2->where('tt2.pid <> t.id');
160 // Create subquery 3, which is just a normal query object.
161 $subquery3 = db_select('test_task', 'tt3');
162 $subquery3->addExpression('AVG(tt3.priority)');
163 $subquery3->where('tt3.pid <> t.id');
165 // Create another query that adds a clause using the subqueries.
166 $select = db_select('test', 't');
167 $select->addField('t', 'name');
168 $select->condition($subquery1, [$subquery2, $subquery3], 'BETWEEN');
170 // The resulting query should be equivalent to:
171 // SELECT t.name AS name
173 // WHERE (SELECT AVG(tt.priority) AS expression FROM {test_task} tt WHERE (tt.pid = t.id))
174 // BETWEEN (SELECT MIN(tt2.priority) AS expression FROM {test_task} tt2 WHERE (tt2.pid <> t.id))
175 // AND (SELECT AVG(tt3.priority) AS expression FROM {test_task} tt3 WHERE (tt3.pid <> t.id));
176 $people = $select->execute()->fetchCol();
177 $this->assertEquals(['George', 'Paul'], $people, 'Returned George and Paul.', 0.0, 10, TRUE);
181 * Tests that we can use a subquery in a JOIN clause.
183 public function testJoinSubquerySelect() {
184 // Create a subquery, which is just a normal query object.
185 $subquery = db_select('test_task', 'tt');
186 $subquery->addField('tt', 'pid', 'pid');
187 $subquery->condition('priority', 1);
189 // Create another query that joins against the virtual table resulting
190 // from the subquery.
191 $select = db_select('test', 't');
192 $select->join($subquery, 'tt', 't.id=tt.pid');
193 $select->addField('t', 'name');
195 // The resulting query should be equivalent to:
198 // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
199 $people = $select->execute()->fetchCol();
201 $this->assertCount(2, $people, 'Returned the correct number of rows.');
205 * Tests EXISTS subquery conditionals on SELECT statements.
207 * We essentially select all rows from the {test} table that have matching
208 * rows in the {test_people} table based on the shared name column.
210 public function testExistsSubquerySelect() {
211 // Put George into {test_people}.
212 db_insert('test_people')
219 // Base query to {test}.
220 $query = db_select('test', 't')
221 ->fields('t', ['name']);
222 // Subquery to {test_people}.
223 $subquery = db_select('test_people', 'tp')
224 ->fields('tp', ['name'])
225 ->where('tp.name = t.name');
226 $query->exists($subquery);
227 $result = $query->execute();
229 // Ensure that we got the right record.
230 $record = $result->fetch();
231 $this->assertEquals('George', $record->name, 'Fetched name is correct using EXISTS query.');
235 * Tests NOT EXISTS subquery conditionals on SELECT statements.
237 * We essentially select all rows from the {test} table that don't have
238 * matching rows in the {test_people} table based on the shared name column.
240 public function testNotExistsSubquerySelect() {
241 // Put George into {test_people}.
242 db_insert('test_people')
250 // Base query to {test}.
251 $query = db_select('test', 't')
252 ->fields('t', ['name']);
253 // Subquery to {test_people}.
254 $subquery = db_select('test_people', 'tp')
255 ->fields('tp', ['name'])
256 ->where('tp.name = t.name');
257 $query->notExists($subquery);
259 // Ensure that we got the right number of records.
260 $people = $query->execute()->fetchCol();
261 $this->assertCount(3, $people, 'NOT EXISTS query returned the correct results.');