Опираясь на пример Джонатана Леффлера и комментарии RET о порядке упорядочения объединенных значений, используя Informix 12.10FC8DE, я придумал следующую совокупность пользователей:
CREATE FUNCTION mgc_init
(
dummy VARCHAR(255)
)
RETURNING
SET(LVARCHAR(2048) NOT NULL);
RETURN SET{}::SET(LVARCHAR(2048) NOT NULL);
END FUNCTION;
CREATE FUNCTION mgc_iter
(
p_result SET(LVARCHAR(2048) NOT NULL)
, p_value VARCHAR(255)
)
RETURNING
SET(LVARCHAR(2048) NOT NULL);
IF p_value IS NOT NULL THEN
INSERT INTO TABLE(p_result) VALUES (TRIM(p_value));
END IF;
RETURN p_result;
END FUNCTION;
CREATE FUNCTION mgc_comb
(
p_partial1 SET(LVARCHAR(2048) NOT NULL)
, p_partial2 SET(LVARCHAR(2048) NOT NULL)
)
RETURNING
SET(LVARCHAR(2048) NOT NULL);
INSERT INTO TABLE(p_partial1)
SELECT vc1 FROM TABLE(p_partial2)(vc1);
RETURN p_partial1;
END FUNCTION;
CREATE FUNCTION mgc_fini
(
p_final SET(LVARCHAR(2048) NOT NULL)
)
RETURNING
LVARCHAR;
DEFINE l_str LVARCHAR(2048);
DEFINE l_value LVARCHAR(2048);
LET l_str = NULL;
FOREACH SELECT vvalue1 INTO l_value FROM TABLE(p_final) AS vt1(vvalue1) ORDER BY vvalue1
IF l_str IS NULL THEN
LET l_str = l_value;
ELSE
LET l_str = l_str || ',' || l_value;
END IF;
END FOREACH;
RETURN l_str;
END FUNCTION;
GRANT EXECUTE ON mgc_fini TO PUBLIC;
CREATE AGGREGATE m_group_concat
WITH
(
INIT = mgc_init
, ITER = mgc_iter
, COMBINE = mgc_comb
, FINAL = mgc_fini
);
Объединенные значения не будут иметь дубликатов и будут упорядочены.
Я использовал Informix collections
, а именно SET
, который не допускает дублирование значений, чтобы попытаться сделать код несколько простым.
Метод состоит в том, чтобы использовать SET
для сохранения промежуточных результатов (и устранения дубликатов) и в конце построить объединенную строку из упорядоченных значений конечных SET
.
Использование LVARCHAR
для элементов SET
связано с тем, что изначально я использовал VARCHAR
, но потребление памяти было очень и очень высоким. В документации намекает, что внутренне Informix может приводить VARCHAR
к CHAR
. Я внес изменение, и оно фактически уменьшило потребление памяти (но оно все еще остается высоким).
Однако это совокупное потребление памяти примерно на 2 порядка выше, чем у Джонатана, и примерно в 2 раза медленнее в проведенных мною тестах (с использованием таблицы с 300 000 строк).
Так что используйте с осторожностью. Он потребляет много памяти и не подвергается тщательному тестированию (возможно, где-то происходит утечка памяти).
РЕДАКТИРОВАТЬ 1:
Мой предыдущий код должен где-то просачиваться в структуру памяти (или внутренне Informix хранит таблицы производных коллекций и может генерировать их много).
Итак, все еще пытаясь избежать необходимости кодировать агрегатную функцию в C
, вот еще одна альтернатива, использующая встроенные функции Informix BSON
, которая будет использовать намного меньше памяти и будет немного быстрее.
CREATE FUNCTION m2gc_init
(
dummy VARCHAR(255)
)
RETURNING
BSON;
RETURN '{"terms":[]}'::JSON::BSON;
END FUNCTION;
CREATE FUNCTION m2gc_iter
(
p_result BSON
, p_value VARCHAR(255)
)
RETURNING
BSON;
DEFINE l_add_array_element LVARCHAR(2048);
IF p_value IS NOT NULL THEN
LET l_add_array_element = '{ $addToSet: { terms: "' || TRIM(p_value) || '" } }';
LET p_result = BSON_UPDATE(p_result, l_add_array_element);
END IF;
RETURN p_result;
END FUNCTION;
CREATE FUNCTION m2gc_comb
(
p_partial1 BSON
, p_partial2 BSON
)
RETURNING
BSON;
DEFINE l_array_elements LVARCHAR(2048);
DEFINE l_an_element LVARCHAR(2048);
DEFINE l_guard INTEGER;
LET l_array_elements = NULL;
LET l_guard = BSON_SIZE(p_partial2, 'terms.0');
IF l_guard > 0 THEN
WHILE l_guard > 0
LET l_an_element = BSON_VALUE_LVARCHAR(p_partial2, 'terms.0');
IF l_array_elements IS NULL THEN
LET l_array_elements = '"' || l_an_element || '"';
ELSE
LET l_array_elements = l_array_elements || ', "' || l_an_element || '"';
END IF;
LET p_partial2 = BSON_UPDATE(p_partial2, '{ $pop: { terms: -1 } }');
LET l_guard = BSON_SIZE(p_partial2, 'terms.0');
END WHILE;
LET l_array_elements = '{ $addToSet: { terms: { $each: [ ' || l_array_elements || ' ] } } }';
LET p_partial1 = BSON_UPDATE(p_partial1, l_array_elements);
END IF;
RETURN p_partial1;
END FUNCTION;
CREATE FUNCTION m2gc_fini
(
p_final BSON
)
RETURNING
LVARCHAR;
DEFINE l_str_agg LVARCHAR(2048);
DEFINE l_an_element LVARCHAR(2048);
DEFINE l_iter_int INTEGER;
DEFINE l_guard INTEGER;
LET l_str_agg = NULL;
LET l_guard = BSON_SIZE(p_final, 'terms.0');
IF l_guard > 0 THEN
LET p_final = BSON_UPDATE(p_final, '{ $push: { terms: { $each: [], $sort: 1 } } }');
LET l_iter_int = 0;
WHILE l_guard > 0
LET l_an_element = BSON_VALUE_LVARCHAR(p_final, 'terms.' || l_iter_int);
IF l_str_agg IS NULL THEN
LET l_str_agg = TRIM(l_an_element);
ELSE
LET l_str_agg = l_str_agg || ',' || TRIM(l_an_element);
END IF;
LET l_iter_int = l_iter_int + 1;
LET l_guard = BSON_SIZE(p_final, 'terms.' || l_iter_int);
END WHILE;
END IF;
RETURN l_str_agg;
END FUNCTION;
CREATE AGGREGATE m2_group_concat
WITH
(
INIT = m2gc_init
, ITER = m2gc_iter
, COMBINE = m2gc_comb
, FINAL = m2gc_fini
)
;
Агрегированное возвращаемое значение будет упорядочено без дубликатов.
Опять же, это не было должным образом проверено. Это просто POC.
Одна из проблем заключается в том, что он не очищает входные значения.
Некоторые из BSON
манипулирующих функций получают параметры, которые строятся путем объединения строк, и не экранированные символы могут нарушать эти параметры.
Например, строковое значение с кавычками: 'I"BrokeIt'
) может вызвать ряд ошибок (включая ошибки подтверждения).
И я уверен, что есть и другие проблемы.
Тем не менее, потребление памяти этой реализацией имеет тот же порядок величины, что и в примере с Джонатаном, и примерно на 60% медленнее (опять же, было проведено только очень элементарное тестирование).