Подтвердить что ты не робот

Выключение MySQL GROUP_CONCAT

(ПРИМЕЧАНИЕ. Этот вопрос касается не экранирования запросов, а результатов об исключении)

Я использую GROUP_CONCAT для объединения нескольких строк в список с разделителями-запятыми. Например, предположим, что у меня есть две (примерные) таблицы:

CREATE TABLE IF NOT EXISTS `Comment` (
`id` int(11) unsigned NOT NULL auto_increment,
`post_id` int(11) unsigned NOT NULL,
`name` varchar(255) collate utf8_unicode_ci NOT NULL,
`comment` varchar(255) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY  (`id`),
KEY `post_id` (`post_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=6 ;

INSERT INTO `Comment` (`id`, `post_id`, `name`, `comment`) VALUES
(1, 1, 'bill', 'some comment'),
(2, 1, 'john', 'another comment'),
(3, 2, 'bill', 'blah'),
(4, 3, 'john', 'asdf'),
(5, 4, 'x', 'asdf');


CREATE TABLE IF NOT EXISTS `Post` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) collate utf8_unicode_ci NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=7 ;

INSERT INTO `Post` (`id`, `title`) VALUES
(1, 'first post'),
(2, 'second post'),
(3, 'third post'),
(4, 'fourth post'),
(5, 'fifth post'),
(6, 'sixth post');

И я хочу перечислить все сообщения вместе со списком каждого имени пользователя, который прокомментировал сообщение:

SELECT
Post.id as post_id, Post.title as title, GROUP_CONCAT(name) 
FROM Post 
LEFT JOIN Comment on Comment.post_id = Post.id
GROUP BY Post.id

дает мне:

id  title   GROUP_CONCAT( name )
1   first post  bill,john
2   second post     bill
3   third post  john
4   fourth post     x
5   fifth post  NULL
6   sixth post  NULL

Это отлично работает, за исключением того, что если имя пользователя содержит запятую, оно разрушит список пользователей. Есть ли у MySQL функция, которая позволит мне избежать этих символов? (Предположите, что имена пользователей могут содержать любые символы, поскольку это всего лишь примерная схема)

4b9b3361

Ответ 1

Если есть какой-то другой символ, который является незаконным в именах пользователей, вы можете указать другой разделительный символ, используя малоизвестный синтаксис:

...GROUP_CONCAT(name SEPARATOR '|')...

... Вы хотите разрешить трубы? или любой символ?

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

group_concat(replace(replace(name, '\\', '\\\\'), '|', '\\|') SEPARATOR '|')

Это будет:

  • избежать любых обратных косых черт с помощью другой обратной косой черты
  • вывести символ разделителя с помощью обратного слэша
  • объединить результаты с символом разделителя

Чтобы получить невыбранные результаты, выполните одно и то же в обратном порядке:

  • разделит результаты на разделительный символ, где не предшествует обратная косая черта. На самом деле, это немного сложно, вы хотите разбить его, где ему не предшествует нечетное количество черных пятен. Это регулярное выражение будет соответствовать этому:
    (?<!\\)(?:\\\\)*\|
  • заменить все символы экранированного разделителя на литералы, т.е. заменить \| с |
  • замените все двойные обратные косые черты на обратную косую черту, например. замените\\на\

Ответ 2

На самом деле существуют ascii control characters, специально предназначенные для разделения полей и записей базы данных:

0x1F (31): unit (fields) separator

0x1E (30): record separator

0x1D (29): group separator

Подробнее: о символах ascii

У вас никогда не будет их в именах пользователей и, скорее всего, никогда в других non-binary data в вашей базе данных, чтобы их можно было безопасно использовать:

GROUP_CONCAT(foo SEPARATOR 0x1D)

Затем разделите на CHAR(0x1D) на любом желаемом языке.

Ответ 3

REPLACE()

Пример:

... GROUP_CONCAT(REPLACE(name, ',', '\\,')) 

Обратите внимание, что вам нужно использовать двойную обратную косую черту (если вы избегаете запятую с обратной косой чертой), потому что обратная косая черта является магической, а \, становится просто ,.

Ответ 4

Я бы предложил GROUP_CONCAT (name SEPARATOR '\n'), так как \n обычно не встречается. Это может быть немного проще, поскольку вам не нужно ничего избегать, но может привести к неожиданным проблемам. Конечно, декодирование кодировки /regexp, предложенное ником, тоже хорошо.

Ответ 5

Если вы собираетесь делать декодирование в своем приложении, возможно, просто используйте hex:

SELECT GROUP_CONCAT(HEX(foo)) ...

или вы также можете поместить в них длину:

SELECT GROUP_CONCAT(CONCAT(LENGTH(foo), ':', foo)) ...

Не то, чтобы я тестировал: -D

Ответ 6

какой ник сказал действительно, с улучшением - разделитель может быть более одного символа тоже.

Я часто использовал

GROUP_CONCAT(name SEPARATOR '"|"')

Шансы на имя пользователя, содержащее "|" я бы сказал.

Ответ 7

Вы попадаете в ту серую область, где лучше выполнить постпроцесс за пределами мира SQL.

По крайней мере, что бы я сделал: я бы просто ORDER BY, а не GROUP BY, и проверил результаты для обработки группировки в качестве фильтра, выполненного на языке клиента:

  • Начните с инициализации last_id до NULL
  • Извлеките следующую строку набора результатов (если на шаге 6 больше строк)
  • Если идентификатор строки отличается от last_id, запустите новую выходную строку:

    а. если last_id не является NULL, тогда выведите сгруппированную строку

    б. установите новую сгруппированную строку = строку ввода, но сохраните это имя как один массив элементов

    с. установите last_id на значение текущего идентификатора

  • В противном случае (id совпадает с last_id) добавьте имя строки в существующую сгруппированную строку.

  • Вернитесь к шагу 2
  • В противном случае вы закончили; если last_id не является NULL, тогда выведите существующую строку группы.

Затем ваш вывод заканчивается, включая имена, организованные в виде массива, и может решить, как вы хотите обрабатывать/удалять/форматировать их.

Какой язык/система вы используете? PHP? Perl? Ява?

Ответ 8

Джейсон С: Это именно то, с чем я имею дело. Я использую фреймворк PHP MVC и обрабатываю результаты, как вы описываете (несколько строк на результат и код для группировки результатов вместе). Тем не менее, я работал над двумя функциями для моих моделей. Один возвращает список всех необходимых полей, необходимых для воссоздания объекта, а другой - это функция, которая задает строку с полями из первой функции, создавая экземпляр нового объекта. Это позволяет мне запрашивать строку из базы данных и легко возвращать ее обратно в объект, не зная внутренних данных, необходимых модели. Это не работает, когда несколько строк представляют один объект, поэтому я пытался использовать GROUP_CONCAT, чтобы обойти эту проблему.

Ответ 9

Сейчас я разрешаю любому персонажу. Я понимаю, что труба вряд ли появится, но я бы хотел ее разрешить.

Как насчет управляющего символа, который вы все равно должны удалять из приложения? Я сомневаюсь, что вам нужно, например. вкладку или новую строку в поле имени.

Ответ 10

Чтобы развернуть некоторые ответы, я внедрил @derobert второе предложение в PHP, и он работает хорошо. Учитывая MySQL, например:

GROUP_CONCAT(CONCAT(LENGTH(field), ':', field) SEPARATOR '') AS fields

Я использовал следующую функцию для ее разбиения:

function concat_split( $str ) {
    // Need to guard against PHP stupid multibyte string function overloading.
    static $mb_overload_string = null;
    if ( null === $mb_overload_string ) {
        $mb_overload_string = defined( 'MB_OVERLOAD_STRING' )
                && ( ini_get( 'mbstring.func_overload' ) & MB_OVERLOAD_STRING );
    }
    if ( $mb_overload_string ) {
        $mb_internal_encoding = mb_internal_encoding();
        mb_internal_encoding( '8bit' );
    }

    $ret = array();
    for ( $offset = 0; $colon = strpos( $str, ':', $offset ); $offset = $colon + 1 + $len ) {
        $len = intval( substr( $str, $offset, $colon ) );
        $ret[] = substr( $str, $colon + 1, $len );
    }

    if ( $mb_overload_string ) {
        mb_internal_encoding( $mb_internal_encoding );
    }

    return $ret;
}

Я также изначально реализовал предложение @ʞɔıu, используя один из разделителей соков @Lemon. Он работал нормально, но помимо его усложнения он был медленнее, основная проблема заключалась в том, что PCRE позволяет только фиксированную длину lookbehind, поэтому использование предлагаемого регулярного выражения для разделения требует захвата разделителей, в противном случае будут потеряны двойные обратные косые черты в конце строк. Так что, учитывая MySQL, например (примечание 4 PHP backslashes = > 2 MySQL обратная косая черта = > 1 реальная обратная косая черта):

GROUP_CONCAT(REPLACE(REPLACE(field, '\\\\', '\\\\\\\\'),
    CHAR(31), CONCAT('\\\\', CHAR(31))) SEPARATOR 0x1f) AS fields

функция расщепления была:

function concat_split( $str ) {
    $ret = array();
    // 4 PHP backslashes => 2 PCRE backslashes => 1 real backslash.
    $strs = preg_split( '/(?<!\\\\)((?:\\\\\\\\)*+\x1f)/', $str, -1, PREG_SPLIT_DELIM_CAPTURE );
    // Need to add back any captured double backslashes.
    for ( $i = 0, $cnt = count( $strs ); $i < $cnt; $i += 2 ) {
        $ret[] = isset( $strs[ $i + 1 ] ) ? ( $strs[ $i ] . substr( $strs[ $i + 1 ], 0, -1 ) ) : $strs[ $i ];
    }
    return str_replace( array( "\\\x1f", "\\\\" ), array( "\x1f", "\\" ), $ret );
}