SelectSubqueryTest.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. namespace Drupal\KernelTests\Core\Database;
  3. /**
  4. * Tests the Select query builder.
  5. *
  6. * @group Database
  7. */
  8. class SelectSubqueryTest extends DatabaseTestBase {
  9. /**
  10. * Tests that we can use a subquery in a FROM clause.
  11. */
  12. public function testFromSubquerySelect() {
  13. // Create a subquery, which is just a normal query object.
  14. $subquery = db_select('test_task', 'tt');
  15. $subquery->addField('tt', 'pid', 'pid');
  16. $subquery->addField('tt', 'task', 'task');
  17. $subquery->condition('priority', 1);
  18. for ($i = 0; $i < 2; $i++) {
  19. // Create another query that joins against the virtual table resulting
  20. // from the subquery.
  21. $select = db_select($subquery, 'tt2');
  22. $select->join('test', 't', 't.id=tt2.pid');
  23. $select->addField('t', 'name');
  24. if ($i) {
  25. // Use a different number of conditions here to confuse the subquery
  26. // placeholder counter, testing https://www.drupal.org/node/1112854.
  27. $select->condition('name', 'John');
  28. }
  29. $select->condition('task', 'code');
  30. // The resulting query should be equivalent to:
  31. // SELECT t.name
  32. // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt WHERE priority=1) tt
  33. // INNER JOIN test t ON t.id=tt.pid
  34. // WHERE tt.task = 'code'
  35. $people = $select->execute()->fetchCol();
  36. $this->assertCount(1, $people, 'Returned the correct number of rows.');
  37. }
  38. }
  39. /**
  40. * Tests that we can use a subquery in a FROM clause with a LIMIT.
  41. */
  42. public function testFromSubquerySelectWithLimit() {
  43. // Create a subquery, which is just a normal query object.
  44. $subquery = db_select('test_task', 'tt');
  45. $subquery->addField('tt', 'pid', 'pid');
  46. $subquery->addField('tt', 'task', 'task');
  47. $subquery->orderBy('priority', 'DESC');
  48. $subquery->range(0, 1);
  49. // Create another query that joins against the virtual table resulting
  50. // from the subquery.
  51. $select = db_select($subquery, 'tt2');
  52. $select->join('test', 't', 't.id=tt2.pid');
  53. $select->addField('t', 'name');
  54. // The resulting query should be equivalent to:
  55. // SELECT t.name
  56. // FROM (SELECT tt.pid AS pid, tt.task AS task FROM test_task tt ORDER BY priority DESC LIMIT 1 OFFSET 0) tt
  57. // INNER JOIN test t ON t.id=tt.pid
  58. $people = $select->execute()->fetchCol();
  59. $this->assertCount(1, $people, 'Returned the correct number of rows.');
  60. }
  61. /**
  62. * Tests that we can use a subquery with an IN operator in a WHERE clause.
  63. */
  64. public function testConditionSubquerySelect() {
  65. // Create a subquery, which is just a normal query object.
  66. $subquery = db_select('test_task', 'tt');
  67. $subquery->addField('tt', 'pid', 'pid');
  68. $subquery->condition('tt.priority', 1);
  69. // Create another query that joins against the virtual table resulting
  70. // from the subquery.
  71. $select = db_select('test_task', 'tt2');
  72. $select->addField('tt2', 'task');
  73. $select->condition('tt2.pid', $subquery, 'IN');
  74. // The resulting query should be equivalent to:
  75. // SELECT tt2.name
  76. // FROM test tt2
  77. // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1)
  78. $people = $select->execute()->fetchCol();
  79. $this->assertCount(5, $people, 'Returned the correct number of rows.');
  80. }
  81. /**
  82. * Test that we can use a subquery with a relational operator in a WHERE clause.
  83. */
  84. public function testConditionSubquerySelect2() {
  85. // Create a subquery, which is just a normal query object.
  86. $subquery = db_select('test', 't2');
  87. $subquery->addExpression('AVG(t2.age)');
  88. // Create another query that adds a clause using the subquery.
  89. $select = db_select('test', 't');
  90. $select->addField('t', 'name');
  91. $select->condition('t.age', $subquery, '<');
  92. // The resulting query should be equivalent to:
  93. // SELECT t.name
  94. // FROM test t
  95. // WHERE t.age < (SELECT AVG(t2.age) FROM test t2)
  96. $people = $select->execute()->fetchCol();
  97. $this->assertEquals(['John', 'Paul'], $people, 'Returned Paul and John.', 0.0, 10, TRUE);
  98. }
  99. /**
  100. * Test that we can use 2 subqueries with a relational operator in a WHERE clause.
  101. */
  102. public function testConditionSubquerySelect3() {
  103. // Create subquery 1, which is just a normal query object.
  104. $subquery1 = db_select('test_task', 'tt');
  105. $subquery1->addExpression('AVG(tt.priority)');
  106. $subquery1->where('tt.pid = t.id');
  107. // Create subquery 2, which is just a normal query object.
  108. $subquery2 = db_select('test_task', 'tt2');
  109. $subquery2->addExpression('AVG(tt2.priority)');
  110. // Create another query that adds a clause using the subqueries.
  111. $select = db_select('test', 't');
  112. $select->addField('t', 'name');
  113. $select->condition($subquery1, $subquery2, '>');
  114. // The resulting query should be equivalent to:
  115. // SELECT t.name
  116. // FROM test t
  117. // WHERE (SELECT AVG(tt.priority) FROM test_task tt WHERE tt.pid = t.id) > (SELECT AVG(tt2.priority) FROM test_task tt2)
  118. $people = $select->execute()->fetchCol();
  119. $this->assertEquals(['John'], $people, 'Returned John.', 0.0, 10, TRUE);
  120. }
  121. /**
  122. * Test that we can use multiple subqueries.
  123. *
  124. * This test uses a subquery at the left hand side and multiple subqueries at
  125. * the right hand side. The test query may not be that logical but that's due
  126. * to the limited amount of data and tables. 'Valid' use cases do exist :)
  127. */
  128. public function testConditionSubquerySelect4() {
  129. // Create subquery 1, which is just a normal query object.
  130. $subquery1 = db_select('test_task', 'tt');
  131. $subquery1->addExpression('AVG(tt.priority)');
  132. $subquery1->where('tt.pid = t.id');
  133. // Create subquery 2, which is just a normal query object.
  134. $subquery2 = db_select('test_task', 'tt2');
  135. $subquery2->addExpression('MIN(tt2.priority)');
  136. $subquery2->where('tt2.pid <> t.id');
  137. // Create subquery 3, which is just a normal query object.
  138. $subquery3 = db_select('test_task', 'tt3');
  139. $subquery3->addExpression('AVG(tt3.priority)');
  140. $subquery3->where('tt3.pid <> t.id');
  141. // Create another query that adds a clause using the subqueries.
  142. $select = db_select('test', 't');
  143. $select->addField('t', 'name');
  144. $select->condition($subquery1, [$subquery2, $subquery3], 'BETWEEN');
  145. // The resulting query should be equivalent to:
  146. // SELECT t.name AS name
  147. // FROM {test} t
  148. // WHERE (SELECT AVG(tt.priority) AS expression FROM {test_task} tt WHERE (tt.pid = t.id))
  149. // BETWEEN (SELECT MIN(tt2.priority) AS expression FROM {test_task} tt2 WHERE (tt2.pid <> t.id))
  150. // AND (SELECT AVG(tt3.priority) AS expression FROM {test_task} tt3 WHERE (tt3.pid <> t.id));
  151. $people = $select->execute()->fetchCol();
  152. $this->assertEquals(['George', 'Paul'], $people, 'Returned George and Paul.', 0.0, 10, TRUE);
  153. }
  154. /**
  155. * Tests that we can use a subquery in a JOIN clause.
  156. */
  157. public function testJoinSubquerySelect() {
  158. // Create a subquery, which is just a normal query object.
  159. $subquery = db_select('test_task', 'tt');
  160. $subquery->addField('tt', 'pid', 'pid');
  161. $subquery->condition('priority', 1);
  162. // Create another query that joins against the virtual table resulting
  163. // from the subquery.
  164. $select = db_select('test', 't');
  165. $select->join($subquery, 'tt', 't.id=tt.pid');
  166. $select->addField('t', 'name');
  167. // The resulting query should be equivalent to:
  168. // SELECT t.name
  169. // FROM test t
  170. // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid
  171. $people = $select->execute()->fetchCol();
  172. $this->assertCount(2, $people, 'Returned the correct number of rows.');
  173. }
  174. /**
  175. * Tests EXISTS subquery conditionals on SELECT statements.
  176. *
  177. * We essentially select all rows from the {test} table that have matching
  178. * rows in the {test_people} table based on the shared name column.
  179. */
  180. public function testExistsSubquerySelect() {
  181. // Put George into {test_people}.
  182. db_insert('test_people')
  183. ->fields([
  184. 'name' => 'George',
  185. 'age' => 27,
  186. 'job' => 'Singer',
  187. ])
  188. ->execute();
  189. // Base query to {test}.
  190. $query = db_select('test', 't')
  191. ->fields('t', ['name']);
  192. // Subquery to {test_people}.
  193. $subquery = db_select('test_people', 'tp')
  194. ->fields('tp', ['name'])
  195. ->where('tp.name = t.name');
  196. $query->exists($subquery);
  197. $result = $query->execute();
  198. // Ensure that we got the right record.
  199. $record = $result->fetch();
  200. $this->assertEquals('George', $record->name, 'Fetched name is correct using EXISTS query.');
  201. }
  202. /**
  203. * Tests NOT EXISTS subquery conditionals on SELECT statements.
  204. *
  205. * We essentially select all rows from the {test} table that don't have
  206. * matching rows in the {test_people} table based on the shared name column.
  207. */
  208. public function testNotExistsSubquerySelect() {
  209. // Put George into {test_people}.
  210. db_insert('test_people')
  211. ->fields([
  212. 'name' => 'George',
  213. 'age' => 27,
  214. 'job' => 'Singer',
  215. ])
  216. ->execute();
  217. // Base query to {test}.
  218. $query = db_select('test', 't')
  219. ->fields('t', ['name']);
  220. // Subquery to {test_people}.
  221. $subquery = db_select('test_people', 'tp')
  222. ->fields('tp', ['name'])
  223. ->where('tp.name = t.name');
  224. $query->notExists($subquery);
  225. // Ensure that we got the right number of records.
  226. $people = $query->execute()->fetchCol();
  227. $this->assertCount(3, $people, 'NOT EXISTS query returned the correct results.');
  228. }
  229. }