显示一对多关系为2列 – 1个唯一行(ID和逗号分隔列表)

我需要类似于这2个SO问题的东西,但使用Informix SQL语法。

  • 用SQL将几个字段连接成一个字段

  • SQL帮助:Select语句连接一对多关系

我的数据是这样的:

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 

另请参阅Informix中的group_concat() 。

我相信你需要的答案是一个用户定义的聚合,类似于这个:

 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); 

给定一个元素(称为元素)的表格,其中包含名称(包含足够有趣)元素名称的列和名为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 

添加额外的数据集以testing插入序列是否影响结果; 它似乎没有这样做(代码是按sorting顺序,我不知道是否有办法改变 – 反向 – 这个顺序)。


笔记:

  1. 此聚合应该可用于任何可以转换为VARCHAR(255)的types,这意味着任何数字或时间types。 不处理长CHAR列和blobtypes(BYTE,TEXT,BLOB,CLOB)。
  2. 简单的LVARCHAR将聚合大小限制为2048字节。 如果您认为需要更长的时间,请指定LVARCHAR(10240) (例如10 KiB)。
  3. 从Informix 12.10.FC5开始,最大工作长度似乎是16380; 更长的东西似乎触发SQL -528: Maximum output rowsize (32767) exceeded ,这让我感到惊讶。
  4. 如果您需要删除聚合,您可以使用:

     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; 

我不确定informix sql,但在MSSQL或Oracle中,你可以用

DECODE或CASE关键字,将它们连接在一起。 但是,这将要求你提前知道所有潜在的价值,这是脆弱的。

我假设你不喜欢STUFF关键字的原因是因为informix不支持它?

Oracle也支持CONNECT BY关键字,这可能会起作用,但是informix可能不支持。

可能最好的答案是在查询之后在你的客户端/数据层build立这个输出。 为什么在查询中必须完成这个工作吗?

另外,如果informix允许你创build用户函数,你可以创build一个函数,返回一个带有连接值的string。

基于Jonathan Leffler的例子和RET对连接值sorting的评论,使用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 ); 

串联的值将没有重复,并将被sorting。

我使用Informix collections ,即不允许重复值的SET来尝试保持代码的简单性。

该方法是使用SET来保留中间结果(并消除重复项),并在最后从最终SET的有序值构build连接string。

SET元素使用LVARCHAR是因为最初我使用VARCHAR但内存消耗非常高。 文档提示Informix内部可能将VARCHARCHAR 。 我做了改变,实际上降低了内存消耗(但仍然很高)。

然而,这个总的内存消耗比乔纳森高大约2个数量级,在我进行的testing(使用大约30万行的表)上大约慢了2倍。

所以小心使用。 它消耗了大量的内存,并没有经过广泛的testing(可能是内存泄漏)。

编辑1:

我以前的代码必须在某个地方泄漏内存结构(或者Informix内部保留集合派生表,并且可以生成很多这样的表)。

因此,仍然试图避免在C编写集合函数,这里是另一种select,使用内置函数的Informix BSON ,它将使用更less的内存,并且速度更快。

 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 ) ; 

汇总的返回值将被sorting并且没有重复。

再一次,这没有得到适当的testing。 这只是一个POC。

其中一个问题是,它没有消毒input值。 一些BSON操作函数接收正在通过连接string构build的参数,而非转义字符可能会破坏这些参数。 例如,一个带有引号的string值: 'I"BrokeIt' )会引起各种各样的错误(包括Assert Failures)。

我相信还有其他问题。

然而,这个实现的内存消耗与Jonathan的例子的数量级相同,大约慢了60%(同样,只进行了非常基本的testing)。

我想在堆栈溢出的另一个类似的问题上指出你的答案 。 你正在寻找像MySQL的group_concat()函数。