This fills a gap in the MySql spec. It returns the first result of a GROUP_CONCAT set.
if (static::$dataType) {
$dataType = static::$dataType;
}
+ elseif (static::$category === self::CATEGORY_AGGREGATE) {
+ $exprArgs = $this->getArgs();
+ // If the first expression is a SqlFunction/SqlEquation, allow it to control the aggregate dataType
+ if (method_exists($exprArgs[0]['expr'][0], 'formatOutputValue')) {
+ $exprArgs[0]['expr'][0]->formatOutputValue($dataType, $values, $key);
+ }
+ }
if (isset($values[$key]) && $this->suffix && $this->suffix !== 'id') {
$dataType = 'String';
$value =& $values[$key];
* @return string
*/
protected function renderExpression(string $output): string {
- return $this->getName() . '(' . $output . ')';
+ return $this->getName() . "($output)";
}
/**
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Query;
+
+/**
+ * Sql function returns the first item in a GROUP_CONCAT set (per the ORDER_BY param)
+ * @since 5.69
+ */
+class SqlFunctionGROUP_FIRST extends SqlFunction {
+
+ public $supportsExpansion = TRUE;
+
+ protected static $category = self::CATEGORY_AGGREGATE;
+
+ protected static function params(): array {
+ return [
+ [
+ 'max_expr' => 1,
+ 'must_be' => ['SqlField', 'SqlFunction', 'SqlEquation'],
+ 'optional' => FALSE,
+ ],
+ [
+ 'name' => 'ORDER BY',
+ 'label' => ts('Order by'),
+ 'max_expr' => 1,
+ 'flag_after' => ['ASC' => ts('Ascending'), 'DESC' => ts('Descending')],
+ 'must_be' => ['SqlField'],
+ 'optional' => TRUE,
+ ],
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public static function getTitle(): string {
+ return ts('First');
+ }
+
+ /**
+ * @return string
+ */
+ public static function getDescription(): string {
+ return ts('First value in the grouping.');
+ }
+
+ /**
+ * Render the final expression
+ * @param string $output
+ * @return string
+ */
+ protected function renderExpression(string $output): string {
+ $sep = \CRM_Core_DAO::VALUE_SEPARATOR;
+ return "SUBSTRING_INDEX(GROUP_CONCAT($output SEPARATOR '$sep'), '$sep', 1)";
+ }
+
+}
$this->assertContains('1, ' . $cid . ', 100.00', $agg['GROUP_CONCAT:financial_type_id_contact_id_total_amount']);
$this->assertEquals([TRUE, TRUE, FALSE, FALSE], $agg['is_donation']);
$this->assertEquals(['January', 'February', 'March', 'April'], $agg['months']);
+
+ // Test GROUP_FIRST
+ $agg = Contribution::get(FALSE)
+ ->addGroupBy('contact_id')
+ ->addWhere('contact_id', '=', $cid)
+ ->addSelect('GROUP_FIRST(financial_type_id:name ORDER BY id) AS financial_type_1')
+ ->addSelect("GROUP_FIRST((financial_type_id = 1) ORDER BY id) AS is_donation_1")
+ ->addSelect("GROUP_FIRST((financial_type_id = 1) ORDER BY id DESC) AS is_donation_4")
+ ->addSelect("GROUP_FIRST(MONTH(receive_date):label ORDER BY id) AS months")
+ ->addSelect('COUNT(*) AS count')
+ ->execute()
+ ->first();
+
+ $this->assertTrue(4 === $agg['count']);
+ $this->assertEquals('Donation', $agg['financial_type_1']);
+ $this->assertEquals('January', $agg['months']);
+ $this->assertTrue($agg['is_donation_1']);
+ $this->assertFalse($agg['is_donation_4']);
}
public function testGroupConcatUnique(): void {