/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php

https://github.com/markn86/moodle · PHP · 608 lines · 197 code · 38 blank · 373 comment · 21 complexity · 3deda767e78a81378cb29b5ebd4b30a0 MD5 · raw file

  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Calculation;
  3. class Database
  4. {
  5. /**
  6. * fieldExtract.
  7. *
  8. * Extracts the column ID to use for the data field.
  9. *
  10. * @param mixed[] $database The range of cells that makes up the list or database.
  11. * A database is a list of related data in which rows of related
  12. * information are records, and columns of data are fields. The
  13. * first row of the list contains labels for each column.
  14. * @param mixed $field Indicates which column is used in the function. Enter the
  15. * column label enclosed between double quotation marks, such as
  16. * "Age" or "Yield," or a number (without quotation marks) that
  17. * represents the position of the column within the list: 1 for
  18. * the first column, 2 for the second column, and so on.
  19. *
  20. * @return null|string
  21. */
  22. private static function fieldExtract($database, $field)
  23. {
  24. $field = strtoupper(Functions::flattenSingleValue($field));
  25. $fieldNames = array_map('strtoupper', array_shift($database));
  26. if (is_numeric($field)) {
  27. $keys = array_keys($fieldNames);
  28. return $keys[$field - 1];
  29. }
  30. $key = array_search($field, $fieldNames);
  31. return ($key) ? $key : null;
  32. }
  33. /**
  34. * filter.
  35. *
  36. * Parses the selection criteria, extracts the database rows that match those criteria, and
  37. * returns that subset of rows.
  38. *
  39. * @param mixed[] $database The range of cells that makes up the list or database.
  40. * A database is a list of related data in which rows of related
  41. * information are records, and columns of data are fields. The
  42. * first row of the list contains labels for each column.
  43. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  44. * You can use any range for the criteria argument, as long as it
  45. * includes at least one column label and at least one cell below
  46. * the column label in which you specify a condition for the
  47. * column.
  48. *
  49. * @return array of mixed
  50. */
  51. private static function filter($database, $criteria)
  52. {
  53. $fieldNames = array_shift($database);
  54. $criteriaNames = array_shift($criteria);
  55. // Convert the criteria into a set of AND/OR conditions with [:placeholders]
  56. $testConditions = $testValues = [];
  57. $testConditionsCount = 0;
  58. foreach ($criteriaNames as $key => $criteriaName) {
  59. $testCondition = [];
  60. $testConditionCount = 0;
  61. foreach ($criteria as $row => $criterion) {
  62. if ($criterion[$key] > '') {
  63. $testCondition[] = '[:' . $criteriaName . ']' . Functions::ifCondition($criterion[$key]);
  64. ++$testConditionCount;
  65. }
  66. }
  67. if ($testConditionCount > 1) {
  68. $testConditions[] = 'OR(' . implode(',', $testCondition) . ')';
  69. ++$testConditionsCount;
  70. } elseif ($testConditionCount == 1) {
  71. $testConditions[] = $testCondition[0];
  72. ++$testConditionsCount;
  73. }
  74. }
  75. if ($testConditionsCount > 1) {
  76. $testConditionSet = 'AND(' . implode(',', $testConditions) . ')';
  77. } elseif ($testConditionsCount == 1) {
  78. $testConditionSet = $testConditions[0];
  79. }
  80. // Loop through each row of the database
  81. foreach ($database as $dataRow => $dataValues) {
  82. // Substitute actual values from the database row for our [:placeholders]
  83. $testConditionList = $testConditionSet;
  84. foreach ($criteriaNames as $key => $criteriaName) {
  85. $k = array_search($criteriaName, $fieldNames);
  86. if (isset($dataValues[$k])) {
  87. $dataValue = $dataValues[$k];
  88. $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
  89. $testConditionList = str_replace('[:' . $criteriaName . ']', $dataValue, $testConditionList);
  90. }
  91. }
  92. // evaluate the criteria against the row data
  93. $result = Calculation::getInstance()->_calculateFormulaValue('=' . $testConditionList);
  94. // If the row failed to meet the criteria, remove it from the database
  95. if (!$result) {
  96. unset($database[$dataRow]);
  97. }
  98. }
  99. return $database;
  100. }
  101. private static function getFilteredColumn($database, $field, $criteria)
  102. {
  103. // reduce the database to a set of rows that match all the criteria
  104. $database = self::filter($database, $criteria);
  105. // extract an array of values for the requested column
  106. $colData = [];
  107. foreach ($database as $row) {
  108. $colData[] = $row[$field];
  109. }
  110. return $colData;
  111. }
  112. /**
  113. * DAVERAGE.
  114. *
  115. * Averages the values in a column of a list or database that match conditions you specify.
  116. *
  117. * Excel Function:
  118. * DAVERAGE(database,field,criteria)
  119. *
  120. * @param mixed[] $database The range of cells that makes up the list or database.
  121. * A database is a list of related data in which rows of related
  122. * information are records, and columns of data are fields. The
  123. * first row of the list contains labels for each column.
  124. * @param int|string $field Indicates which column is used in the function. Enter the
  125. * column label enclosed between double quotation marks, such as
  126. * "Age" or "Yield," or a number (without quotation marks) that
  127. * represents the position of the column within the list: 1 for
  128. * the first column, 2 for the second column, and so on.
  129. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  130. * You can use any range for the criteria argument, as long as it
  131. * includes at least one column label and at least one cell below
  132. * the column label in which you specify a condition for the
  133. * column.
  134. *
  135. * @return float|string
  136. */
  137. public static function DAVERAGE($database, $field, $criteria)
  138. {
  139. $field = self::fieldExtract($database, $field);
  140. if ($field === null) {
  141. return null;
  142. }
  143. // Return
  144. return Statistical::AVERAGE(
  145. self::getFilteredColumn($database, $field, $criteria)
  146. );
  147. }
  148. /**
  149. * DCOUNT.
  150. *
  151. * Counts the cells that contain numbers in a column of a list or database that match conditions
  152. * that you specify.
  153. *
  154. * Excel Function:
  155. * DCOUNT(database,[field],criteria)
  156. *
  157. * Excel Function:
  158. * DAVERAGE(database,field,criteria)
  159. *
  160. * @param mixed[] $database The range of cells that makes up the list or database.
  161. * A database is a list of related data in which rows of related
  162. * information are records, and columns of data are fields. The
  163. * first row of the list contains labels for each column.
  164. * @param int|string $field Indicates which column is used in the function. Enter the
  165. * column label enclosed between double quotation marks, such as
  166. * "Age" or "Yield," or a number (without quotation marks) that
  167. * represents the position of the column within the list: 1 for
  168. * the first column, 2 for the second column, and so on.
  169. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  170. * You can use any range for the criteria argument, as long as it
  171. * includes at least one column label and at least one cell below
  172. * the column label in which you specify a condition for the
  173. * column.
  174. *
  175. * @return int
  176. *
  177. * @TODO The field argument is optional. If field is omitted, DCOUNT counts all records in the
  178. * database that match the criteria.
  179. */
  180. public static function DCOUNT($database, $field, $criteria)
  181. {
  182. $field = self::fieldExtract($database, $field);
  183. if ($field === null) {
  184. return null;
  185. }
  186. // Return
  187. return Statistical::COUNT(
  188. self::getFilteredColumn($database, $field, $criteria)
  189. );
  190. }
  191. /**
  192. * DCOUNTA.
  193. *
  194. * Counts the nonblank cells in a column of a list or database that match conditions that you specify.
  195. *
  196. * Excel Function:
  197. * DCOUNTA(database,[field],criteria)
  198. *
  199. * @param mixed[] $database The range of cells that makes up the list or database.
  200. * A database is a list of related data in which rows of related
  201. * information are records, and columns of data are fields. The
  202. * first row of the list contains labels for each column.
  203. * @param int|string $field Indicates which column is used in the function. Enter the
  204. * column label enclosed between double quotation marks, such as
  205. * "Age" or "Yield," or a number (without quotation marks) that
  206. * represents the position of the column within the list: 1 for
  207. * the first column, 2 for the second column, and so on.
  208. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  209. * You can use any range for the criteria argument, as long as it
  210. * includes at least one column label and at least one cell below
  211. * the column label in which you specify a condition for the
  212. * column.
  213. *
  214. * @return int
  215. *
  216. * @TODO The field argument is optional. If field is omitted, DCOUNTA counts all records in the
  217. * database that match the criteria.
  218. */
  219. public static function DCOUNTA($database, $field, $criteria)
  220. {
  221. $field = self::fieldExtract($database, $field);
  222. if ($field === null) {
  223. return null;
  224. }
  225. // reduce the database to a set of rows that match all the criteria
  226. $database = self::filter($database, $criteria);
  227. // extract an array of values for the requested column
  228. $colData = [];
  229. foreach ($database as $row) {
  230. $colData[] = $row[$field];
  231. }
  232. // Return
  233. return Statistical::COUNTA(
  234. self::getFilteredColumn($database, $field, $criteria)
  235. );
  236. }
  237. /**
  238. * DGET.
  239. *
  240. * Extracts a single value from a column of a list or database that matches conditions that you
  241. * specify.
  242. *
  243. * Excel Function:
  244. * DGET(database,field,criteria)
  245. *
  246. * @param mixed[] $database The range of cells that makes up the list or database.
  247. * A database is a list of related data in which rows of related
  248. * information are records, and columns of data are fields. The
  249. * first row of the list contains labels for each column.
  250. * @param int|string $field Indicates which column is used in the function. Enter the
  251. * column label enclosed between double quotation marks, such as
  252. * "Age" or "Yield," or a number (without quotation marks) that
  253. * represents the position of the column within the list: 1 for
  254. * the first column, 2 for the second column, and so on.
  255. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  256. * You can use any range for the criteria argument, as long as it
  257. * includes at least one column label and at least one cell below
  258. * the column label in which you specify a condition for the
  259. * column.
  260. *
  261. * @return mixed
  262. */
  263. public static function DGET($database, $field, $criteria)
  264. {
  265. $field = self::fieldExtract($database, $field);
  266. if ($field === null) {
  267. return null;
  268. }
  269. // Return
  270. $colData = self::getFilteredColumn($database, $field, $criteria);
  271. if (count($colData) > 1) {
  272. return Functions::NAN();
  273. }
  274. return $colData[0];
  275. }
  276. /**
  277. * DMAX.
  278. *
  279. * Returns the largest number in a column of a list or database that matches conditions you that
  280. * specify.
  281. *
  282. * Excel Function:
  283. * DMAX(database,field,criteria)
  284. *
  285. * @param mixed[] $database The range of cells that makes up the list or database.
  286. * A database is a list of related data in which rows of related
  287. * information are records, and columns of data are fields. The
  288. * first row of the list contains labels for each column.
  289. * @param int|string $field Indicates which column is used in the function. Enter the
  290. * column label enclosed between double quotation marks, such as
  291. * "Age" or "Yield," or a number (without quotation marks) that
  292. * represents the position of the column within the list: 1 for
  293. * the first column, 2 for the second column, and so on.
  294. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  295. * You can use any range for the criteria argument, as long as it
  296. * includes at least one column label and at least one cell below
  297. * the column label in which you specify a condition for the
  298. * column.
  299. *
  300. * @return float
  301. */
  302. public static function DMAX($database, $field, $criteria)
  303. {
  304. $field = self::fieldExtract($database, $field);
  305. if ($field === null) {
  306. return null;
  307. }
  308. // Return
  309. return Statistical::MAX(
  310. self::getFilteredColumn($database, $field, $criteria)
  311. );
  312. }
  313. /**
  314. * DMIN.
  315. *
  316. * Returns the smallest number in a column of a list or database that matches conditions you that
  317. * specify.
  318. *
  319. * Excel Function:
  320. * DMIN(database,field,criteria)
  321. *
  322. * @param mixed[] $database The range of cells that makes up the list or database.
  323. * A database is a list of related data in which rows of related
  324. * information are records, and columns of data are fields. The
  325. * first row of the list contains labels for each column.
  326. * @param int|string $field Indicates which column is used in the function. Enter the
  327. * column label enclosed between double quotation marks, such as
  328. * "Age" or "Yield," or a number (without quotation marks) that
  329. * represents the position of the column within the list: 1 for
  330. * the first column, 2 for the second column, and so on.
  331. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  332. * You can use any range for the criteria argument, as long as it
  333. * includes at least one column label and at least one cell below
  334. * the column label in which you specify a condition for the
  335. * column.
  336. *
  337. * @return float
  338. */
  339. public static function DMIN($database, $field, $criteria)
  340. {
  341. $field = self::fieldExtract($database, $field);
  342. if ($field === null) {
  343. return null;
  344. }
  345. // Return
  346. return Statistical::MIN(
  347. self::getFilteredColumn($database, $field, $criteria)
  348. );
  349. }
  350. /**
  351. * DPRODUCT.
  352. *
  353. * Multiplies the values in a column of a list or database that match conditions that you specify.
  354. *
  355. * Excel Function:
  356. * DPRODUCT(database,field,criteria)
  357. *
  358. * @param mixed[] $database The range of cells that makes up the list or database.
  359. * A database is a list of related data in which rows of related
  360. * information are records, and columns of data are fields. The
  361. * first row of the list contains labels for each column.
  362. * @param int|string $field Indicates which column is used in the function. Enter the
  363. * column label enclosed between double quotation marks, such as
  364. * "Age" or "Yield," or a number (without quotation marks) that
  365. * represents the position of the column within the list: 1 for
  366. * the first column, 2 for the second column, and so on.
  367. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  368. * You can use any range for the criteria argument, as long as it
  369. * includes at least one column label and at least one cell below
  370. * the column label in which you specify a condition for the
  371. * column.
  372. *
  373. * @return float
  374. */
  375. public static function DPRODUCT($database, $field, $criteria)
  376. {
  377. $field = self::fieldExtract($database, $field);
  378. if ($field === null) {
  379. return null;
  380. }
  381. // Return
  382. return MathTrig::PRODUCT(
  383. self::getFilteredColumn($database, $field, $criteria)
  384. );
  385. }
  386. /**
  387. * DSTDEV.
  388. *
  389. * Estimates the standard deviation of a population based on a sample by using the numbers in a
  390. * column of a list or database that match conditions that you specify.
  391. *
  392. * Excel Function:
  393. * DSTDEV(database,field,criteria)
  394. *
  395. * @param mixed[] $database The range of cells that makes up the list or database.
  396. * A database is a list of related data in which rows of related
  397. * information are records, and columns of data are fields. The
  398. * first row of the list contains labels for each column.
  399. * @param int|string $field Indicates which column is used in the function. Enter the
  400. * column label enclosed between double quotation marks, such as
  401. * "Age" or "Yield," or a number (without quotation marks) that
  402. * represents the position of the column within the list: 1 for
  403. * the first column, 2 for the second column, and so on.
  404. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  405. * You can use any range for the criteria argument, as long as it
  406. * includes at least one column label and at least one cell below
  407. * the column label in which you specify a condition for the
  408. * column.
  409. *
  410. * @return float|string
  411. */
  412. public static function DSTDEV($database, $field, $criteria)
  413. {
  414. $field = self::fieldExtract($database, $field);
  415. if ($field === null) {
  416. return null;
  417. }
  418. // Return
  419. return Statistical::STDEV(
  420. self::getFilteredColumn($database, $field, $criteria)
  421. );
  422. }
  423. /**
  424. * DSTDEVP.
  425. *
  426. * Calculates the standard deviation of a population based on the entire population by using the
  427. * numbers in a column of a list or database that match conditions that you specify.
  428. *
  429. * Excel Function:
  430. * DSTDEVP(database,field,criteria)
  431. *
  432. * @param mixed[] $database The range of cells that makes up the list or database.
  433. * A database is a list of related data in which rows of related
  434. * information are records, and columns of data are fields. The
  435. * first row of the list contains labels for each column.
  436. * @param int|string $field Indicates which column is used in the function. Enter the
  437. * column label enclosed between double quotation marks, such as
  438. * "Age" or "Yield," or a number (without quotation marks) that
  439. * represents the position of the column within the list: 1 for
  440. * the first column, 2 for the second column, and so on.
  441. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  442. * You can use any range for the criteria argument, as long as it
  443. * includes at least one column label and at least one cell below
  444. * the column label in which you specify a condition for the
  445. * column.
  446. *
  447. * @return float|string
  448. */
  449. public static function DSTDEVP($database, $field, $criteria)
  450. {
  451. $field = self::fieldExtract($database, $field);
  452. if ($field === null) {
  453. return null;
  454. }
  455. // Return
  456. return Statistical::STDEVP(
  457. self::getFilteredColumn($database, $field, $criteria)
  458. );
  459. }
  460. /**
  461. * DSUM.
  462. *
  463. * Adds the numbers in a column of a list or database that match conditions that you specify.
  464. *
  465. * Excel Function:
  466. * DSUM(database,field,criteria)
  467. *
  468. * @param mixed[] $database The range of cells that makes up the list or database.
  469. * A database is a list of related data in which rows of related
  470. * information are records, and columns of data are fields. The
  471. * first row of the list contains labels for each column.
  472. * @param int|string $field Indicates which column is used in the function. Enter the
  473. * column label enclosed between double quotation marks, such as
  474. * "Age" or "Yield," or a number (without quotation marks) that
  475. * represents the position of the column within the list: 1 for
  476. * the first column, 2 for the second column, and so on.
  477. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  478. * You can use any range for the criteria argument, as long as it
  479. * includes at least one column label and at least one cell below
  480. * the column label in which you specify a condition for the
  481. * column.
  482. *
  483. * @return float
  484. */
  485. public static function DSUM($database, $field, $criteria)
  486. {
  487. $field = self::fieldExtract($database, $field);
  488. if ($field === null) {
  489. return null;
  490. }
  491. // Return
  492. return MathTrig::SUM(
  493. self::getFilteredColumn($database, $field, $criteria)
  494. );
  495. }
  496. /**
  497. * DVAR.
  498. *
  499. * Estimates the variance of a population based on a sample by using the numbers in a column
  500. * of a list or database that match conditions that you specify.
  501. *
  502. * Excel Function:
  503. * DVAR(database,field,criteria)
  504. *
  505. * @param mixed[] $database The range of cells that makes up the list or database.
  506. * A database is a list of related data in which rows of related
  507. * information are records, and columns of data are fields. The
  508. * first row of the list contains labels for each column.
  509. * @param int|string $field Indicates which column is used in the function. Enter the
  510. * column label enclosed between double quotation marks, such as
  511. * "Age" or "Yield," or a number (without quotation marks) that
  512. * represents the position of the column within the list: 1 for
  513. * the first column, 2 for the second column, and so on.
  514. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  515. * You can use any range for the criteria argument, as long as it
  516. * includes at least one column label and at least one cell below
  517. * the column label in which you specify a condition for the
  518. * column.
  519. *
  520. * @return float|string (string if result is an error)
  521. */
  522. public static function DVAR($database, $field, $criteria)
  523. {
  524. $field = self::fieldExtract($database, $field);
  525. if ($field === null) {
  526. return null;
  527. }
  528. // Return
  529. return Statistical::VARFunc(
  530. self::getFilteredColumn($database, $field, $criteria)
  531. );
  532. }
  533. /**
  534. * DVARP.
  535. *
  536. * Calculates the variance of a population based on the entire population by using the numbers
  537. * in a column of a list or database that match conditions that you specify.
  538. *
  539. * Excel Function:
  540. * DVARP(database,field,criteria)
  541. *
  542. * @param mixed[] $database The range of cells that makes up the list or database.
  543. * A database is a list of related data in which rows of related
  544. * information are records, and columns of data are fields. The
  545. * first row of the list contains labels for each column.
  546. * @param int|string $field Indicates which column is used in the function. Enter the
  547. * column label enclosed between double quotation marks, such as
  548. * "Age" or "Yield," or a number (without quotation marks) that
  549. * represents the position of the column within the list: 1 for
  550. * the first column, 2 for the second column, and so on.
  551. * @param mixed[] $criteria The range of cells that contains the conditions you specify.
  552. * You can use any range for the criteria argument, as long as it
  553. * includes at least one column label and at least one cell below
  554. * the column label in which you specify a condition for the
  555. * column.
  556. *
  557. * @return float|string (string if result is an error)
  558. */
  559. public static function DVARP($database, $field, $criteria)
  560. {
  561. $field = self::fieldExtract($database, $field);
  562. if ($field === null) {
  563. return null;
  564. }
  565. // Return
  566. return Statistical::VARP(
  567. self::getFilteredColumn($database, $field, $criteria)
  568. );
  569. }
  570. }