9

これらの2つのSOの質問に似たものが必要ですが、InformixSQL構文を使用しています。

入ってくる私のデータは次のようになります:

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()も参照してください。

4

7 に答える 7

27

必要な答えは、次のようなユーザー定義の集計であると思います。

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 という別の列を持つ要素 (elements と呼ばれる) のテーブルを指定すると、このクエリは次の結果を生成します。

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

挿入配列が結果に影響を与えるかどうかをテストするために、追加のデータセットが追加されました。そうではないようです(コードはソートされた順序になっています。その順序を変更(逆)する方法があるかどうかはわかりません)。


ノート:

  1. この集計は、VARCHAR(255) に変換できる任意の型で使用できる必要があります。これは、任意の数値型または時間型を意味します。長い CHAR 列と BLOB 型 (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;
    
于 2009-04-04T06:42:44.797 に答える
1

Informix sql についてはよくわかりませんが、MSSQL または Oracle では、

DECODE または CASE キーワードを連結します。ただし、これには潜在的な値をすべて事前に知っておく必要があり、脆弱です。

あなたが STUFF キーワードを好まない理由は、informix がサポートしていないからだと思いますか?

Oracle は CONNECT BY キーワードもサポートしていますが、これは機能しますが、informix ではサポートされていない可能性があります。

おそらく最良の答えは、クエリの後にクライアント/データ層でこの出力を構築することです。クエリでこれを行う必要がある特定の理由はありますか?

于 2009-04-03T20:19:03.703 に答える
1

Jonathan Leffler の例と、Informix 12.10FC8DE を使用した連結値の順序付けに関する RET コメントに基づいて、次のユーザー集計を作成しました。

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して中間結果を保持し (重複を排除し)、最後に final の順序付けられた値から連結された文字列を構築しますSET

要素の使用はLVARCHAR、最初は使用していたが、メモリ消費が非常に高かったという事実によるものです。ドキュメンテーションは、Informix が内部的に を にキャストしている可能性があることを示唆しています。変更を行ったところ、実際にメモリ消費量が減少しました (ただし、依然として高いままです)。SETVARCHARVARCHARCHAR

ただし、この総メモリ消費量は、ジョナサンよりも約 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 です。

問題の 1 つは、入力値をサニタイズしていないことです。一部のBSON操作関数は、文字列を連結することによって構築されているパラメーターを受け取り、エスケープされていない文字はそれらのパラメーターを壊す可能性があります。たとえば、引用符で囲まれた文字列値: 'I"BrokeIt') は、さまざまなエラー (Assert Failures を含む) を引き起こす可能性があります。

そして、他にも問題があると確信しています。

ただし、この実装のメモリ消費量は、Jonathan の例と同程度であり、約 60% 遅くなります (ここでも、非常に初歩的なテストのみが実行されました)。

于 2017-03-14T17:49:46.373 に答える
0

Stack Overflow の別の同様の質問に対するこの回答を示したいと思います。group_concat()MySQL の関数のようなものを探しています。

于 2009-04-03T20:22:47.470 に答える
0

このソリューションは、ユーザーが指定したセパレーターをサポートしています。

例:

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);
于 2021-06-09T15:34:39.677 に答える