我需要类似于这 2 个 SO 问题的内容,但使用 Informix SQL 语法。
我进来的数据是这样的:
id codes
63592 PELL
58640 SUBL
58640 USBL
73571 PELL
73571 USBL
73571 SUBL
我想看到它像这样回来:
id codes
63592 PELL
58640 SUBL, USBL
73571 PELL, USBL, SUBL
我需要类似于这 2 个 SO 问题的内容,但使用 Informix SQL 语法。
我进来的数据是这样的:
id codes
63592 PELL
58640 SUBL
58640 USBL
73571 PELL
73571 USBL
73571 SUBL
我想看到它像这样回来:
id codes
63592 PELL
58640 SUBL, USBL
73571 PELL, USBL, SUBL
我相信您需要的答案是一个用户定义的聚合,类似于这个:
CREATE FUNCTION gc_init(dummy VARCHAR(255)) RETURNING LVARCHAR;
RETURN '';
END FUNCTION;
CREATE FUNCTION gc_iter(result LVARCHAR, value VARCHAR(255))
RETURNING LVARCHAR;
IF result = '' THEN
RETURN TRIM(value);
ELSE
RETURN result || ',' || TRIM(value);
END IF;
END FUNCTION;
CREATE FUNCTION gc_comb(partial1 LVARCHAR, partial2 LVARCHAR)
RETURNING LVARCHAR;
IF partial1 IS NULL OR partial1 = '' THEN
RETURN partial2;
ELIF partial2 IS NULL OR partial2 = '' THEN
RETURN partial1;
ELSE
RETURN partial1 || ',' || partial2;
END IF;
END FUNCTION;
CREATE FUNCTION gc_fini(final LVARCHAR) RETURNING LVARCHAR;
RETURN final;
END FUNCTION;
CREATE AGGREGATE group_concat
WITH (INIT = gc_init, ITER = gc_iter,
COMBINE = gc_comb, FINAL = gc_fini);
给定一个元素表(称为元素),其中一个名为 name 的列包含(很有趣)元素名称,以及另一个名为 atomic_number 的列,此查询产生以下结果:
SELECT group_concat(name) FROM elements WHERE atomic_number < 10;
Hydrogen,Helium,Lithium,Beryllium,Boron,Carbon,Nitrogen,Oxygen,Fluorine
应用于该问题,您应该从以下位置获得所需的答案:
SELECT id, group_concat(codes)
FROM anonymous_table
GROUP BY id;
CREATE TEMP TABLE anonymous_table
(
id INTEGER NOT NULL,
codes CHAR(4) NOT NULL,
PRIMARY KEY (id, codes)
);
INSERT INTO anonymous_table VALUES(63592, 'PELL');
INSERT INTO anonymous_table VALUES(58640, 'SUBL');
INSERT INTO anonymous_table VALUES(58640, 'USBL');
INSERT INTO anonymous_table VALUES(73571, 'PELL');
INSERT INTO anonymous_table VALUES(73571, 'USBL');
INSERT INTO anonymous_table VALUES(73571, 'SUBL');
INSERT INTO anonymous_table VALUES(73572, 'USBL');
INSERT INTO anonymous_table VALUES(73572, 'PELL');
INSERT INTO anonymous_table VALUES(73572, 'SUBL');
SELECT id, group_concat(codes)
FROM anonymous_table
GROUP BY id
ORDER BY id;
输出是:
58640 SUBL,USBL
63592 PELL
73571 PELL,SUBL,USBL
73572 PELL,SUBL,USBL
添加了额外的数据集以测试插入序列是否影响结果;它似乎没有这样做(代码按排序顺序排列;我不确定是否有办法改变 - 反转 - 该顺序)。
笔记:
LVARCHAR(10240)
(对于 10 KiB)。SQL -528: Maximum output rowsize (32767) exceeded
,这让我感到惊讶。如果需要删除聚合,可以使用:
DROP AGGREGATE IF EXISTS group_concat;
DROP FUNCTION IF EXISTS gc_fini;
DROP FUNCTION IF EXISTS gc_init;
DROP FUNCTION IF EXISTS gc_iter;
DROP FUNCTION IF EXISTS gc_comb;
Oracle 为此类需求提供了列表聚合器功能。
SELECT id, LISTAGG(codes,',') as CODE_LIST FROM <TABLE> GROUP BY id
输出会像
ID CODE_LIST
63592 PELL
58640 SUBL,USBL
73571 PELL,USBL,SUBL
我不确定informix sql,但在MSSQL 或Oracle 中,您可以使用
DECODE 或 CASE 关键字,通过将它们连接在一起。但是,这需要您提前知道所有潜在值,这很脆弱。
我假设您不喜欢 STUFF 关键字的原因是因为 informix 不支持它?
Oracle 还支持 CONNECT BY 关键字,它可以工作,但同样可能不受 informix 支持。
可能最好的答案是在查询之后在您的客户端/数据层中构建此输出。是否有特殊原因必须在查询中完成此操作?
此外,如果 informix 允许您创建用户函数,您可以创建一个函数,该函数返回一个带有连接值的字符串。
基于 Jonathan Leffler 示例和关于连接值排序的 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
's 来保留中间结果(并消除重复项),最后从 final 的有序值构建连接字符串SET
。
使用LVARCHAR
forSET
元素是因为最初我使用的是VARCHAR
,但内存消耗非常非常高。VARCHAR
文档提示 Informix内部可能会将CHAR
. 我进行了更改,实际上确实降低了内存消耗(但仍然很高)。
然而,这个总内存消耗比乔纳森的高大约 2 个数量级,并且在我进行的测试中慢了大约 2 倍(使用大约 300 000 行的表)。
所以要小心使用。它消耗大量内存并且没有经过广泛测试(它可能在某处泄漏内存)。
编辑1:
我之前的代码一定在某处泄漏了内存结构(或者 Informix 在内部保留了集合派生表,它可以生成很多这样的表)。
因此,仍然试图避免在 中编写聚合函数C
,这是另一种选择,使用 InformixBSON
内置函数,这将使用更少的内存并且速度更快。
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'
可能会引发各种错误(包括断言失败)。
我确信还有其他问题。
但是,此实现的内存消耗与 Jonathan 的示例中的数量级相同,并且慢了大约 60%(同样,只执行了非常基本的测试)。
我想在 Stack Overflow 上的另一个类似问题上向您指出这个答案。您正在寻找类似 MySQL 的group_concat()
函数。
此解决方案支持用户指定的分隔符:
例子:
SELECT id, group_concat(codes, '+')
FROM anonymous_table
GROUP BY id
ORDER BY id;
这与Jonathan Leffler的解决方案相同,但支持分隔符。如果未指定,默认分隔符是','。
代码:
CREATE ROW TYPE group_concat_t
(
result LVARCHAR,
separator VARCHAR(1)
);
CREATE FUNCTION gc_init(dummy VARCHAR(255), separator VARCHAR(1) default ',' ) RETURNING group_concat_t;
RETURN ROW('',separator)::group_concat_t;
END FUNCTION;
CREATE FUNCTION gc_iter(result group_concat_t, value VARCHAR(255))
RETURNING group_concat_t;
IF result.result = '' THEN
RETURN ROW(TRIM(value),result.separator)::group_concat_t ;
ELSE
RETURN ROW(result.result || result.separator || TRIM(value),result.separator)::group_concat_t;
END IF;
END FUNCTION;
CREATE FUNCTION gc_comb(partial1 group_concat_t, partial2 group_concat_t)
RETURNING group_concat_t;
IF partial1 IS NULL OR partial1.result = '' THEN
RETURN partial2;
ELIF partial2 IS NULL OR partial2.result = '' THEN
RETURN partial1;
ELSE
RETURN ROW(partial1.result || partial1.separator || partial2.result, partial1.separator)::group_concat_t;
END IF;
END FUNCTION;
CREATE FUNCTION gc_fini(final group_concat_t) RETURNING LVARCHAR;
RETURN final.result;
END FUNCTION;
CREATE AGGREGATE group_concat
WITH (INIT = gc_init, ITER = gc_iter,
COMBINE = gc_comb, FINAL = gc_fini);