Показать отношение "один ко многим" в виде 2 столбцов - 1 уникальной строки (идентификатор и список, разделенный запятыми)

StackOverflow https://stackoverflow.com/questions/715350

Вопрос

Мне нужно что-то похожее на эти 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

Смотрите также group_concat() в Informix.

Это было полезно?

Решение

Я считаю, что ответ, который вам нужен, - это определяемый пользователем агрегат, подобный этому:

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

Учитывая таблицу элементов (называемых elements) со столбцом под названием 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

Дополнительный набор данных был добавлен, чтобы проверить, повлияла ли последовательность вставки на результат;похоже, что это не так (коды приведены в отсортированном порядке;Я не уверен, есть ли способ изменить - обратить вспять - этот порядок).


Примечания:

  1. Этот агрегат должен использоваться для любого типа, который может быть преобразован в VARCHAR(255), что означает любой числовой или временной тип.Длинные столбцы символов и типы больших двоичных объектов (BYTE, TEXT, BLOB, CLOB) не обрабатываются.
  2. Обычный LVARCHAR ограничивает совокупный размер 2048 байтами.Если вы считаете, что вам нужна большая длина, укажите LVARCHAR(10240) (для 10 КБ), например.
  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 вы могли бы сделать это с помощью

РАСШИФРУЙТЕ ключевые слова или регистр, объединив их вместе.Однако для этого потребовалось бы, чтобы вы заранее знали все потенциальные значения, что является хрупким.

Я предполагаю, что причина, по которой вам не нравится ключевое слово STUFF, заключается в том, что informix его не поддерживает?

Oracle также поддерживает ПОДКЛЮЧЕНИЕ ПО ключевым словам, которое могло бы работать, но опять же может не поддерживаться informix.

Вероятно, лучшим ответом было бы создать этот вывод на вашем уровне клиент / данные после запроса.Есть ли какая-то особая причина, по которой это должно быть сделано в запросе?

Кроме того, если informix позволяет вам создавать пользовательские функции, вы могли бы создать функцию, которая возвращала строку с объединенным значением.

Основываясь на примере Джонатана Леффлера и комментариях 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 сохранить промежуточные результаты (и исключить дубликаты) и в конце построить объединенную строку из упорядоченных значений конечного 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% медленнее (опять же, было выполнено только очень элементарное тестирование).

Я хотел бы указать вам этот ответ по другому аналогичному вопросу о переполнении стека.Вы ищете что-то вроде MySQL's group_concat() функция.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top